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