]> git.lyx.org Git - lyx.git/blob - src/BufferView_pimpl.C
More errorlist adjustements.
[lyx.git] / src / BufferView_pimpl.C
1 /**
2  * \file BufferView_pimpl.C
3  * Copyright 2002 the LyX Team
4  * Read the file COPYING
5  *
6  * \author various
7  */
8
9 #include <config.h>
10
11 #include "BufferView_pimpl.h"
12 #include "bufferlist.h"
13 #include "buffer.h"
14 #include "buffer_funcs.h"
15 #include "bufferview_funcs.h"
16 #include "lfuns.h"
17 #include "debug.h"
18 #include "factory.h"
19 #include "FloatList.h"
20 #include "funcrequest.h"
21 #include "gettext.h"
22 #include "intl.h"
23 #include "iterators.h"
24 #include "Lsstream.h"
25 #include "lyx_cb.h" // added for Dispatch functions
26 #include "lyx_main.h"
27 #include "lyxfind.h"
28 #include "lyxfunc.h"
29 #include "lyxtext.h"
30 #include "lyxrc.h"
31 #include "lyxrow.h"
32 #include "lastfiles.h"
33 #include "paragraph.h"
34 #include "ParagraphParameters.h"
35 #include "TextCache.h"
36 #include "undo_funcs.h"
37
38 #include "insets/insetfloatlist.h"
39 #include "insets/insetgraphics.h"
40 #include "insets/insetinclude.h"
41 #include "insets/insetref.h"
42 #include "insets/insettext.h"
43
44 #include "frontends/Alert.h"
45 #include "frontends/Dialogs.h"
46 #include "frontends/FileDialog.h"
47 #include "frontends/LyXView.h"
48 #include "frontends/LyXScreenFactory.h"
49 #include "frontends/mouse_state.h"
50 #include "frontends/screen.h"
51 #include "frontends/WorkArea.h"
52 #include "frontends/WorkAreaFactory.h"
53
54 #include "mathed/formulabase.h"
55
56 #include "graphics/Previews.h"
57
58 #include "support/LAssert.h"
59 #include "support/tostr.h"
60 #include "support/filetools.h"
61
62 #include <boost/bind.hpp>
63 #include <boost/signals/connection.hpp>
64
65 #include <unistd.h>
66 #include <sys/wait.h>
67
68
69 using std::vector;
70 using std::find_if;
71 using std::find;
72 using std::pair;
73 using std::endl;
74 using std::make_pair;
75 using std::min;
76
77 using lyx::pos_type;
78 using namespace bv_funcs;
79
80 extern BufferList bufferlist;
81
82
83 namespace {
84
85 unsigned int const saved_positions_num = 20;
86
87 // All the below connection objects are needed because of a bug in some
88 // versions of GCC (<=2.96 are on the suspects list.) By having and assigning
89 // to these connections we avoid a segfault upon startup, and also at exit.
90 // (Lgb)
91
92 boost::signals::connection dispatchcon;
93 boost::signals::connection timecon;
94 boost::signals::connection doccon;
95 boost::signals::connection resizecon;
96 boost::signals::connection kpresscon;
97 boost::signals::connection selectioncon;
98 boost::signals::connection lostcon;
99
100
101 } // anon namespace
102
103
104 BufferView::Pimpl::Pimpl(BufferView * bv, LyXView * owner,
105              int xpos, int ypos, int width, int height)
106         : bv_(bv), owner_(owner), buffer_(0), cursor_timeout(400),
107           using_xterm_cursor(false)
108 {
109         workarea_.reset(WorkAreaFactory::create(xpos, ypos, width, height));
110         screen_.reset(LyXScreenFactory::create(workarea()));
111
112         // Setup the signals
113         doccon = workarea().scrollDocView
114                 .connect(boost::bind(&BufferView::Pimpl::scrollDocView, this, _1));
115         resizecon = workarea().workAreaResize
116                 .connect(boost::bind(&BufferView::Pimpl::workAreaResize, this));
117         dispatchcon = workarea().dispatch
118                 .connect(boost::bind(&BufferView::Pimpl::workAreaDispatch, this, _1));
119         kpresscon = workarea().workAreaKeyPress
120                 .connect(boost::bind(&BufferView::Pimpl::workAreaKeyPress, this, _1, _2));
121         selectioncon = workarea().selectionRequested
122                 .connect(boost::bind(&BufferView::Pimpl::selectionRequested, this));
123         lostcon = workarea().selectionLost
124                 .connect(boost::bind(&BufferView::Pimpl::selectionLost, this));
125
126         timecon = cursor_timeout.timeout
127                 .connect(boost::bind(&BufferView::Pimpl::cursorToggle, this));
128         cursor_timeout.start();
129         saved_positions.resize(saved_positions_num);
130 }
131
132
133 void BufferView::Pimpl::addError(ErrorItem const & ei)
134 {
135         errorlist_.push_back(ei);
136
137 }
138
139
140 bool BufferView::Pimpl::loadLyXFile(string const & filename, bool tolastfiles)
141 {
142         // get absolute path of file and add ".lyx" to the filename if
143         // necessary
144         string s = FileSearch(string(), filename, "lyx");
145         if (s.empty()) {
146                 s = filename;
147         }
148
149         // file already open?
150         if (bufferlist.exists(s)) {
151                 string const file = MakeDisplayPath(s, 20);
152                 string text = bformat(_("The document %1$s is already "
153                                         "loaded.\n\nDo you want to revert "
154                                         "to the saved version?"), file);
155                 int const ret = Alert::prompt(_("Revert to saved document?"),
156                         text, 0, 1,  _("&Revert"), _("&Switch to document"));
157
158                 if (ret != 0) {
159                         buffer(bufferlist.getBuffer(s));
160                         return true;
161                 } else {
162                         // FIXME: should be LFUN_REVERT
163                         if (!bufferlist.close(bufferlist.getBuffer(s), false))
164                                 return false;
165                         // Fall through to new load. (Asger)
166                 }
167         }
168         Buffer * b = bufferlist.newBuffer(s);
169
170         //attach to the error signal in the buffer
171         b->parseError.connect(boost::bind(&BufferView::Pimpl::addError, 
172                                           this, _1));
173
174         bool loaded = ::loadLyXFile(b, s);
175
176         if (! loaded) {
177                 bufferlist.release(b);
178                 string text = bformat(_("The document %1$s does "
179                                         "not yet exist.\n\n"
180                                         "Do you want to create "
181                                         "a new document?"), s);
182                 int const ret = Alert::prompt(_("Create new document?"),
183                          text, 0, 1, _("&Create"), _("Cancel"));
184
185                 if (ret == 0) {
186                         bufferlist.close(buffer_, false);
187                         buffer(newFile(s, string(), true));
188                 }
189         } 
190
191         buffer(b);
192
193         if (tolastfiles)
194                 lastfiles->newFile(b->fileName());
195
196         if (loaded)
197                 bv_->showErrorList(_("Parse"));
198
199         return true;
200 }
201
202 WorkArea & BufferView::Pimpl::workarea() const
203 {
204         return *workarea_.get();
205 }
206
207
208 LyXScreen & BufferView::Pimpl::screen() const
209 {
210         return *screen_.get();
211 }
212
213
214 Painter & BufferView::Pimpl::painter() const
215 {
216         return workarea().getPainter();
217 }
218
219
220 void BufferView::Pimpl::buffer(Buffer * b)
221 {
222         lyxerr[Debug::INFO] << "Setting buffer in BufferView ("
223                             << b << ')' << endl;
224         if (buffer_) {
225                 buffer_->delUser(bv_);
226
227                 // Put the old text into the TextCache, but
228                 // only if the buffer is still loaded.
229                 // Also set the owner of the test to 0
230                 //              bv_->text->owner(0);
231                 textcache.add(buffer_, workarea().workWidth(), bv_->text);
232                 if (lyxerr.debugging())
233                         textcache.show(lyxerr, "BufferView::buffer");
234
235                 bv_->text = 0;
236         }
237
238         // set current buffer
239         buffer_ = b;
240
241         // if we're quitting lyx, don't bother updating stuff
242         if (quitting)
243                 return;
244
245         // if we are closing the buffer, use the first buffer as current
246         if (!buffer_) {
247                 buffer_ = bufferlist.first();
248         }
249
250         if (buffer_) {
251                 lyxerr[Debug::INFO] << "Buffer addr: " << buffer_ << endl;
252                 buffer_->addUser(bv_);
253
254                 // If we don't have a text object for this, we make one
255                 if (bv_->text == 0) {
256                         resizeCurrentBuffer();
257                 }
258
259                 // FIXME: needed when ?
260                 bv_->text->top_y(screen().topCursorVisible(bv_->text->cursor, bv_->text->top_y()));
261
262                 // Buffer-dependent dialogs should be updated or
263                 // hidden. This should go here because some dialogs (eg ToC)
264                 // require bv_->text.
265                 owner_->getDialogs().updateBufferDependent(true);
266         } else {
267                 lyxerr[Debug::INFO] << "  No Buffer!" << endl;
268                 owner_->getDialogs().hideBufferDependent();
269
270                 // Also remove all remaining text's from the testcache.
271                 // (there should not be any!) (if there is any it is a
272                 // bug!)
273                 if (lyxerr.debugging())
274                         textcache.show(lyxerr, "buffer delete all");
275                 textcache.clear();
276         }
277
278         repaint();
279         updateScrollbar();
280         owner_->updateMenubar();
281         owner_->updateToolbar();
282         owner_->updateLayoutChoice();
283         owner_->updateWindowTitle();
284
285         if (buffer_) {
286                 // Don't forget to update the Layout
287                 string const layoutname =
288                         bv_->text->cursor.par()->layout()->name();
289                 owner_->setLayout(layoutname);
290         }
291
292         if (grfx::Previews::activated() && buffer_)
293                 grfx::Previews::get().generateBufferPreviews(*buffer_);
294 }
295
296
297 bool BufferView::Pimpl::fitCursor()
298 {
299         bool ret;
300
301         if (bv_->theLockingInset()) {
302                 bv_->theLockingInset()->fitInsetCursor(bv_);
303                 ret = true;
304         } else {
305                 ret = screen().fitCursor(bv_->text, bv_);
306         }
307
308         dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
309
310         // We need to always update, in case we did a
311         // paste and we stayed anchored to a row, but
312         // the actual height of the doc changed ...
313         updateScrollbar();
314         return ret;
315 }
316
317
318 void BufferView::Pimpl::redoCurrentBuffer()
319 {
320         lyxerr[Debug::INFO] << "BufferView::redoCurrentBuffer" << endl;
321         if (buffer_ && bv_->text) {
322                 resizeCurrentBuffer();
323                 updateScrollbar();
324                 owner_->updateLayoutChoice();
325                 repaint();
326         }
327 }
328
329
330 int BufferView::Pimpl::resizeCurrentBuffer()
331 {
332         lyxerr[Debug::INFO] << "resizeCurrentBuffer" << endl;
333
334         ParagraphList::iterator par;
335         ParagraphList::iterator selstartpar;
336         ParagraphList::iterator selendpar;
337         UpdatableInset * the_locking_inset = 0;
338
339         pos_type pos = 0;
340         pos_type selstartpos = 0;
341         pos_type selendpos = 0;
342         bool selection = false;
343         bool mark_set  = false;
344
345         owner_->busy(true);
346
347         owner_->message(_("Formatting document..."));
348
349         if (bv_->text) {
350                 par = bv_->text->cursor.par();
351                 pos = bv_->text->cursor.pos();
352                 selstartpar = bv_->text->selection.start.par();
353                 selstartpos = bv_->text->selection.start.pos();
354                 selendpar = bv_->text->selection.end.par();
355                 selendpos = bv_->text->selection.end.pos();
356                 selection = bv_->text->selection.set();
357                 mark_set = bv_->text->selection.mark();
358                 the_locking_inset = bv_->theLockingInset();
359                 buffer_->resizeInsets(bv_);
360                 // I don't think the delete and new are necessary here we 
361                 // just could call only init! (Jug 20020419)
362                 delete bv_->text;
363                 bv_->text = new LyXText(bv_);
364                 bv_->text->init(bv_);
365         } else {
366                 // See if we have a text in TextCache that fits
367                 // the new buffer_ with the correct width.
368                 bv_->text = textcache.findFit(buffer_, workarea().workWidth());
369                 if (bv_->text) {
370                         if (lyxerr.debugging()) {
371                                 lyxerr << "Found a LyXText that fits:\n";
372                                 textcache.show(lyxerr, make_pair(buffer_, make_pair(workarea().workWidth(), bv_->text)));
373                         }
374                         // Set the owner of the newly found text
375                         //      bv_->text->owner(bv_);
376                         if (lyxerr.debugging())
377                                 textcache.show(lyxerr, "resizeCurrentBuffer");
378
379                         buffer_->resizeInsets(bv_);
380                 } else {
381                         bv_->text = new LyXText(bv_);
382                         bv_->text->init(bv_);
383                         //buffer_->resizeInsets(bv_);
384                 }
385
386                 par = bv_->text->ownerParagraphs().end();
387                 selstartpar = bv_->text->ownerParagraphs().end();
388                 selendpar = bv_->text->ownerParagraphs().end();
389         }
390
391         if (par != bv_->text->ownerParagraphs().end()) {
392                 bv_->text->selection.set(true);
393                 // At this point just to avoid the Delete-Empty-Paragraph-
394                 // Mechanism when setting the cursor.
395                 bv_->text->selection.mark(mark_set);
396                 if (selection) {
397                         bv_->text->setCursor(selstartpar, selstartpos);
398                         bv_->text->selection.cursor = bv_->text->cursor;
399                         bv_->text->setCursor(selendpar, selendpos);
400                         bv_->text->setSelection();
401                         bv_->text->setCursor(par, pos);
402                 } else {
403                         bv_->text->setCursor(par, pos);
404                         bv_->text->selection.cursor = bv_->text->cursor;
405                         bv_->text->selection.set(false);
406                 }
407                 // remake the inset locking
408                 bv_->theLockingInset(the_locking_inset);
409         }
410
411         bv_->text->top_y(screen().topCursorVisible(bv_->text->cursor, bv_->text->top_y()));
412
413         switchKeyMap();
414         owner_->busy(false);
415
416         // reset the "Formatting..." message
417         owner_->clearMessage();
418
419         updateScrollbar();
420
421         return 0;
422 }
423
424
425 void BufferView::Pimpl::repaint()
426 {
427         // Regenerate the screen.
428         screen().redraw(bv_->text, bv_);
429 }
430
431
432 void BufferView::Pimpl::updateScrollbar()
433 {
434         if (!bv_->text) {
435                 lyxerr[Debug::GUI] << "no text in updateScrollbar" << endl;
436                 workarea().setScrollbarParams(0, 0, 0);
437                 return;
438         }
439
440         LyXText const & t = *bv_->text;
441
442         lyxerr[Debug::GUI] << "Updating scrollbar: h " << t.height << ", top_y() "
443                 << t.top_y() << ", default height " << defaultRowHeight() << endl;
444
445         workarea().setScrollbarParams(t.height, t.top_y(), defaultRowHeight());
446 }
447
448
449 void BufferView::Pimpl::scrollDocView(int value)
450 {
451         lyxerr[Debug::GUI] << "scrollDocView of " << value << endl;
452
453         if (!buffer_)
454                 return;
455
456         screen().hideCursor();
457
458         screen().draw(bv_->text, bv_, value);
459
460         if (!lyxrc.cursor_follows_scrollbar)
461                 return;
462
463         LyXText * vbt = bv_->text;
464
465         int const height = defaultRowHeight();
466         int const first = static_cast<int>((bv_->text->top_y() + height));
467         int const last = static_cast<int>((bv_->text->top_y() + workarea().workHeight() - height));
468
469         if (vbt->cursor.y() < first)
470                 vbt->setCursorFromCoordinates(0, first);
471         else if (vbt->cursor.y() > last)
472                 vbt->setCursorFromCoordinates(0, last);
473
474         owner_->updateLayoutChoice();
475 }
476
477
478 void BufferView::Pimpl::scroll(int lines)
479 {
480         if (!buffer_) {
481                 return;
482         }
483
484         LyXText const * t = bv_->text;
485         int const line_height = defaultRowHeight();
486
487         // The new absolute coordinate
488         int new_top_y = t->top_y() + lines * line_height;
489
490         // Restrict to a valid value
491         new_top_y = std::min(t->height - 4 * line_height, new_top_y);
492         new_top_y = std::max(0, new_top_y);
493
494         scrollDocView(new_top_y);
495
496         // Update the scrollbar.
497         workarea().setScrollbarParams(t->height, t->top_y(), defaultRowHeight());
498 }
499
500
501 void BufferView::Pimpl::workAreaKeyPress(LyXKeySymPtr key,
502                                          key_modifier::state state)
503 {
504         bv_->owner()->getLyXFunc().processKeySym(key, state);
505
506         /* This is perhaps a bit of a hack. When we move
507          * around, or type, it's nice to be able to see
508          * the cursor immediately after the keypress. So
509          * we reset the toggle timeout and force the visibility
510          * of the cursor. Note we cannot do this inside
511          * dispatch() itself, because that's called recursively.
512          */
513         if (available()) {
514                 cursor_timeout.restart();
515                 screen().showCursor(*bv_);
516         }
517 }
518
519
520 void BufferView::Pimpl::selectionRequested()
521 {
522         static string sel;
523
524         if (!available())
525                 return;
526
527         LyXText * text = bv_->getLyXText();
528
529         if (text->selection.set() &&
530                 (!bv_->text->xsel_cache.set() ||
531                  text->selection.start != bv_->text->xsel_cache.start ||
532                  text->selection.end != bv_->text->xsel_cache.end))
533         {
534                 bv_->text->xsel_cache = text->selection;
535                 sel = text->selectionAsString(bv_->buffer(), false);
536         } else if (!text->selection.set()) {
537                 sel = string();
538                 bv_->text->xsel_cache.set(false);
539         }
540         if (!sel.empty()) {
541                 workarea().putClipboard(sel);
542         }
543 }
544
545
546 void BufferView::Pimpl::selectionLost()
547 {
548         if (available()) {
549                 screen().hideCursor();
550                 toggleSelection();
551                 bv_->getLyXText()->clearSelection();
552                 bv_->text->xsel_cache.set(false);
553         }
554 }
555
556
557 void BufferView::Pimpl::workAreaResize()
558 {
559         static int work_area_width;
560         static int work_area_height;
561
562         bool const widthChange = workarea().workWidth() != work_area_width;
563         bool const heightChange = workarea().workHeight() != work_area_height;
564
565         // update from work area
566         work_area_width = workarea().workWidth();
567         work_area_height = workarea().workHeight();
568
569         if (buffer_ != 0) {
570                 if (widthChange) {
571                         // The visible LyXView need a resize
572                         resizeCurrentBuffer();
573
574                         // Remove all texts from the textcache
575                         // This is not _really_ what we want to do. What
576                         // we really want to do is to delete in textcache
577                         // that does not have a BufferView with matching
578                         // width, but as long as we have only one BufferView
579                         // deleting all gives the same result.
580                         if (lyxerr.debugging())
581                                 textcache.show(lyxerr, "Expose delete all");
582                         textcache.clear();
583                         // FIXME: this is already done in resizeCurrentBuffer() ??
584                         buffer_->resizeInsets(bv_);
585                 } else if (heightChange) {
586                         // fitCursor() ensures we don't jump back
587                         // to the start of the document on vertical
588                         // resize
589                         fitCursor();
590                 }
591         }
592
593         if (widthChange || heightChange) {
594                 repaint();
595         }
596
597         // always make sure that the scrollbar is sane.
598         updateScrollbar();
599         owner_->updateLayoutChoice();
600         return;
601 }
602
603
604 void BufferView::Pimpl::update()
605 {
606         if (!bv_->theLockingInset() || !bv_->theLockingInset()->nodraw()) {
607                 screen().update(*bv_);
608                 bv_->text->clearPaint();
609         }
610 }
611
612
613 void BufferView::Pimpl::update(LyXText * text, BufferView::UpdateCodes f)
614 {
615         if (!text->selection.set() && (f & SELECT)) {
616                 text->selection.cursor = text->cursor;
617         }
618
619         text->fullRebreak();
620
621         if (text->inset_owner) {
622                 text->inset_owner->setUpdateStatus(bv_, InsetText::NONE);
623                 updateInset(text->inset_owner);
624         } else {
625                 update();
626         }
627 }
628
629
630 void BufferView::Pimpl::update(BufferView::UpdateCodes f)
631 {
632         LyXText * text = bv_->text;
633
634         if (!text->selection.set() && (f & SELECT)) {
635                 text->selection.cursor = text->cursor;
636         }
637
638         text->fullRebreak();
639
640         if (text->inset_owner) {
641                 text->inset_owner->setUpdateStatus(bv_, InsetText::NONE);
642                 updateInset(text->inset_owner);
643         } else {
644                 update();
645         }
646 }
647
648
649 // Callback for cursor timer
650 void BufferView::Pimpl::cursorToggle()
651 {
652         if (!buffer_) {
653                 cursor_timeout.restart();
654                 return;
655         }
656
657         screen().toggleCursor(*bv_);
658
659         cursor_timeout.restart();
660 }
661
662
663 bool BufferView::Pimpl::available() const
664 {
665         if (buffer_ && bv_->text)
666                 return true;
667         return false;
668 }
669
670
671 Change const BufferView::Pimpl::getCurrentChange()
672 {
673         if (!bv_->buffer()->params.tracking_changes)
674                 return Change(Change::UNCHANGED);
675
676         LyXText * t(bv_->getLyXText());
677
678         if (!t->selection.set())
679                 return Change(Change::UNCHANGED);
680
681         LyXCursor const & cur(t->selection.start);
682         return cur.par()->lookupChangeFull(cur.pos());
683 }
684
685
686 void BufferView::Pimpl::beforeChange(LyXText * text)
687 {
688         toggleSelection();
689         text->clearSelection();
690 }
691
692
693 void BufferView::Pimpl::savePosition(unsigned int i)
694 {
695         if (i >= saved_positions_num)
696                 return;
697         saved_positions[i] = Position(buffer_->fileName(),
698                                       bv_->text->cursor.par()->id(),
699                                       bv_->text->cursor.pos());
700         if (i > 0)
701                 owner_->message(bformat(_("Saved bookmark %1$s"), tostr(i)));
702 }
703
704
705 void BufferView::Pimpl::restorePosition(unsigned int i)
706 {
707         if (i >= saved_positions_num)
708                 return;
709
710         string const fname = saved_positions[i].filename;
711
712         beforeChange(bv_->text);
713
714         if (fname != buffer_->fileName()) {
715                 Buffer * b;
716                 if (bufferlist.exists(fname))
717                         b = bufferlist.getBuffer(fname);
718                 else {
719                         b = bufferlist.newBuffer(fname);
720                         ::loadLyXFile(b, fname); // don't ask, just load it
721                 }
722                 if (b != 0) 
723                         buffer(b);
724         }
725
726         ParIterator par = buffer_->getParFromID(saved_positions[i].par_id);
727         if (par == buffer_->par_iterator_end())
728                 return;
729
730         bv_->text->setCursor(par.pit(),
731                              min(par->size(), saved_positions[i].par_pos));
732
733         update(BufferView::SELECT);
734         if (i > 0)
735                 owner_->message(bformat(_("Moved to bookmark %1$s"), tostr(i)));
736 }
737
738
739 bool BufferView::Pimpl::isSavedPosition(unsigned int i)
740 {
741         if (i >= saved_positions_num)
742                 return false;
743
744         return !saved_positions[i].filename.empty();
745 }
746
747
748 void BufferView::Pimpl::switchKeyMap()
749 {
750         if (!lyxrc.rtl_support)
751                 return;
752
753         LyXText * text = bv_->getLyXText();
754         if (text->real_current_font.isRightToLeft()
755             && !(bv_->theLockingInset()
756                  && bv_->theLockingInset()->lyxCode() == Inset::ERT_CODE))
757         {
758                 if (owner_->getIntl().keymap == Intl::PRIMARY)
759                         owner_->getIntl().KeyMapSec();
760         } else {
761                 if (owner_->getIntl().keymap == Intl::SECONDARY)
762                         owner_->getIntl().KeyMapPrim();
763         }
764 }
765
766
767 void BufferView::Pimpl::insetUnlock()
768 {
769         if (bv_->theLockingInset()) {
770                 bv_->theLockingInset()->insetUnlock(bv_);
771                 bv_->theLockingInset(0);
772                 finishUndo();
773         }
774 }
775
776
777 void BufferView::Pimpl::toggleSelection(bool b)
778 {
779         if (bv_->theLockingInset())
780                 bv_->theLockingInset()->toggleSelection(bv_, b);
781         screen().toggleSelection(bv_->text, bv_, b);
782 }
783
784
785 void BufferView::Pimpl::toggleToggle()
786 {
787         screen().toggleToggle(bv_->text, bv_);
788 }
789
790
791 void BufferView::Pimpl::center()
792 {
793         LyXText * t = bv_->text;
794
795         beforeChange(t);
796         int const half_height = workarea().workHeight() / 2;
797         int new_y = 0;
798
799         if (t->cursor.y() > half_height) {
800                 new_y = t->cursor.y() - half_height;
801         }
802
803         // FIXME: look at this comment again ...
804
805         // FIXME: can we do this w/o calling screen directly ?
806         // This updates top_y() but means the fitCursor() call
807         // from the update(FITCUR) doesn't realise that we might
808         // have moved (e.g. from GOTOPARAGRAPH), so doesn't cause
809         // the scrollbar to be updated as it should, so we have
810         // to do it manually. Any operation that does a center()
811         // and also might have moved top_y() must make sure to call
812         // updateScrollbar() currently. Never mind that this is a
813         // pretty obfuscated way of updating t->top_y()
814         screen().draw(t, bv_, new_y);
815
816         update(BufferView::SELECT);
817 }
818
819
820 void BufferView::Pimpl::stuffClipboard(string const & stuff) const
821 {
822         workarea().putClipboard(stuff);
823 }
824
825
826 /*
827  * Dispatch functions for actions which can be valid for BufferView->text
828  * and/or InsetText->text!!!
829  */
830
831
832 Inset * BufferView::Pimpl::getInsetByCode(Inset::Code code)
833 {
834 #if 0
835         LyXCursor cursor = bv_->getLyXText()->cursor;
836         Buffer::inset_iterator it =
837                 find_if(Buffer::inset_iterator(
838                         cursor.par(), cursor.pos()),
839                         buffer_->inset_iterator_end(),
840                         lyx::compare_memfun(&Inset::lyxCode, code));
841         return it != buffer_->inset_iterator_end() ? (*it) : 0;
842 #else
843         // Ok, this is a little bit too brute force but it
844         // should work for now. Better infrastructure is comming. (Lgb)
845
846         Buffer * b = bv_->buffer();
847         LyXCursor cursor = bv_->getLyXText()->cursor;
848
849         Buffer::inset_iterator beg = b->inset_iterator_begin();
850         Buffer::inset_iterator end = b->inset_iterator_end();
851
852         bool cursor_par_seen = false;
853
854         for (; beg != end; ++beg) {
855                 if (beg.getPar() == cursor.par()) {
856                         cursor_par_seen = true;
857                 }
858                 if (cursor_par_seen) {
859                         if (beg.getPar() == cursor.par()
860                             && beg.getPos() >= cursor.pos()) {
861                                 break;
862                         } else if (beg.getPar() != cursor.par()) {
863                                 break;
864                         }
865                 }
866
867         }
868         if (beg != end) {
869                 // Now find the first inset that matches code.
870                 for (; beg != end; ++beg) {
871                         if (beg->lyxCode() == code) {
872                                 return &(*beg);
873                         }
874                 }
875         }
876         return 0;
877 #endif
878 }
879
880
881 void BufferView::Pimpl::MenuInsertLyXFile(string const & filen)
882 {
883         string filename = filen;
884
885         if (filename.empty()) {
886                 // Launch a file browser
887                 string initpath = lyxrc.document_path;
888
889                 if (available()) {
890                         string const trypath = owner_->buffer()->filePath();
891                         // If directory is writeable, use this as default.
892                         if (IsDirWriteable(trypath))
893                                 initpath = trypath;
894                 }
895
896                 FileDialog fileDlg(_("Select LyX document to insert"),
897                         LFUN_FILE_INSERT,
898                         make_pair(string(_("Documents|#o#O")),
899                                   string(lyxrc.document_path)),
900                         make_pair(string(_("Examples|#E#e")),
901                                   string(AddPath(system_lyxdir, "examples"))));
902
903                 FileDialog::Result result =
904                         fileDlg.open(initpath,
905                                        _("*.lyx| LyX Documents (*.lyx)"));
906
907                 if (result.first == FileDialog::Later)
908                         return;
909
910                 filename = result.second;
911
912                 // check selected filename
913                 if (filename.empty()) {
914                         owner_->message(_("Canceled."));
915                         return;
916                 }
917         }
918
919         // get absolute path of file and add ".lyx" to the filename if
920         // necessary
921         filename = FileSearch(string(), filename, "lyx");
922
923         string const disp_fn = MakeDisplayPath(filename);
924         owner_->message(bformat(_("Inserting document %1$s..."), disp_fn));
925         bool const res = bv_->insertLyXFile(filename);
926         if (res)
927                 owner_->message(bformat(_("Document %1$s inserted."), disp_fn));
928         else
929                 owner_->message(bformat(_("Could not insert document %1$s"), disp_fn));
930 }
931
932
933 void BufferView::Pimpl::trackChanges()
934 {
935         Buffer * buf(bv_->buffer());
936         bool const tracking(buf->params.tracking_changes);
937
938         if (!tracking) {
939                 ParIterator const end = buf->par_iterator_end();
940                 for (ParIterator it = buf->par_iterator_begin(); it != end; ++it)
941                         it->trackChanges();
942                 buf->params.tracking_changes = true;
943
944                 // we cannot allow undos beyond the freeze point
945                 buf->undostack.clear();
946         } else {
947                 update(BufferView::SELECT);
948                 bv_->text->setCursor(buf->paragraphs.begin(), 0);
949 #warning changes FIXME
950                 //moveCursorUpdate(false);
951
952                 bool found = lyxfind::findNextChange(bv_);
953                 if (found) {
954                         owner_->getDialogs().show("changes");
955                         return;
956                 }
957
958                 ParIterator const end = buf->par_iterator_end();
959                 for (ParIterator it = buf->par_iterator_begin(); it != end; ++it)
960                         it->untrackChanges();
961                 buf->params.tracking_changes = false;
962         }
963
964         buf->redostack.clear();
965 }
966
967
968 // Doesn't go through lyxfunc, so we need to update the
969 // layout choice etc. ourselves
970 bool BufferView::Pimpl::workAreaDispatch(FuncRequest const & ev_in)
971 {
972         // e.g. Qt mouse press when no buffer
973         if (!available())
974                 return false;
975
976         screen().hideCursor();
977
978         bool const res = dispatch(ev_in);
979
980         // see workAreaKeyPress
981         cursor_timeout.restart();
982         screen().showCursor(*bv_);
983
984         // FIXME: we should skip these when selecting
985         bv_->owner()->updateLayoutChoice();
986         bv_->owner()->updateToolbar();
987         bv_->fitCursor();
988
989         // slight hack: this is only called currently when
990         // we clicked somewhere, so we force through the display
991         // of the new status here.
992         bv_->owner()->clearMessage();
993
994         return res;
995 }
996
997
998 bool BufferView::Pimpl::dispatch(FuncRequest const & ev_in)
999 {
1000         // Make sure that the cached BufferView is correct.
1001         FuncRequest ev = ev_in;
1002         ev.setView(bv_);
1003
1004         lyxerr[Debug::ACTION] << "BufferView::Pimpl::Dispatch:"
1005                 << " action[" << ev.action << ']'
1006                 << " arg[" << ev.argument << ']'
1007                 << " x[" << ev.x << ']'
1008                 << " y[" << ev.y << ']'
1009                 << " button[" << ev.button() << ']'
1010                 << endl;
1011
1012         LyXTextClass const & tclass = buffer_->params.getLyXTextClass();
1013
1014         switch (ev.action) {
1015
1016         case LFUN_SCROLL_INSET:
1017                 // this is not handled here as this function is only active
1018                 // if we have a locking_inset and that one is (or contains)
1019                 // a tabular-inset
1020                 break;
1021
1022         case LFUN_FILE_INSERT:
1023                 MenuInsertLyXFile(ev.argument);
1024                 break;
1025
1026         case LFUN_FILE_INSERT_ASCII_PARA:
1027                 InsertAsciiFile(bv_, ev.argument, true);
1028                 break;
1029
1030         case LFUN_FILE_INSERT_ASCII:
1031                 InsertAsciiFile(bv_, ev.argument, false);
1032                 break;
1033
1034         case LFUN_LANGUAGE:
1035                 lang(bv_, ev.argument);
1036                 switchKeyMap();
1037                 owner_->view_state_changed();
1038                 break;
1039
1040         case LFUN_EMPH:
1041                 emph(bv_);
1042                 owner_->view_state_changed();
1043                 break;
1044
1045         case LFUN_BOLD:
1046                 bold(bv_);
1047                 owner_->view_state_changed();
1048                 break;
1049
1050         case LFUN_NOUN:
1051                 noun(bv_);
1052                 owner_->view_state_changed();
1053                 break;
1054
1055         case LFUN_CODE:
1056                 code(bv_);
1057                 owner_->view_state_changed();
1058                 break;
1059
1060         case LFUN_SANS:
1061                 sans(bv_);
1062                 owner_->view_state_changed();
1063                 break;
1064
1065         case LFUN_ROMAN:
1066                 roman(bv_);
1067                 owner_->view_state_changed();
1068                 break;
1069
1070         case LFUN_DEFAULT:
1071                 styleReset(bv_);
1072                 owner_->view_state_changed();
1073                 break;
1074
1075         case LFUN_UNDERLINE:
1076                 underline(bv_);
1077                 owner_->view_state_changed();
1078                 break;
1079
1080         case LFUN_FONT_SIZE:
1081                 fontSize(bv_, ev.argument);
1082                 owner_->view_state_changed();
1083                 break;
1084
1085         case LFUN_FONT_STATE:
1086                 owner_->getLyXFunc().setMessage(currentState(bv_));
1087                 break;
1088
1089         case LFUN_INSERT_LABEL: {
1090                 // Try and generate a valid label
1091                 string const contents = ev.argument.empty() ?
1092                         getPossibleLabel(*bv_) : ev.argument;
1093                 InsetCommandParams icp("label", contents);
1094                 string data = InsetCommandMailer::params2string("label", icp);
1095                 owner_->getDialogs().show("label", data, 0);
1096         }
1097         break;
1098
1099         case LFUN_BOOKMARK_SAVE:
1100                 savePosition(strToUnsignedInt(ev.argument));
1101                 break;
1102
1103         case LFUN_BOOKMARK_GOTO:
1104                 restorePosition(strToUnsignedInt(ev.argument));
1105                 break;
1106
1107         case LFUN_REF_GOTO: {
1108                 string label = ev.argument;
1109                 if (label.empty()) {
1110                         InsetRef * inset =
1111                                 static_cast<InsetRef*>(getInsetByCode(Inset::REF_CODE));
1112                         if (inset) {
1113                                 label = inset->getContents();
1114                                 savePosition(0);
1115                         }
1116                 }
1117
1118                 if (!label.empty())
1119                         bv_->gotoLabel(label);
1120         }
1121         break;
1122
1123         // --- accented characters ---------------------------
1124
1125         case LFUN_UMLAUT:
1126         case LFUN_CIRCUMFLEX:
1127         case LFUN_GRAVE:
1128         case LFUN_ACUTE:
1129         case LFUN_TILDE:
1130         case LFUN_CEDILLA:
1131         case LFUN_MACRON:
1132         case LFUN_DOT:
1133         case LFUN_UNDERDOT:
1134         case LFUN_UNDERBAR:
1135         case LFUN_CARON:
1136         case LFUN_SPECIAL_CARON:
1137         case LFUN_BREVE:
1138         case LFUN_TIE:
1139         case LFUN_HUNG_UMLAUT:
1140         case LFUN_CIRCLE:
1141         case LFUN_OGONEK:
1142                 if (ev.argument.empty()) {
1143                         // As always...
1144                         owner_->getLyXFunc().handleKeyFunc(ev.action);
1145                 } else {
1146                         owner_->getLyXFunc().handleKeyFunc(ev.action);
1147                         owner_->getIntl().getTransManager()
1148                                 .TranslateAndInsert(ev.argument[0], bv_->getLyXText());
1149                         update(bv_->getLyXText(), BufferView::SELECT);
1150                 }
1151                 break;
1152
1153         case LFUN_MATH_MACRO:
1154         case LFUN_MATH_DELIM:
1155         case LFUN_INSERT_MATRIX:
1156         case LFUN_INSERT_MATH:
1157         case LFUN_MATH_IMPORT_SELECTION: // Imports LaTeX from the X selection
1158         case LFUN_MATH_DISPLAY:          // Open or create a displayed math inset
1159         case LFUN_MATH_MODE:             // Open or create an inlined math inset
1160                 mathDispatch(ev);
1161                 break;
1162
1163         case LFUN_INSET_APPLY: {
1164                 string const name = ev.getArg(0);
1165
1166                 InsetBase * inset = owner_->getDialogs().getOpenInset(name);
1167                 if (inset) {
1168                         // This works both for 'original' and 'mathed' insets.
1169                         // Note that the localDispatch performs updateInset
1170                         // also.
1171                         FuncRequest fr(bv_, LFUN_INSET_MODIFY, ev.argument);
1172                         inset->localDispatch(fr);
1173                 } else {
1174                         FuncRequest fr(bv_, LFUN_INSET_INSERT, ev.argument);
1175                         dispatch(fr);
1176                 }
1177         }
1178         break;
1179
1180         case LFUN_INSET_INSERT: {
1181                 Inset * inset = createInset(ev);
1182                 if (inset && insertInset(inset)) {
1183                         updateInset(inset);
1184
1185                         string const name = ev.getArg(0);
1186                         if (name == "bibitem") {
1187                                 // We need to do a redraw because the maximum
1188                                 // InsetBibitem width could have changed
1189 #warning please check you mean repaint() not update(),
1190 #warning and whether the repaint() is needed at all
1191                                 bv_->repaint();
1192                                 bv_->fitCursor();
1193                         }
1194                 } else {
1195                         delete inset;
1196                 }
1197         }
1198         break;
1199
1200         case LFUN_FLOAT_LIST:
1201                 if (tclass.floats().typeExist(ev.argument)) {
1202                         Inset * inset = new InsetFloatList(ev.argument);
1203                         if (!insertInset(inset, tclass.defaultLayoutName()))
1204                                 delete inset;
1205                 } else {
1206                         lyxerr << "Non-existent float type: "
1207                                << ev.argument << endl;
1208                 }
1209                 break;
1210
1211         case LFUN_LAYOUT_PARAGRAPH: {
1212                 Paragraph const * par = &*bv_->getLyXText()->cursor.par();
1213                 if (!par)
1214                         break;
1215
1216                 string data;
1217                 params2string(*par, data);
1218
1219                 data = "show\n" + data;
1220                 bv_->owner()->getDialogs().show("paragraph", data);
1221                 break;
1222         }
1223
1224         case LFUN_PARAGRAPH_UPDATE: {
1225                 if (!bv_->owner()->getDialogs().visible("paragraph"))
1226                         break;
1227                 Paragraph const * par = &*bv_->getLyXText()->cursor.par();
1228                 if (!par)
1229                         break;
1230
1231                 string data;
1232                 params2string(*par, data);
1233
1234                 // Will the paragraph accept changes from the dialog?
1235                 Inset * const inset = par->inInset();
1236                 bool const accept =
1237                         !(inset && inset->forceDefaultParagraphs(inset));
1238
1239                 data = "update " + tostr(accept) + '\n' + data;
1240                 bv_->owner()->getDialogs().update("paragraph", data);
1241                 break;
1242         }
1243
1244         case LFUN_PARAGRAPH_APPLY:
1245                 setParagraphParams(*bv_, ev.argument);
1246                 break;
1247
1248         case LFUN_THESAURUS_ENTRY:
1249         {
1250                 string arg = ev.argument;
1251
1252                 if (arg.empty()) {
1253                         arg = bv_->getLyXText()->selectionAsString(buffer_,
1254                                                                    false);
1255
1256                         // FIXME
1257                         if (arg.size() > 100 || arg.empty()) {
1258                                 // Get word or selection
1259                                 bv_->getLyXText()->selectWordWhenUnderCursor(LyXText::WHOLE_WORD);
1260                                 arg = bv_->getLyXText()->selectionAsString(buffer_, false);
1261                                 // FIXME: where is getLyXText()->unselect(bv_) ?
1262                         }
1263                 }
1264
1265                 bv_->owner()->getDialogs().show("thesaurus", arg);
1266         }
1267                 break;
1268
1269         case LFUN_TRACK_CHANGES:
1270                 trackChanges();
1271                 break;
1272
1273         case LFUN_MERGE_CHANGES:
1274                 owner_->getDialogs().show("changes");
1275                 break;
1276
1277         case LFUN_ACCEPT_ALL_CHANGES: {
1278                 update(BufferView::SELECT);
1279                 bv_->text->setCursor(bv_->buffer()->paragraphs.begin(), 0);
1280 #warning FIXME changes
1281                 //moveCursorUpdate(false);
1282
1283                 while (lyxfind::findNextChange(bv_)) {
1284                         bv_->getLyXText()->acceptChange();
1285                 }
1286                 update(BufferView::SELECT);
1287                 break;
1288         }
1289
1290         case LFUN_REJECT_ALL_CHANGES: {
1291                 update(BufferView::SELECT);
1292                 bv_->text->setCursor(bv_->buffer()->paragraphs.begin(), 0);
1293 #warning FIXME changes
1294                 //moveCursorUpdate(false);
1295
1296                 while (lyxfind::findNextChange(bv_)) {
1297                         bv_->getLyXText()->rejectChange();
1298                 }
1299                 update(BufferView::SELECT);
1300                 break;
1301         }
1302
1303         case LFUN_ACCEPT_CHANGE: {
1304                 bv_->getLyXText()->acceptChange();
1305                 update(BufferView::SELECT);
1306                 break;
1307         }
1308
1309         case LFUN_REJECT_CHANGE: {
1310                 bv_->getLyXText()->rejectChange();
1311                 update(BufferView::SELECT);
1312                 break;
1313         }
1314
1315         case LFUN_UNKNOWN_ACTION:
1316                 ev.errorMessage(N_("Unknown function!"));
1317                 break;
1318
1319         default:
1320                 return bv_->getLyXText()->dispatch(FuncRequest(ev, bv_));
1321         } // end of switch
1322
1323         return true;
1324 }
1325
1326
1327 bool BufferView::Pimpl::insertInset(Inset * inset, string const & lout)
1328 {
1329         // if we are in a locking inset we should try to insert the
1330         // inset there otherwise this is a illegal function now
1331         if (bv_->theLockingInset()) {
1332                 if (bv_->theLockingInset()->insetAllowed(inset))
1333                         return bv_->theLockingInset()->insertInset(bv_, inset);
1334                 return false;
1335         }
1336
1337         // not quite sure if we want this...
1338         setCursorParUndo(bv_);
1339         freezeUndo();
1340
1341         beforeChange(bv_->text);
1342         if (!lout.empty()) {
1343                 update(BufferView::SELECT);
1344                 bv_->text->breakParagraph(bv_->buffer()->paragraphs);
1345                 update(BufferView::SELECT);
1346
1347                 if (!bv_->text->cursor.par()->empty()) {
1348                         bv_->text->cursorLeft(bv_);
1349
1350                         bv_->text->breakParagraph(bv_->buffer()->paragraphs);
1351                         update(BufferView::SELECT);
1352                 }
1353
1354                 string lres = lout;
1355                 LyXTextClass const & tclass =
1356                         buffer_->params.getLyXTextClass();
1357                 bool hasLayout = tclass.hasLayout(lres);
1358                 string lay = tclass.defaultLayoutName();
1359
1360                 if (hasLayout != false) {
1361                         // layout found
1362                         lay = lres;
1363                 } else {
1364                         // layout not fount using default
1365                         lay = tclass.defaultLayoutName();
1366                 }
1367
1368                 bv_->text->setLayout(lay);
1369
1370                 bv_->text->setParagraph(0, 0,
1371                                    0, 0,
1372                                    VSpace(VSpace::NONE), VSpace(VSpace::NONE),
1373                                    Spacing(),
1374                                    LYX_ALIGN_LAYOUT,
1375                                    string(),
1376                                    0);
1377                 update(BufferView::SELECT);
1378         }
1379
1380         bv_->text->insertInset(inset);
1381         update(BufferView::SELECT);
1382
1383         unFreezeUndo();
1384         return true;
1385 }
1386
1387
1388 void BufferView::Pimpl::updateInset(Inset * inset)
1389 {
1390         if (!inset || !available())
1391                 return;
1392
1393         // first check for locking insets
1394         if (bv_->theLockingInset()) {
1395                 if (bv_->theLockingInset() == inset) {
1396                         if (bv_->text->updateInset(inset)) {
1397                                 update();
1398                                 updateScrollbar();
1399                                 return;
1400                         }
1401                 } else if (bv_->theLockingInset()->updateInsetInInset(bv_, inset)) {
1402                         if (bv_->text->updateInset(bv_->theLockingInset())) {
1403                                 update();
1404                                 updateScrollbar();
1405                                 return;
1406                         }
1407                 }
1408         }
1409
1410         // then check if the inset is a top_level inset (has no owner)
1411         // if yes do the update as always otherwise we have to update the
1412         // toplevel inset where this inset is inside
1413         Inset * tl_inset = inset;
1414         while (tl_inset->owner())
1415                 tl_inset = tl_inset->owner();
1416         if (tl_inset == inset) {
1417                 update(BufferView::UPDATE);
1418                 if (bv_->text->updateInset(inset)) {
1419                         update(BufferView::SELECT);
1420                         return;
1421                 }
1422         } else if (static_cast<UpdatableInset *>(tl_inset)
1423                            ->updateInsetInInset(bv_, inset))
1424         {
1425                 if (bv_->text->updateInset(tl_inset)) {
1426                         update();
1427                         updateScrollbar();
1428                 }
1429         }
1430 }