]> git.lyx.org Git - lyx.git/blob - src/BufferView_pimpl.C
a6db146074e208901149c6ce67e2e3ea341febb0
[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         LCursor & cur = bv_->cursor();
404         par = cur.par();
405         pos = cur.pos();
406         selstartpar = cur.selStart().par();
407         selstartpos = cur.selStart().pos();
408         selendpar = cur.selEnd().par();
409         selendpos = cur.selEnd().pos();
410         sel = cur.selection();
411         mark_set = cur.mark();
412         text->textwidth_ = bv_->workWidth();
413         text->fullRebreak();
414         update();
415
416         if (par != -1) {
417                 cur.selection() = true;
418                 // At this point just to avoid the Delete-Empty-Paragraph-
419                 // Mechanism when setting the cursor.
420                 cur.mark() = mark_set;
421                 if (sel) {
422                         text->setCursor(selstartpar, selstartpos);
423                         cur.resetAnchor();
424                         text->setCursor(selendpar, selendpos);
425                         cur.setSelection();
426                         text->setCursor(par, pos);
427                 } else {
428                         text->setCursor(par, pos);
429                         cur.resetAnchor();
430                         cur.selection() = false;
431                 }
432         }
433
434         fitCursor();
435
436         switchKeyMap();
437         owner_->busy(false);
438
439         // reset the "Formatting..." message
440         owner_->clearMessage();
441
442         updateScrollbar();
443 }
444
445
446 void BufferView::Pimpl::updateScrollbar()
447 {
448         if (!bv_->text()) {
449                 lyxerr[Debug::GUI] << "no text in updateScrollbar" << endl;
450                 workarea().setScrollbarParams(0, 0, 0);
451                 return;
452         }
453
454         LyXText const & t = *bv_->text();
455
456         lyxerr[Debug::GUI] << "Updating scrollbar: h " << t.height << ", top_y() "
457                 << top_y() << ", default height " << defaultRowHeight() << endl;
458
459         workarea().setScrollbarParams(t.height, top_y(), defaultRowHeight());
460 }
461
462
463 void BufferView::Pimpl::scrollDocView(int value)
464 {
465         lyxerr[Debug::GUI] << "scrollDocView of " << value << endl;
466
467         if (!buffer_)
468                 return;
469
470         screen().hideCursor();
471
472         top_y(value);
473         screen().redraw(*bv_);
474
475         if (!lyxrc.cursor_follows_scrollbar)
476                 return;
477
478         int const height = defaultRowHeight();
479         int const first = top_y() + height;
480         int const last = top_y() + workarea().workHeight() - height;
481
482         LyXText * text = bv_->text();
483         if (text->cursorY() < first)
484                 text->setCursorFromCoordinates(0, first);
485         else if (text->cursorY() > last)
486                 text->setCursorFromCoordinates(0, last);
487
488         owner_->updateLayoutChoice();
489 }
490
491
492 void BufferView::Pimpl::scroll(int lines)
493 {
494         if (!buffer_)
495                 return;
496
497         LyXText const * t = bv_->text();
498         int const line_height = defaultRowHeight();
499
500         // The new absolute coordinate
501         int new_top_y = top_y() + lines * line_height;
502
503         // Restrict to a valid value
504         new_top_y = std::min(t->height - 4 * line_height, new_top_y);
505         new_top_y = std::max(0, new_top_y);
506
507         scrollDocView(new_top_y);
508
509         // Update the scrollbar.
510         workarea().setScrollbarParams(t->height, top_y(), defaultRowHeight());
511 }
512
513
514 void BufferView::Pimpl::workAreaKeyPress(LyXKeySymPtr key,
515                                          key_modifier::state state)
516 {
517         bv_->owner()->getLyXFunc().processKeySym(key, state);
518
519         /* This is perhaps a bit of a hack. When we move
520          * around, or type, it's nice to be able to see
521          * the cursor immediately after the keypress. So
522          * we reset the toggle timeout and force the visibility
523          * of the cursor. Note we cannot do this inside
524          * dispatch() itself, because that's called recursively.
525          */
526         if (available()) {
527                 cursor_timeout.restart();
528                 screen().showCursor(*bv_);
529         }
530 }
531
532
533 void BufferView::Pimpl::selectionRequested()
534 {
535         static string sel;
536
537         if (!available())
538                 return;
539
540         LCursor & cur = bv_->cursor();
541
542         if (!cur.selection()) {
543                 xsel_cache_.set = false;
544                 return;
545         }
546
547         if (!xsel_cache_.set ||
548             cur.cursor_.back() != xsel_cache_.cursor ||
549             cur.anchor_.back() != xsel_cache_.anchor)
550         {
551                 xsel_cache_.cursor = cur.cursor_.back();
552                 xsel_cache_.anchor = cur.anchor_.back();
553                 xsel_cache_.set = cur.selection();
554                 sel = bv_->getLyXText()->selectionAsString(*bv_->buffer(), false);
555                 if (!sel.empty())
556                         workarea().putClipboard(sel);
557         } 
558 }
559
560
561 void BufferView::Pimpl::selectionLost()
562 {
563         if (available()) {
564                 screen().hideCursor();
565                 bv_->cursor().clearSelection();
566                 xsel_cache_.set = false;
567         }
568 }
569
570
571 void BufferView::Pimpl::workAreaResize()
572 {
573         static int work_area_width;
574         static int work_area_height;
575
576         bool const widthChange = workarea().workWidth() != work_area_width;
577         bool const heightChange = workarea().workHeight() != work_area_height;
578
579         // update from work area
580         work_area_width = workarea().workWidth();
581         work_area_height = workarea().workHeight();
582
583         if (buffer_ != 0) {
584                 if (widthChange) {
585                         // The visible LyXView need a resize
586                         resizeCurrentBuffer();
587                 }
588         }
589
590         if (widthChange || heightChange)
591                 update();
592
593         // always make sure that the scrollbar is sane.
594         updateScrollbar();
595         owner_->updateLayoutChoice();
596 }
597
598
599 void BufferView::Pimpl::update()
600 {
601         //lyxerr << "BufferView::update()" << endl;
602         // fix cursor coordinate cache in case something went wrong
603
604         // check needed to survive LyX startup
605         if (bv_->getLyXText()) {
606                 // update all 'visible' paragraphs
607                 ParagraphList::iterator beg;
608                 ParagraphList::iterator end;
609                 getParsInRange(buffer_->paragraphs(),
610                                top_y(), top_y() + workarea().workHeight(),
611                                beg, end);
612                 bv_->text()->redoParagraphs(beg, end);
613                 updateScrollbar();
614         }
615         screen().redraw(*bv_);
616 }
617
618
619 // Callback for cursor timer
620 void BufferView::Pimpl::cursorToggle()
621 {
622         if (!buffer_) {
623                 cursor_timeout.restart();
624                 return;
625         }
626
627         screen().toggleCursor(*bv_);
628         cursor_timeout.restart();
629 }
630
631
632 bool BufferView::Pimpl::available() const
633 {
634         return buffer_ && bv_->text();
635 }
636
637
638 Change const BufferView::Pimpl::getCurrentChange()
639 {
640         if (!bv_->buffer()->params().tracking_changes)
641                 return Change(Change::UNCHANGED);
642
643         LyXText * text = bv_->getLyXText();
644         LCursor & cur = bv_->cursor();
645
646         if (!cur.selection())
647                 return Change(Change::UNCHANGED);
648
649         return text->getPar(cur.selStart())
650                 ->lookupChangeFull(cur.selStart().pos());
651 }
652
653
654 void BufferView::Pimpl::savePosition(unsigned int i)
655 {
656         if (i >= saved_positions_num)
657                 return;
658         saved_positions[i] = Position(buffer_->fileName(),
659                                       bv_->text()->cursorPar()->id(),
660                                       bv_->text()->cursor().pos());
661         if (i > 0)
662                 owner_->message(bformat(_("Saved bookmark %1$s"), tostr(i)));
663 }
664
665
666 void BufferView::Pimpl::restorePosition(unsigned int i)
667 {
668         if (i >= saved_positions_num)
669                 return;
670
671         string const fname = saved_positions[i].filename;
672
673         bv_->cursor().clearSelection();
674
675         if (fname != buffer_->fileName()) {
676                 Buffer * b = 0;
677                 if (bufferlist.exists(fname))
678                         b = bufferlist.getBuffer(fname);
679                 else {
680                         b = bufferlist.newBuffer(fname);
681                         ::loadLyXFile(b, fname); // don't ask, just load it
682                 }
683                 if (b)
684                         buffer(b);
685         }
686
687         ParIterator par = buffer_->getParFromID(saved_positions[i].par_id);
688         if (par == buffer_->par_iterator_end())
689                 return;
690
691         bv_->text()->setCursor(par.pit(),
692                              min(par->size(), saved_positions[i].par_pos));
693
694         if (i > 0)
695                 owner_->message(bformat(_("Moved to bookmark %1$s"), tostr(i)));
696 }
697
698
699 bool BufferView::Pimpl::isSavedPosition(unsigned int i)
700 {
701         return i < saved_positions_num && !saved_positions[i].filename.empty();
702 }
703
704
705 void BufferView::Pimpl::switchKeyMap()
706 {
707         if (!lyxrc.rtl_support)
708                 return;
709
710         Intl & intl = owner_->getIntl();
711         if (bv_->getLyXText()->real_current_font.isRightToLeft()) {
712                 if (intl.keymap == Intl::PRIMARY)
713                         intl.KeyMapSec();
714         } else {
715                 if (intl.keymap == Intl::SECONDARY)
716                         intl.KeyMapPrim();
717         }
718 }
719
720
721 void BufferView::Pimpl::center()
722 {
723         LyXText * text = bv_->text();
724
725         bv_->cursor().clearSelection();
726         int const half_height = workarea().workHeight() / 2;
727         int new_y = std::max(0, text->cursorY() - half_height);
728
729         // FIXME: look at this comment again ...
730         // This updates top_y() but means the fitCursor() call
731         // from the update(FITCUR) doesn't realise that we might
732         // have moved (e.g. from GOTOPARAGRAPH), so doesn't cause
733         // the scrollbar to be updated as it should, so we have
734         // to do it manually. Any operation that does a center()
735         // and also might have moved top_y() must make sure to call
736         // updateScrollbar() currently. Never mind that this is a
737         // pretty obfuscated way of updating t->top_y()
738         top_y(new_y);
739 }
740
741
742 void BufferView::Pimpl::stuffClipboard(string const & stuff) const
743 {
744         workarea().putClipboard(stuff);
745 }
746
747
748 InsetOld * BufferView::Pimpl::getInsetByCode(InsetOld::Code code)
749 {
750 #if 0
751         CursorSlice cursor = bv_->getLyXText()->cursor;
752         Buffer::inset_iterator it =
753                 find_if(Buffer::inset_iterator(
754                         cursorPar(), cursor().pos()),
755                         buffer_->inset_iterator_end(),
756                         lyx::compare_memfun(&Inset::lyxCode, code));
757         return it != buffer_->inset_iterator_end() ? (*it) : 0;
758 #else
759         // Ok, this is a little bit too brute force but it
760         // should work for now. Better infrastructure is coming. (Lgb)
761
762         Buffer * b = bv_->buffer();
763         LyXText * text = bv_->getLyXText();
764
765         Buffer::inset_iterator beg = b->inset_iterator_begin();
766         Buffer::inset_iterator end = b->inset_iterator_end();
767
768         bool cursor_par_seen = false;
769
770         for (; beg != end; ++beg) {
771                 if (beg.getPar() == text->cursorPar()) {
772                         cursor_par_seen = true;
773                 }
774                 if (cursor_par_seen) {
775                         if (beg.getPar() == text->cursorPar()
776                             && beg.getPos() >= text->cursor().pos()) {
777                                 break;
778                         } else if (beg.getPar() != text->cursorPar()) {
779                                 break;
780                         }
781                 }
782
783         }
784         if (beg != end) {
785                 // Now find the first inset that matches code.
786                 for (; beg != end; ++beg) {
787                         if (beg->lyxCode() == code) {
788                                 return &(*beg);
789                         }
790                 }
791         }
792         return 0;
793 #endif
794 }
795
796
797 void BufferView::Pimpl::MenuInsertLyXFile(string const & filen)
798 {
799         string filename = filen;
800
801         if (filename.empty()) {
802                 // Launch a file browser
803                 string initpath = lyxrc.document_path;
804
805                 if (available()) {
806                         string const trypath = owner_->buffer()->filePath();
807                         // If directory is writeable, use this as default.
808                         if (IsDirWriteable(trypath))
809                                 initpath = trypath;
810                 }
811
812                 FileDialog fileDlg(_("Select LyX document to insert"),
813                         LFUN_FILE_INSERT,
814                         make_pair(string(_("Documents|#o#O")),
815                                   string(lyxrc.document_path)),
816                         make_pair(string(_("Examples|#E#e")),
817                                   string(AddPath(system_lyxdir(), "examples"))));
818
819                 FileDialog::Result result =
820                         fileDlg.open(initpath,
821                                      FileFilterList(_("LyX Documents (*.lyx)")),
822                                      string());
823
824                 if (result.first == FileDialog::Later)
825                         return;
826
827                 filename = result.second;
828
829                 // check selected filename
830                 if (filename.empty()) {
831                         owner_->message(_("Canceled."));
832                         return;
833                 }
834         }
835
836         // get absolute path of file and add ".lyx" to the filename if
837         // necessary
838         filename = FileSearch(string(), filename, "lyx");
839
840         string const disp_fn = MakeDisplayPath(filename);
841         owner_->message(bformat(_("Inserting document %1$s..."), disp_fn));
842         if (bv_->insertLyXFile(filename))
843                 owner_->message(bformat(_("Document %1$s inserted."),
844                                         disp_fn));
845         else
846                 owner_->message(bformat(_("Could not insert document %1$s"),
847                                         disp_fn));
848 }
849
850
851 void BufferView::Pimpl::trackChanges()
852 {
853         Buffer * buf(bv_->buffer());
854         bool const tracking(buf->params().tracking_changes);
855
856         if (!tracking) {
857                 ParIterator const end = buf->par_iterator_end();
858                 for (ParIterator it = buf->par_iterator_begin(); it != end; ++it)
859                         it->trackChanges();
860                 buf->params().tracking_changes = true;
861
862                 // we cannot allow undos beyond the freeze point
863                 buf->undostack().clear();
864         } else {
865                 update();
866                 bv_->text()->setCursor(0, 0);
867 #warning changes FIXME
868                 bool found = lyx::find::findNextChange(bv_);
869                 if (found) {
870                         owner_->getDialogs().show("changes");
871                         return;
872                 }
873
874                 ParIterator const end = buf->par_iterator_end();
875                 for (ParIterator it = buf->par_iterator_begin(); it != end; ++it)
876                         it->untrackChanges();
877                 buf->params().tracking_changes = false;
878         }
879
880         buf->redostack().clear();
881 }
882
883 #warning remove me
884 CursorBase theTempCursor;
885
886 namespace {
887
888         InsetOld * insetFromCoords(BufferView * bv, int x, int y)
889         {
890                 lyxerr << "insetFromCoords" << endl;
891                 LyXText * text = bv->text();
892                 InsetOld * inset = 0;
893                 theTempCursor.clear();
894                 while (true) {
895                         InsetOld * const inset_hit = text->checkInsetHit(x, y);
896                         if (!inset_hit) {
897                                 lyxerr << "no further inset hit" << endl;
898                                 break;
899                         }
900                         inset = inset_hit;
901                         if (!inset->descendable()) {
902                                 lyxerr << "not descendable" << endl;
903                                 break;
904                         }
905                         int const cell = inset->getCell(x, y + bv->top_y());
906                         if (cell == -1)
907                                 break;
908                         text = inset_hit->getText(cell);
909                         lyxerr << "Hit inset: " << inset << " at x: " << x
910                                 << " text: " << text << " y: " << y << endl;
911                         theTempCursor.push_back(CursorSlice(inset));
912                 }
913                 //lyxerr << "theTempCursor: " << theTempCursor << endl;
914                 return inset;
915         }
916
917 }
918
919
920 bool BufferView::Pimpl::workAreaDispatch(FuncRequest const & cmd)
921 {
922         LCursor & cur = bv_->cursor();
923         switch (cmd.action) {
924         case LFUN_MOUSE_MOTION: {
925                 if (!available())
926                         return false;
927                 FuncRequest cmd1 = cmd;
928                 InsetBase * inset = cur.inset();
929                 DispatchResult res;
930                 if (inset) {
931                         cmd1.x -= inset->x();
932                         cmd1.y -= inset->y();
933                         res = inset->dispatch(cur, cmd1);
934                 } else {
935                         cmd1.y += bv_->top_y();
936                         res = cur.innerText()->dispatch(cur, cmd1);
937                 }
938
939                 if (bv_->fitCursor() || res.update()) {
940                         bv_->update();
941                         cur.updatePos();
942                 }
943
944                 return true;
945         }
946
947         case LFUN_MOUSE_PRESS:
948         case LFUN_MOUSE_RELEASE:
949         case LFUN_MOUSE_DOUBLE:
950         case LFUN_MOUSE_TRIPLE: {
951                 // We pass those directly to the Bufferview, since
952                 // otherwise selection handling breaks down
953
954                 // Doesn't go through lyxfunc, so we need to update
955                 // the layout choice etc. ourselves
956
957                 // e.g. Qt mouse press when no buffer
958                 if (!available())
959                         return false;
960
961                 screen().hideCursor();
962
963                 // either the inset under the cursor or the surrounding LyXText will
964                 // handle this event.
965
966                 // built temporary path to inset
967                 InsetOld * inset = insetFromCoords(bv_, cmd.x, cmd.y);
968                 DispatchResult res;
969
970                 // try to dispatch to that inset
971                 if (inset) {
972                         FuncRequest cmd2 = cmd;
973                         lyxerr << "dispatching action " << cmd2.action
974                                << " to inset " << inset << endl;
975                         cmd2.x -= inset->x();
976                         cmd2.y -= inset->y();
977                         res = inset->dispatch(cur, cmd2);
978                         if (res.update()) {
979                                 bv_->update();
980                                 cur.updatePos();
981                         }
982                         res.update(false);
983                         switch (res.val()) {
984                                 case FINISHED:
985                                 case FINISHED_RIGHT:
986                                 case FINISHED_UP:
987                                 case FINISHED_DOWN:
988                                         theTempCursor.pop_back();
989                                         cur.cursor_ = theTempCursor;
990                                         cur.innerText()
991                                                 ->setCursorFromCoordinates(cmd.x, top_y() + cmd.y);
992                                         if (bv_->fitCursor())
993                                                 bv_->update();
994                                         return true;
995                                 default:
996                                         lyxerr << "not dispatched by inner inset val: " << res.val() << endl;
997                                         break;
998                         }
999                 }
1000
1001                 // otherwise set cursor to surrounding LyXText
1002                 if (!res.dispatched()) {
1003                         //lyxerr << "temp cursor is: " << theTempCursor << endl;
1004                         //lyxerr << "dispatching " << cmd
1005                          //      << " to surrounding LyXText "
1006                           //     << theTempCursor.innerText() << endl;
1007                         cur.cursor_ = theTempCursor;
1008                         FuncRequest cmd1 = cmd;
1009                         cmd1.y += bv_->top_y();
1010                         res = cur.innerText()->dispatch(cur, cmd1);
1011                         if (bv_->fitCursor() || res.update())
1012                                 bv_->update();
1013
1014                         //return DispatchResult(true, true);
1015                 }
1016                 // see workAreaKeyPress
1017                 cursor_timeout.restart();
1018                 screen().showCursor(*bv_);
1019
1020                 // skip these when selecting
1021                 if (cmd.action != LFUN_MOUSE_MOTION) {
1022                         owner_->updateLayoutChoice();
1023                         owner_->updateToolbar();
1024                 }
1025
1026                 // slight hack: this is only called currently when we
1027                 // clicked somewhere, so we force through the display
1028                 // of the new status here.
1029                 owner_->clearMessage();
1030                 return true;
1031         }
1032
1033         default:
1034                 owner_->dispatch(cmd);
1035                 return true;
1036         }
1037 }
1038
1039
1040 bool BufferView::Pimpl::dispatch(FuncRequest const & ev)
1041 {
1042         // Make sure that the cached BufferView is correct.
1043         lyxerr[Debug::ACTION] << "BufferView::Pimpl::Dispatch:"
1044                 << " action[" << ev.action << ']'
1045                 << " arg[" << ev.argument << ']'
1046                 << " x[" << ev.x << ']'
1047                 << " y[" << ev.y << ']'
1048                 << " button[" << ev.button() << ']'
1049                 << endl;
1050
1051         LyXTextClass const & tclass = buffer_->params().getLyXTextClass();
1052         LCursor & cur = bv_->cursor();
1053
1054         switch (ev.action) {
1055
1056         case LFUN_SCROLL_INSET:
1057                 // this is not handled here as this function is only active
1058                 // if we have a locking_inset and that one is (or contains)
1059                 // a tabular-inset
1060                 break;
1061
1062         case LFUN_FILE_INSERT:
1063                 MenuInsertLyXFile(ev.argument);
1064                 break;
1065
1066         case LFUN_FILE_INSERT_ASCII_PARA:
1067                 InsertAsciiFile(bv_, ev.argument, true);
1068                 break;
1069
1070         case LFUN_FILE_INSERT_ASCII:
1071                 InsertAsciiFile(bv_, ev.argument, false);
1072                 break;
1073
1074         case LFUN_FONT_STATE:
1075                 owner_->getLyXFunc().setMessage(currentState(bv_));
1076                 break;
1077
1078         case LFUN_INSERT_LABEL: {
1079                 // Try and generate a valid label
1080                 string const contents = ev.argument.empty() ?
1081                         getPossibleLabel(*bv_) : ev.argument;
1082                 InsetCommandParams icp("label", contents);
1083                 string data = InsetCommandMailer::params2string("label", icp);
1084                 owner_->getDialogs().show("label", data, 0);
1085                 break;
1086         }
1087
1088         case LFUN_BOOKMARK_SAVE:
1089                 savePosition(strToUnsignedInt(ev.argument));
1090                 break;
1091
1092         case LFUN_BOOKMARK_GOTO:
1093                 restorePosition(strToUnsignedInt(ev.argument));
1094                 break;
1095
1096         case LFUN_REF_GOTO: {
1097                 string label = ev.argument;
1098                 if (label.empty()) {
1099                         InsetRef * inset =
1100                                 static_cast<InsetRef*>(getInsetByCode(InsetOld::REF_CODE));
1101                         if (inset) {
1102                                 label = inset->getContents();
1103                                 savePosition(0);
1104                         }
1105                 }
1106
1107                 if (!label.empty())
1108                         bv_->gotoLabel(label);
1109         }
1110         break;
1111
1112         // --- accented characters ---------------------------
1113
1114         case LFUN_UMLAUT:
1115         case LFUN_CIRCUMFLEX:
1116         case LFUN_GRAVE:
1117         case LFUN_ACUTE:
1118         case LFUN_TILDE:
1119         case LFUN_CEDILLA:
1120         case LFUN_MACRON:
1121         case LFUN_DOT:
1122         case LFUN_UNDERDOT:
1123         case LFUN_UNDERBAR:
1124         case LFUN_CARON:
1125         case LFUN_SPECIAL_CARON:
1126         case LFUN_BREVE:
1127         case LFUN_TIE:
1128         case LFUN_HUNG_UMLAUT:
1129         case LFUN_CIRCLE:
1130         case LFUN_OGONEK:
1131                 if (ev.argument.empty()) {
1132                         // As always...
1133                         owner_->getLyXFunc().handleKeyFunc(ev.action);
1134                 } else {
1135                         owner_->getLyXFunc().handleKeyFunc(ev.action);
1136                         owner_->getIntl().getTransManager()
1137                                 .TranslateAndInsert(ev.argument[0], bv_->getLyXText());
1138                         update();
1139                 }
1140                 break;
1141
1142         case LFUN_MATH_MACRO:
1143         case LFUN_MATH_DELIM:
1144         case LFUN_INSERT_MATRIX:
1145         case LFUN_INSERT_MATH:
1146         case LFUN_MATH_IMPORT_SELECTION: // Imports LaTeX from the X selection
1147         case LFUN_MATH_DISPLAY:          // Open or create a displayed math inset
1148         case LFUN_MATH_MODE:             // Open or create an inlined math inset
1149                 mathDispatch(cur, ev);
1150                 break;
1151
1152         case LFUN_INSET_INSERT: {
1153                 // Same as above.
1154                 BOOST_ASSERT(false);
1155                 InsetOld * inset = createInset(bv_, ev);
1156                 if (!inset || !insertInset(inset))
1157                         delete inset;
1158                 break;
1159         }
1160
1161         case LFUN_FLOAT_LIST:
1162                 if (tclass.floats().typeExist(ev.argument)) {
1163                         InsetOld * inset = new InsetFloatList(ev.argument);
1164                         if (!insertInset(inset, tclass.defaultLayoutName()))
1165                                 delete inset;
1166                 } else {
1167                         lyxerr << "Non-existent float type: "
1168                                << ev.argument << endl;
1169                 }
1170                 break;
1171
1172         case LFUN_LAYOUT_PARAGRAPH: {
1173                 string data;
1174                 params2string(*bv_->getLyXText()->cursorPar(), data);
1175                 data = "show\n" + data;
1176                 bv_->owner()->getDialogs().show("paragraph", data);
1177                 break;
1178         }
1179
1180         case LFUN_PARAGRAPH_UPDATE:
1181                 updateParagraphDialog();
1182                 break;
1183
1184         case LFUN_PARAGRAPH_APPLY:
1185                 setParagraphParams(*bv_, ev.argument);
1186                 break;
1187
1188         case LFUN_THESAURUS_ENTRY: {
1189                 string arg = ev.argument;
1190
1191                 if (arg.empty()) {
1192                         arg = bv_->getLyXText()->selectionAsString(*buffer_,
1193                                                                    false);
1194
1195                         // FIXME
1196                         if (arg.size() > 100 || arg.empty()) {
1197                                 // Get word or selection
1198                                 bv_->getLyXText()->selectWordWhenUnderCursor(lyx::WHOLE_WORD);
1199                                 arg = bv_->getLyXText()->selectionAsString(*buffer_, false);
1200                                 // FIXME: where is getLyXText()->unselect(bv_) ?
1201                         }
1202                 }
1203
1204                 bv_->owner()->getDialogs().show("thesaurus", arg);
1205                 break;
1206         }
1207
1208         case LFUN_TRACK_CHANGES:
1209                 trackChanges();
1210                 break;
1211
1212         case LFUN_MERGE_CHANGES:
1213                 owner_->getDialogs().show("changes");
1214                 break;
1215
1216         case LFUN_ACCEPT_ALL_CHANGES: {
1217                 bv_->text()->setCursor(0, 0);
1218 #warning FIXME changes
1219                 while (lyx::find::findNextChange(bv_))
1220                         bv_->getLyXText()->acceptChange();
1221                 update();
1222                 break;
1223         }
1224
1225         case LFUN_REJECT_ALL_CHANGES: {
1226                 bv_->text()->setCursor(0, 0);
1227 #warning FIXME changes
1228                 while (lyx::find::findNextChange(bv_))
1229                         bv_->getLyXText()->rejectChange();
1230                 update();
1231                 break;
1232         }
1233
1234         case LFUN_ACCEPT_CHANGE: {
1235                 bv_->getLyXText()->acceptChange();
1236                 update();
1237                 break;
1238         }
1239
1240         case LFUN_REJECT_CHANGE: {
1241                 bv_->getLyXText()->rejectChange();
1242                 update();
1243                 break;
1244         }
1245
1246         case LFUN_WORD_FIND:
1247                 lyx::find::find(bv_, ev);
1248                 break;
1249
1250         case LFUN_WORD_REPLACE:
1251                 lyx::find::replace(bv_, ev);
1252                 break;
1253
1254         case LFUN_MARK_OFF:
1255                 cur.clearSelection();
1256                 bv_->update();
1257                 cur.resetAnchor();
1258                 ev.message(N_("Mark off"));
1259                 break;
1260
1261         case LFUN_MARK_ON:
1262                 cur.clearSelection();
1263                 cur.mark() = true;
1264                 bv_->update();
1265                 cur.resetAnchor();
1266                 ev.message(N_("Mark on"));
1267                 break;
1268
1269         case LFUN_SETMARK:
1270                 cur.clearSelection();
1271                 if (cur.mark()) {
1272                         ev.message(N_("Mark removed"));
1273                 } else {
1274                         cur.mark() = true;
1275                         ev.message(N_("Mark set"));
1276                 }
1277                 cur.resetAnchor();
1278                 bv_->update();
1279                 break;
1280
1281         case LFUN_UNKNOWN_ACTION:
1282                 ev.errorMessage(N_("Unknown function!"));
1283                 break;
1284
1285         default:
1286                 return cur.dispatch(ev).dispatched();
1287         } // end of switch
1288
1289         return true;
1290 }
1291
1292
1293 bool BufferView::Pimpl::insertInset(InsetOld * inset, string const & lout)
1294 {
1295         // not quite sure if we want this...
1296         bv_->text()->recUndo(bv_->text()->cursor().par());
1297         freezeUndo();
1298
1299         bv_->cursor().clearSelection();
1300         if (!lout.empty()) {
1301                 bv_->text()->breakParagraph(bv_->buffer()->paragraphs());
1302
1303                 if (!bv_->text()->cursorPar()->empty()) {
1304                         bv_->text()->cursorLeft(bv_);
1305                         bv_->text()->breakParagraph(bv_->buffer()->paragraphs());
1306                 }
1307
1308                 string lres = lout;
1309                 LyXTextClass const & tclass = buffer_->params().getLyXTextClass();
1310                 bool hasLayout = tclass.hasLayout(lres);
1311
1312                 bv_->text()->setLayout(hasLayout ? lres : tclass.defaultLayoutName());
1313                 bv_->text()->setParagraph(Spacing(), LYX_ALIGN_LAYOUT, string(), 0);
1314         }
1315         bv_->cursor().innerText()->insertInset(inset);
1316         unFreezeUndo();
1317         return true;
1318 }
1319
1320
1321 bool BufferView::Pimpl::ChangeInsets(InsetOld::Code code,
1322                                      string const & from, string const & to)
1323 {
1324         bool need_update = false;
1325         CursorSlice cur = bv_->text()->cursor();
1326
1327         ParIterator end = bv_->buffer()->par_iterator_end();
1328         for (ParIterator it = bv_->buffer()->par_iterator_begin();
1329              it != end; ++it) {
1330                 bool changed_inset = false;
1331                 for (InsetList::iterator it2 = it->insetlist.begin();
1332                      it2 != it->insetlist.end(); ++it2) {
1333                         if (it2->inset->lyxCode() == code) {
1334                                 InsetCommand * inset = static_cast<InsetCommand *>(it2->inset);
1335                                 if (inset->getContents() == from) {
1336                                         inset->setContents(to);
1337                                         changed_inset = true;
1338                                 }
1339                         }
1340                 }
1341                 if (changed_inset) {
1342                         need_update = true;
1343
1344                         // FIXME
1345
1346                         // The test it.size() == 1 was needed to prevent crashes.
1347                         // How to set the cursor correctly when it.size() > 1 ??
1348                         if (it.size() == 1) {
1349                                 bv_->text()->setCursorIntern(bv_->text()->parOffset(it.pit()), 0);
1350                                 bv_->text()->redoParagraph(bv_->text()->cursorPar());
1351                         }
1352                 }
1353         }
1354         bv_->text()->setCursorIntern(cur.par(), cur.pos());
1355         return need_update;
1356 }
1357
1358
1359 void BufferView::Pimpl::updateParagraphDialog()
1360 {
1361         if (!bv_->owner()->getDialogs().visible("paragraph"))
1362                 return;
1363         Paragraph const & par = *bv_->getLyXText()->cursorPar();
1364         string data;
1365         params2string(par, data);
1366
1367         // Will the paragraph accept changes from the dialog?
1368         InsetOld * const inset = par.inInset();
1369         bool const accept =
1370                 !(inset && inset->forceDefaultParagraphs(inset));
1371
1372         data = "update " + tostr(accept) + '\n' + data;
1373         bv_->owner()->getDialogs().update("paragraph", data);
1374 }