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