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