]> git.lyx.org Git - lyx.git/blob - src/BufferView_pimpl.C
6e1f60e2ef41bfa76c8921c3034d5b92c9b0a156
[lyx.git] / src / BufferView_pimpl.C
1 /**
2  * \file BufferView_pimpl.C
3  * Copyright 2002 the LyX Team
4  * Read the file COPYING
5  *
6  * \author various
7  */
8
9 #include <config.h>
10
11 #ifdef __GNUG__
12 #pragma implementation
13 #endif
14
15 #include "BufferView_pimpl.h"
16 #include "frontends/WorkArea.h"
17 #include "frontends/screen.h"
18 #include "frontends/LyXScreenFactory.h"
19 #include "frontends/WorkAreaFactory.h"
20 #include "frontends/Dialogs.h"
21 #include "frontends/Alert.h"
22 #include "frontends/FileDialog.h"
23 #include "lyxtext.h"
24 #include "lyxrow.h"
25 #include "paragraph.h"
26 #include "frontends/LyXView.h"
27 #include "commandtags.h"
28 #include "lyxfunc.h"
29 #include "debug.h"
30 #include "bufferview_funcs.h"
31 #include "TextCache.h"
32 #include "bufferlist.h"
33 #include "lyxrc.h"
34 #include "intl.h"
35 // added for Dispatch functions
36 #include "lyx_cb.h"
37 #include "lyx_main.h"
38 #include "FloatList.h"
39 #include "gettext.h"
40 #include "ParagraphParameters.h"
41 #include "undo_funcs.h"
42 #include "funcrequest.h"
43 #include "factory.h"
44
45 #include "insets/insetbib.h"
46 #include "insets/insettext.h"
47 #include "insets/inseturl.h"
48 #include "insets/insetlatexaccent.h"
49 #include "insets/insettoc.h"
50 #include "insets/insetref.h"
51 #include "insets/insetparent.h"
52 #include "insets/insetindex.h"
53 #include "insets/insetinclude.h"
54 #include "insets/insetcite.h"
55 #include "insets/insetgraphics.h"
56 #include "insets/insetmarginal.h"
57 #include "insets/insettabular.h"
58 #include "insets/insetcaption.h"
59 #include "insets/insetfloatlist.h"
60
61 #include "mathed/formulabase.h"
62
63 #include "graphics/Previews.h"
64
65 #include "support/LAssert.h"
66 #include "support/lstrings.h"
67 #include "support/filetools.h"
68
69 #include <boost/bind.hpp>
70 #include <boost/signals/connection.hpp>
71
72 #include <cstdio>
73 #include <unistd.h>
74 #include <sys/wait.h>
75
76
77 using std::vector;
78 using std::find_if;
79 using std::find;
80 using std::pair;
81 using std::endl;
82 using std::make_pair;
83 using std::min;
84
85 using lyx::pos_type;
86
87 /* the selection possible is needed, that only motion events are
88  * used, where the bottom press event was on the drawing area too */
89 bool selection_possible = false;
90
91 extern BufferList bufferlist;
92 extern int bibitemMaxWidth(BufferView *, LyXFont const &);
93
94
95 namespace {
96
97 unsigned int const saved_positions_num = 20;
98
99 // All the below connection objects are needed because of a bug in some
100 // versions of GCC (<=2.96 are on the suspects list.) By having and assigning
101 // to these connections we avoid a segfault upon startup, and also at exit.
102 // (Lgb)
103
104 boost::signals::connection timecon;
105 boost::signals::connection doccon;
106 boost::signals::connection resizecon;
107 boost::signals::connection bpresscon;
108 boost::signals::connection breleasecon;
109 boost::signals::connection motioncon;
110 boost::signals::connection doublecon;
111 boost::signals::connection triplecon;
112 boost::signals::connection kpresscon;
113 boost::signals::connection selectioncon;
114 boost::signals::connection lostcon;
115
116 } // anon namespace
117
118
119 BufferView::Pimpl::Pimpl(BufferView * bv, LyXView * owner,
120              int xpos, int ypos, int width, int height)
121         : bv_(bv), owner_(owner), buffer_(0), cursor_timeout(400),
122           using_xterm_cursor(false)
123 {
124         workarea_.reset(WorkAreaFactory::create(xpos, ypos, width, height));
125         screen_.reset(LyXScreenFactory::create(workarea()));
126
127         // Setup the signals
128         doccon = workarea().scrollDocView
129                 .connect(boost::bind(&BufferView::Pimpl::scrollDocView, this, _1));
130         resizecon = workarea().workAreaResize
131                 .connect(boost::bind(&BufferView::Pimpl::workAreaResize, this));
132         bpresscon = workarea().workAreaButtonPress
133                 .connect(boost::bind(&BufferView::Pimpl::workAreaButtonPress, this, _1, _2, _3));
134         breleasecon = workarea().workAreaButtonRelease
135                 .connect(boost::bind(&BufferView::Pimpl::workAreaButtonRelease, this, _1, _2, _3));
136         motioncon = workarea().workAreaMotionNotify
137                 .connect(boost::bind(&BufferView::Pimpl::workAreaMotionNotify, this, _1, _2, _3));
138         doublecon = workarea().workAreaDoubleClick
139                 .connect(boost::bind(&BufferView::Pimpl::doubleClick, this, _1, _2, _3));
140         triplecon = workarea().workAreaTripleClick
141                 .connect(boost::bind(&BufferView::Pimpl::tripleClick, this, _1, _2, _3));
142         kpresscon = workarea().workAreaKeyPress
143                 .connect(boost::bind(&BufferView::Pimpl::workAreaKeyPress, this, _1, _2));
144         selectioncon = workarea().selectionRequested
145                 .connect(boost::bind(&BufferView::Pimpl::selectionRequested, this));
146         lostcon = workarea().selectionLost
147                 .connect(boost::bind(&BufferView::Pimpl::selectionLost, this));
148
149         timecon = cursor_timeout.timeout
150                 .connect(boost::bind(&BufferView::Pimpl::cursorToggle, this));
151         cursor_timeout.start();
152         saved_positions.resize(saved_positions_num);
153 }
154
155
156 WorkArea & BufferView::Pimpl::workarea() const
157 {
158         return *workarea_.get();
159 }
160
161
162 LyXScreen & BufferView::Pimpl::screen() const
163 {
164         return *screen_.get();
165 }
166
167
168 Painter & BufferView::Pimpl::painter() const
169 {
170         return workarea().getPainter();
171 }
172
173
174 void BufferView::Pimpl::buffer(Buffer * b)
175 {
176         lyxerr[Debug::INFO] << "Setting buffer in BufferView ("
177                             << b << ")" << endl;
178         if (buffer_) {
179                 buffer_->delUser(bv_);
180
181                 // Put the old text into the TextCache, but
182                 // only if the buffer is still loaded.
183                 // Also set the owner of the test to 0
184                 //              bv_->text->owner(0);
185                 textcache.add(buffer_, workarea().workWidth(), bv_->text);
186                 if (lyxerr.debugging())
187                         textcache.show(lyxerr, "BufferView::buffer");
188
189                 bv_->text = 0;
190         }
191
192         // set current buffer
193         buffer_ = b;
194
195         if (bufferlist.getState() == BufferList::CLOSING) return;
196
197         // if we are closing the buffer, use the first buffer as current
198         if (!buffer_) {
199                 buffer_ = bufferlist.first();
200         }
201
202         if (buffer_) {
203                 lyxerr[Debug::INFO] << "Buffer addr: " << buffer_ << endl;
204                 buffer_->addUser(bv_);
205
206                 // If we don't have a text object for this, we make one
207                 if (bv_->text == 0) {
208                         resizeCurrentBuffer();
209                 }
210
211                 // FIXME: needed when ?
212                 bv_->text->first_y =
213                         screen().topCursorVisible(bv_->text->cursor, bv_->text->first_y);
214
215                 // Similarly, buffer-dependent dialogs should be updated or
216                 // hidden. This should go here because some dialogs (eg ToC)
217                 // require bv_->text.
218                 owner_->getDialogs().updateBufferDependent(true);
219         } else {
220                 lyxerr[Debug::INFO] << "  No Buffer!" << endl;
221                 owner_->getDialogs().hideBufferDependent();
222
223                 // Also remove all remaining text's from the testcache.
224                 // (there should not be any!) (if there is any it is a
225                 // bug!)
226                 if (lyxerr.debugging())
227                         textcache.show(lyxerr, "buffer delete all");
228                 textcache.clear();
229         }
230
231         repaint();
232         updateScrollbar();
233         owner_->updateMenubar();
234         owner_->updateToolbar();
235         owner_->updateLayoutChoice();
236         owner_->updateWindowTitle();
237
238         if (grfx::Previews::activated() && buffer_)
239                 grfx::Previews::get().generateBufferPreviews(*buffer_);
240 }
241
242
243 bool BufferView::Pimpl::fitCursor()
244 {
245         bool ret;
246
247         if (bv_->theLockingInset()) {
248                 bv_->theLockingInset()->fitInsetCursor(bv_);
249                 ret = true;
250         } else {
251                 ret = screen().fitCursor(bv_->text, bv_);
252         }
253
254         bv_->owner()->getDialogs().updateParagraph();
255         if (ret)
256                 updateScrollbar();
257         return ret;
258 }
259
260
261 void BufferView::Pimpl::redoCurrentBuffer()
262 {
263         lyxerr[Debug::INFO] << "BufferView::redoCurrentBuffer" << endl;
264         if (buffer_ && bv_->text) {
265                 resizeCurrentBuffer();
266                 updateScrollbar();
267                 owner_->updateLayoutChoice();
268                 repaint();
269         }
270 }
271
272
273 int BufferView::Pimpl::resizeCurrentBuffer()
274 {
275         lyxerr[Debug::INFO] << "resizeCurrentBuffer" << endl;
276
277         Paragraph * par = 0;
278         Paragraph * selstartpar = 0;
279         Paragraph * selendpar = 0;
280         UpdatableInset * the_locking_inset = 0;
281
282         pos_type pos = 0;
283         pos_type selstartpos = 0;
284         pos_type selendpos = 0;
285         bool selection = false;
286         bool mark_set  = false;
287
288         owner_->prohibitInput();
289
290         owner_->message(_("Formatting document..."));
291
292         if (bv_->text) {
293                 par = bv_->text->cursor.par();
294                 pos = bv_->text->cursor.pos();
295                 selstartpar = bv_->text->selection.start.par();
296                 selstartpos = bv_->text->selection.start.pos();
297                 selendpar = bv_->text->selection.end.par();
298                 selendpos = bv_->text->selection.end.pos();
299                 selection = bv_->text->selection.set();
300                 mark_set = bv_->text->selection.mark();
301                 the_locking_inset = bv_->theLockingInset();
302                 buffer_->resizeInsets(bv_);
303                 // I don't think the delete and new are necessary here we just could
304                 // call only init! (Jug 20020419)
305                 delete bv_->text;
306                 bv_->text = new LyXText(bv_);
307                 bv_->text->init(bv_);
308         } else {
309                 // See if we have a text in TextCache that fits
310                 // the new buffer_ with the correct width.
311                 bv_->text = textcache.findFit(buffer_, workarea().workWidth());
312                 if (bv_->text) {
313                         if (lyxerr.debugging()) {
314                                 lyxerr << "Found a LyXText that fits:\n";
315                                 textcache.show(lyxerr, make_pair(buffer_, make_pair(workarea().workWidth(), bv_->text)));
316                         }
317                         // Set the owner of the newly found text
318                         //      bv_->text->owner(bv_);
319                         if (lyxerr.debugging())
320                                 textcache.show(lyxerr, "resizeCurrentBuffer");
321                 } else {
322                         bv_->text = new LyXText(bv_);
323                         bv_->text->init(bv_);
324                         //buffer_->resizeInsets(bv_);
325                 }
326         }
327
328         if (par) {
329                 bv_->text->selection.set(true);
330                 // At this point just to avoid the Delete-Empty-Paragraph-
331                 // Mechanism when setting the cursor.
332                 bv_->text->selection.mark(mark_set);
333                 if (selection) {
334                         bv_->text->setCursor(bv_, selstartpar, selstartpos);
335                         bv_->text->selection.cursor = bv_->text->cursor;
336                         bv_->text->setCursor(bv_, selendpar, selendpos);
337                         bv_->text->setSelection(bv_);
338                         bv_->text->setCursor(bv_, par, pos);
339                 } else {
340                         bv_->text->setCursor(bv_, par, pos);
341                         bv_->text->selection.cursor = bv_->text->cursor;
342                         bv_->text->selection.set(false);
343                 }
344                 // remake the inset locking
345                 bv_->theLockingInset(the_locking_inset);
346         }
347
348         bv_->text->first_y = screen().topCursorVisible(bv_->text->cursor, bv_->text->first_y);
349
350         switchKeyMap();
351         owner_->allowInput();
352
353         updateScrollbar();
354
355         return 0;
356 }
357
358
359 void BufferView::Pimpl::repaint()
360 {
361         // Regenerate the screen.
362         screen().redraw(bv_->text, bv_);
363 }
364
365
366 void BufferView::Pimpl::updateScrollbar()
367 {
368         if (!bv_->text) {
369                 lyxerr[Debug::GUI] << "no text in updateScrollbar" << endl;
370                 workarea().setScrollbarParams(0, 0, 0);
371                 return;
372         }
373
374         LyXText const & t = *bv_->text;
375
376         lyxerr[Debug::GUI] << "Updating scrollbar: h " << t.height << ", first_y "
377                 << t.first_y << ", default height " << t.defaultHeight() << endl;
378
379         workarea().setScrollbarParams(t.height, t.first_y, t.defaultHeight());
380 }
381
382
383 void BufferView::Pimpl::scrollDocView(int value)
384 {
385         lyxerr[Debug::GUI] << "scrollDocView of " << value << endl;
386
387         if (!buffer_) return;
388
389         screen().draw(bv_->text, bv_, value);
390
391         if (!lyxrc.cursor_follows_scrollbar) {
392                 return;
393         }
394
395         LyXText * vbt = bv_->text;
396
397         int const height = vbt->defaultHeight();
398         int const first = static_cast<int>((bv_->text->first_y + height));
399         int const last = static_cast<int>((bv_->text->first_y + workarea().workHeight() - height));
400
401         if (vbt->cursor.y() < first)
402                 vbt->setCursorFromCoordinates(bv_, 0, first);
403         else if (vbt->cursor.y() > last)
404                 vbt->setCursorFromCoordinates(bv_, 0, last);
405 }
406
407
408 int BufferView::Pimpl::scroll(long time)
409 {
410         if (!buffer_)
411                 return 0;
412
413         LyXText const * t = bv_->text;
414
415         double const diff = t->defaultHeight()
416                 + double(time) * double(time) * 0.125;
417
418         scrollDocView(int(diff));
419         workarea().setScrollbarParams(t->height, t->first_y, t->defaultHeight());
420         return 0;
421 }
422
423
424 void BufferView::Pimpl::workAreaKeyPress(LyXKeySymPtr key,
425                                          key_modifier::state state)
426 {
427         bv_->owner()->getLyXFunc().processKeySym(key, state);
428 }
429
430
431 void BufferView::Pimpl::workAreaMotionNotify(int x, int y, mouse_button::state state)
432 {
433         // Only use motion with button 1
434         if (!(state & mouse_button::button1))
435                 return;
436
437         if (!buffer_)
438                 return;
439
440         // Check for inset locking
441         if (bv_->theLockingInset()) {
442                 LyXCursor cursor = bv_->text->cursor;
443                 LyXFont font = bv_->text->getFont(buffer_,
444                                                   cursor.par(), cursor.pos());
445                 int width = bv_->theLockingInset()->width(bv_, font);
446                 int inset_x = font.isVisibleRightToLeft()
447                         ? cursor.ix() - width : cursor.ix();
448                 int start_x = inset_x + bv_->theLockingInset()->scroll();
449
450                 FuncRequest cmd(bv_, LFUN_MOUSE_MOTION,
451                                   x - start_x, y - cursor.iy() + bv_->text->first_y, state);
452                 bv_->theLockingInset()->localDispatch(cmd);
453                 return;
454         }
455
456         // The test for not selection possible is needed, that only motion
457         // events are used, where the bottom press event was on
458         //  the drawing area too
459         if (!selection_possible)
460                 return;
461
462         screen().hideCursor();
463
464         Row * cursorrow = bv_->text->cursor.row();
465         bv_->text->setCursorFromCoordinates(bv_, x, y + bv_->text->first_y);
466 #if 0
467         // sorry for this but I have a strange error that the y value jumps at
468         // a certain point. This seems like an error in my xforms library or
469         // in some other local environment, but I would like to leave this here
470         // for the moment until I can remove this (Jug 20020418)
471         if (y_before < bv_->text->cursor.y())
472                 lyxerr << y_before << ":" << bv_->text->cursor.y() << endl;
473 #endif
474         // This is to allow jumping over large insets
475         if (cursorrow == bv_->text->cursor.row()) {
476                 if (y >= int(workarea().workHeight())) {
477                         bv_->text->cursorDown(bv_, false);
478                 } else if (y < 0) {
479                         bv_->text->cursorUp(bv_, false);
480                 }
481         }
482
483         if (!bv_->text->selection.set())
484                 update(bv_->text, BufferView::UPDATE); // Maybe an empty line was deleted
485
486         bv_->text->setSelection(bv_);
487         screen().toggleToggle(bv_->text, bv_);
488         fitCursor();
489         showCursor();
490 }
491
492
493 // Single-click on work area
494 void BufferView::Pimpl::workAreaButtonPress(int xpos, int ypos,
495                                             mouse_button::state button)
496 {
497         if (!buffer_)
498                 return;
499
500         // ok ok, this is a hack (for xforms)
501         if (button == mouse_button::button4) {
502                 scroll(-lyxrc.wheel_jump);
503                 // We shouldn't go further down as we really should only do the
504                 // scrolling and be done with this. Otherwise we may open some
505                 // dialogs (Jug 20020424).
506                 return;
507         } else if (button == mouse_button::button5) {
508                 scroll(lyxrc.wheel_jump);
509                 // We shouldn't go further down as we really should only do the
510                 // scrolling and be done with this. Otherwise we may open some
511                 // dialogs (Jug 20020424).
512                 return;
513         }
514
515         Inset * inset_hit = checkInsetHit(bv_->text, xpos, ypos);
516
517         // Middle button press pastes if we have a selection
518         // We do this here as if the selection was inside an inset
519         // it could get cleared on the unlocking of the inset so
520         // we have to check this first
521         bool paste_internally = false;
522         if (button == mouse_button::button2 && bv_->getLyXText()->selection.set()) {
523                 owner_->dispatch(FuncRequest(LFUN_COPY));
524                 paste_internally = true;
525         }
526
527         int const screen_first = bv_->text->first_y;
528
529         if (bv_->theLockingInset()) {
530                 // We are in inset locking mode
531
532                 // Check whether the inset was hit. If not reset mode,
533                 // otherwise give the event to the inset
534                 if (inset_hit == bv_->theLockingInset()) {
535                         FuncRequest cmd(bv_, LFUN_MOUSE_PRESS, xpos, ypos, button);
536                         bv_->theLockingInset()->localDispatch(cmd);
537                         return;
538                 }
539                 bv_->unlockInset(bv_->theLockingInset());
540         }
541
542         if (!inset_hit)
543                 selection_possible = true;
544         screen().hideCursor();
545
546         // Clear the selection
547         screen().toggleSelection(bv_->text, bv_);
548         bv_->text->clearSelection();
549         bv_->text->fullRebreak(bv_);
550         update();
551         updateScrollbar();
552
553         // Single left click in math inset?
554         if (isHighlyEditableInset(inset_hit)) {
555                 // Highly editable inset, like math
556                 UpdatableInset * inset = static_cast<UpdatableInset *>(inset_hit);
557                 selection_possible = false;
558                 owner_->updateLayoutChoice();
559                 owner_->message(inset->editMessage());
560                 //inset->edit(bv_, xpos, ypos, button);
561                 // We just have to lock the inset before calling a PressEvent on it!
562                 // we don't need the edit() call here! (Jug20020329)
563                 if (!bv_->lockInset(inset)) {
564                         lyxerr[Debug::INSETS] << "Cannot lock inset" << endl;
565                 }
566                 FuncRequest cmd(bv_, LFUN_MOUSE_PRESS, xpos, ypos, button);
567                 inset->localDispatch(cmd);
568                 return;
569         }
570         // I'm not sure we should continue here if we hit an inset (Jug20020403)
571
572         // Right click on a footnote flag opens float menu
573         if (button == mouse_button::button3) {
574                 selection_possible = false;
575                 return;
576         }
577
578         if (!inset_hit) // otherwise it was already set in checkInsetHit(...)
579                 bv_->text->setCursorFromCoordinates(bv_, xpos, ypos + screen_first);
580         finishUndo();
581         bv_->text->selection.cursor = bv_->text->cursor;
582         bv_->text->cursor.x_fix(bv_->text->cursor.x());
583
584         owner_->updateLayoutChoice();
585         if (fitCursor()) {
586                 selection_possible = false;
587         }
588
589         // Insert primary selection with middle mouse
590         // if there is a local selection in the current buffer,
591         // insert this
592         if (button == mouse_button::button2) {
593                 if (paste_internally)
594                         owner_->dispatch(FuncRequest(LFUN_PASTE));
595                 else
596                         owner_->dispatch(FuncRequest(LFUN_PASTESELECTION, "paragraph"));
597                 selection_possible = false;
598                 return;
599         }
600 }
601
602
603 void BufferView::Pimpl::doubleClick(int /*x*/, int /*y*/, mouse_button::state button)
604 {
605         if (!buffer_)
606                 return;
607
608         LyXText * text = bv_->getLyXText();
609
610         if (text->bv_owner && bv_->theLockingInset())
611                 return;
612
613         if (button == mouse_button::button1) {
614                 if (text->bv_owner) {
615                         screen().hideCursor();
616                         screen().toggleSelection(text, bv_);
617                         text->selectWord(bv_, LyXText::WHOLE_WORD_STRICT);
618                         screen().toggleSelection(text, bv_, false);
619                 } else {
620                         text->selectWord(bv_, LyXText::WHOLE_WORD_STRICT);
621                 }
622                 // This will fit the cursor on the screen if necessary
623                 update(text, BufferView::SELECT|BufferView::FITCUR);
624                 workarea().haveSelection(bv_->getLyXText()->selection.set());
625         }
626 }
627
628
629 void BufferView::Pimpl::tripleClick(int /*x*/, int /*y*/, mouse_button::state button)
630 {
631         if (!buffer_)
632                 return;
633
634         LyXText * text = bv_->getLyXText();
635
636         if (text->bv_owner && bv_->theLockingInset())
637             return;
638
639         if (button == mouse_button::button1) {
640                 if (text->bv_owner) {
641                         screen().hideCursor();
642                         screen().toggleSelection(text, bv_);
643                 }
644                 text->cursorHome(bv_);
645                 text->selection.cursor = text->cursor;
646                 text->cursorEnd(bv_);
647                 text->setSelection(bv_);
648                 if (text->bv_owner) {
649                         screen().toggleSelection(text, bv_, false);
650                 }
651                 // This will fit the cursor on the screen if necessary
652                 update(text, BufferView::SELECT|BufferView::FITCUR);
653                 workarea().haveSelection(bv_->getLyXText()->selection.set());
654         }
655 }
656
657
658 void BufferView::Pimpl::selectionRequested()
659 {
660         static string sel;
661
662         if (!available())
663                 return;
664
665         LyXText * text = bv_->getLyXText();
666
667         if (text->selection.set() &&
668                 (!bv_->text->xsel_cache.set() ||
669                  text->selection.start != bv_->text->xsel_cache.start ||
670                  text->selection.end != bv_->text->xsel_cache.end))
671         {
672                 bv_->text->xsel_cache = text->selection;
673                 sel = text->selectionAsString(bv_->buffer(), false);
674         } else if (!text->selection.set()) {
675                 sel = string();
676                 bv_->text->xsel_cache.set(false);
677         }
678         if (!sel.empty()) {
679                 workarea().putClipboard(sel);
680         }
681 }
682
683
684 void BufferView::Pimpl::selectionLost()
685 {
686         if (available()) {
687                 hideCursor();
688                 toggleSelection();
689                 bv_->getLyXText()->clearSelection();
690                 showCursor();
691                 bv_->text->xsel_cache.set(false);
692         }
693 }
694
695
696 void BufferView::Pimpl::workAreaButtonRelease(int x, int y,
697                                               mouse_button::state button)
698 {
699         // do nothing if we used the mouse wheel
700         if (!buffer_ || button == mouse_button::button4 || button == mouse_button::button5)
701                 return;
702
703         // If we hit an inset, we have the inset coordinates in these
704         // and inset_hit points to the inset.  If we do not hit an
705         // inset, inset_hit is 0, and inset_x == x, inset_y == y.
706         Inset * inset_hit = checkInsetHit(bv_->text, x, y);
707
708         if (bv_->theLockingInset()) {
709                 // We are in inset locking mode.
710
711                 // LyX does a kind of work-area grabbing for insets.
712                 // Only a ButtonPress FuncRequest outside the inset will
713                 // force a insetUnlock.
714                 FuncRequest cmd(bv_, LFUN_MOUSE_RELEASE, x, y, button);
715                 bv_->theLockingInset()->localDispatch(cmd);
716                 return;
717         }
718
719         selection_possible = false;
720
721         if (button == mouse_button::button2)
722                 return;
723
724         // finish selection
725         if (button == mouse_button::button1) {
726                 workarea().haveSelection(bv_->getLyXText()->selection.set());
727         }
728
729         switchKeyMap();
730         owner_->view_state_changed();
731         owner_->updateMenubar();
732         owner_->updateToolbar();
733
734         // Did we hit an editable inset?
735         if (inset_hit) {
736                 selection_possible = false;
737
738                 // if we reach this point with a selection, it
739                 // must mean we are currently selecting.
740                 // But we don't want to open the inset
741                 // because that is annoying for the user.
742                 // So just pretend we didn't hit it.
743                 // this is OK because a "kosher" ButtonRelease
744                 // will follow a ButtonPress that clears
745                 // the selection.
746                 // Note this also fixes selection drawing
747                 // problems if we end up opening an inset
748                 if (bv_->getLyXText()->selection.set())
749                         return;
750
751                 // CHECK fix this proper in 0.13
752                 // well, maybe 13.0 !!!!!!!!!
753
754                 // Following a ref shouldn't issue
755                 // a push on the undo-stack
756                 // anylonger, now that we have
757                 // keybindings for following
758                 // references and returning from
759                 // references.  IMHO though, it
760                 // should be the inset's own business
761                 // to push or not push on the undo
762                 // stack. They don't *have* to
763                 // alter the document...
764                 // (Joacim)
765                 // ...or maybe the SetCursorParUndo()
766                 // below isn't necessary at all anylonger?
767                 if (inset_hit->lyxCode() == Inset::REF_CODE) {
768                         setCursorParUndo(bv_);
769                 }
770
771                 owner_->message(inset_hit->editMessage());
772
773                 if (isHighlyEditableInset(inset_hit)) {
774                         // Highly editable inset, like math
775                         UpdatableInset *inset = (UpdatableInset *)inset_hit;
776                         FuncRequest cmd(bv_, LFUN_MOUSE_RELEASE, x, y, button);
777                         inset->localDispatch(cmd);
778                 } else {
779                         FuncRequest cmd(bv_, LFUN_MOUSE_RELEASE, x, y, button);
780                         inset_hit->localDispatch(cmd);
781                         // IMO this is a grosshack! Inset's should be changed so that
782                         // they call the actions they have to do with the insetButtonRel.
783                         // function and not in the edit(). This should be changed
784                         // (Jug 20020329)
785 #ifdef WITH_WARNINGS
786 #warning Please remove donot call inset->edit() here (Jug 20020812)
787 #endif
788                         inset_hit->edit(bv_, x, y, button);
789                 }
790                 return;
791         }
792
793         // Maybe we want to edit a bibitem ale970302
794         if (bv_->text->cursor.par()->bibkey) {
795                 bool const is_rtl = bv_->text->cursor.par()->isRightToLeftPar(buffer_->params);
796                 int const width = bibitemMaxWidth(bv_, buffer_->params.getLyXTextClass().defaultfont());
797                 if ((is_rtl && x > bv_->text->workWidth(bv_)-20-width) ||
798                     (!is_rtl && x < 20+width)) {
799                         bv_->text->cursor.par()->bibkey->edit(bv_, 0, 0, mouse_button::none);
800                 }
801         }
802
803         return;
804 }
805
806
807 Box BufferView::Pimpl::insetDimensions(LyXText const & text,
808                                        LyXCursor const & cursor) const
809 {
810         Paragraph /*const*/ & par = *cursor.par();
811         pos_type const pos = cursor.pos();
812
813         lyx::Assert(par.getInset(pos));
814
815         Inset const & inset(*par.getInset(pos));
816
817         LyXFont const & font = text.getFont(buffer_, &par, pos);
818
819         int const width = inset.width(bv_, font);
820         int const inset_x = font.isVisibleRightToLeft()
821                 ? (cursor.ix() - width) : cursor.ix();
822
823         return Box(
824                 inset_x + inset.scroll(),
825                 inset_x + width,
826                 cursor.iy() - inset.ascent(bv_, font),
827                 cursor.iy() + inset.descent(bv_, font));
828 }
829
830
831 Inset * BufferView::Pimpl::checkInset(LyXText const & text,
832                                       LyXCursor const & cursor,
833                                       int & x, int & y) const
834 {
835         pos_type const pos(cursor.pos());
836         Paragraph /*const*/ & par(*cursor.par());
837
838         if (pos >= par.size() || !par.isInset(pos)) {
839                 return 0;
840         }
841
842         Inset /*const*/ * inset = par.getInset(pos);
843
844         if (!isEditableInset(inset)) {
845                 return 0;
846         }
847
848         Box b(insetDimensions(text, cursor));
849
850         if (!b.contained(x, y)) {
851                 lyxerr[Debug::GUI] << "Missed inset at x,y " << x << "," << y
852                         << " box " << b << endl;
853                 return 0;
854         }
855
856         text.setCursor(bv_, &par, pos, true);
857
858         x -= b.x1;
859         // The origin of an inset is on the baseline
860         y -= text.cursor.iy();
861
862         return inset;
863 }
864
865
866 Inset * BufferView::Pimpl::checkInsetHit(LyXText * text, int & x, int & y)
867 {
868         int y_tmp = y + text->first_y;
869
870         LyXCursor cursor;
871         text->setCursorFromCoordinates(bv_, cursor, x, y_tmp);
872
873         Inset * inset = checkInset(*text, cursor, x, y_tmp);
874
875         if (inset) {
876                 y = y_tmp;
877                 return inset;
878         }
879
880         // look at previous position
881         if (cursor.pos() == 0) {
882                 return 0;
883         }
884
885         // move back one
886         text->setCursor(bv_, cursor, cursor.par(), cursor.pos() - 1, true);
887
888         inset = checkInset(*text, cursor, x, y_tmp);
889         if (inset) {
890                 y = y_tmp;
891         }
892         return inset;
893 }
894
895
896 void BufferView::Pimpl::workAreaResize()
897 {
898         static int work_area_width;
899         static int work_area_height;
900
901         bool const widthChange = workarea().workWidth() != work_area_width;
902         bool const heightChange = workarea().workHeight() != work_area_height;
903
904         // update from work area
905         work_area_width = workarea().workWidth();
906         work_area_height = workarea().workHeight();
907
908         if (buffer_ != 0) {
909                 if (widthChange) {
910                         // The visible LyXView need a resize
911                         resizeCurrentBuffer();
912
913                         // Remove all texts from the textcache
914                         // This is not _really_ what we want to do. What
915                         // we really want to do is to delete in textcache
916                         // that does not have a BufferView with matching
917                         // width, but as long as we have only one BufferView
918                         // deleting all gives the same result.
919                         if (lyxerr.debugging())
920                                 textcache.show(lyxerr, "Expose delete all");
921                         textcache.clear();
922                         // FIXME: this is already done in resizeCurrentBuffer() ??
923                         buffer_->resizeInsets(bv_);
924                 } else if (heightChange) {
925                         // fitCursor() ensures we don't jump back
926                         // to the start of the document on vertical
927                         // resize
928                         fitCursor();
929                 }
930         }
931
932         if (widthChange || heightChange) {
933                 repaint();
934         }
935
936         // always make sure that the scrollbar is sane.
937         updateScrollbar();
938         owner_->updateLayoutChoice();
939         return;
940 }
941
942
943 void BufferView::Pimpl::update()
944 {
945         if (!bv_->theLockingInset() || !bv_->theLockingInset()->nodraw()) {
946                 LyXText::text_status st = bv_->text->status();
947                 screen().update(bv_->text, bv_);
948                 bool fitc = false;
949                 while (bv_->text->status() == LyXText::CHANGED_IN_DRAW) {
950                         bv_->text->fullRebreak(bv_);
951                         st = LyXText::NEED_MORE_REFRESH;
952                         bv_->text->setCursor(bv_, bv_->text->cursor.par(),
953                                              bv_->text->cursor.pos());
954                         if (bv_->text->selection.set()) {
955                                 bv_->text->setCursor(bv_, bv_->text->selection.start,
956                                                      bv_->text->selection.start.par(),
957                                                      bv_->text->selection.start.pos());
958                                 bv_->text->setCursor(bv_, bv_->text->selection.end,
959                                                      bv_->text->selection.end.par(),
960                                                      bv_->text->selection.end.pos());
961                         }
962                         fitc = true;
963                         bv_->text->status(bv_, st);
964                         screen().update(bv_->text, bv_);
965                 }
966                 // do this here instead of in the screen::update because of
967                 // the above loop!
968                 bv_->text->status(bv_, LyXText::UNCHANGED);
969                 if (fitc)
970                         fitCursor();
971         }
972 }
973
974 // Values used when calling update:
975 // -3 - update
976 // -2 - update, move sel_cursor if selection, fitcursor
977 // -1 - update, move sel_cursor if selection, fitcursor, mark dirty
978 //  0 - update, move sel_cursor if selection, fitcursor
979 //  1 - update, move sel_cursor if selection, fitcursor, mark dirty
980 //  3 - update, move sel_cursor if selection
981 //
982 // update -
983 // a simple redraw of the parts that need refresh
984 //
985 // move sel_cursor if selection -
986 // the text's sel_cursor is moved if there is selection is progress
987 //
988 // fitcursor -
989 // fitCursor() is called and the scrollbar updated
990 //
991 // mark dirty -
992 // the buffer is marked dirty.
993 //
994 // enum {
995 //       UPDATE = 0,
996 //       SELECT = 1,
997 //       FITCUR = 2,
998 //       CHANGE = 4
999 // };
1000 //
1001 // UPDATE_ONLY = UPDATE;
1002 // UPDATE_SELECT = UPDATE | SELECT;
1003 // UPDATE_SELECT_MOVE = UPDATE | SELECT | FITCUR;
1004 // UPDATE_SELECT_MOVE_AFTER_CHANGE = UPDATE | SELECT | FITCUR | CHANGE;
1005 //
1006 // update(-3) -> update(0)         -> update(0) -> update(UPDATE)
1007 // update(-2) -> update(1 + 2)     -> update(3) -> update(SELECT|FITCUR)
1008 // update(-1) -> update(1 + 2 + 4) -> update(7) -> update(SELECT|FITCUR|CHANGE)
1009 // update(1)  -> update(1 + 2 + 4) -> update(7) -> update(SELECT|FITCUR|CHANGE)
1010 // update(3)  -> update(1)         -> update(1) -> update(SELECT)
1011
1012 void BufferView::Pimpl::update(LyXText * text, BufferView::UpdateCodes f)
1013 {
1014         owner_->updateLayoutChoice();
1015
1016         if (!text->selection.set() && (f & SELECT)) {
1017                 text->selection.cursor = text->cursor;
1018         }
1019
1020         text->fullRebreak(bv_);
1021
1022         if (text->inset_owner) {
1023                 text->inset_owner->setUpdateStatus(bv_, InsetText::NONE);
1024                 updateInset(text->inset_owner, false);
1025         } else {
1026                 update();
1027         }
1028
1029         if ((f & FITCUR)) {
1030                 fitCursor();
1031         }
1032
1033         if ((f & CHANGE)) {
1034                 buffer_->markDirty();
1035         }
1036 }
1037
1038
1039 // Callback for cursor timer
1040 void BufferView::Pimpl::cursorToggle()
1041 {
1042         if (!buffer_) {
1043                 cursor_timeout.restart();
1044                 return;
1045         }
1046
1047         /* FIXME */
1048         extern void reapSpellchecker(void);
1049         reapSpellchecker();
1050
1051         if (!bv_->theLockingInset()) {
1052                 screen().cursorToggle(bv_);
1053         } else {
1054                 bv_->theLockingInset()->toggleInsetCursor(bv_);
1055         }
1056
1057         cursor_timeout.restart();
1058 }
1059
1060
1061 bool BufferView::Pimpl::available() const
1062 {
1063         if (buffer_ && bv_->text)
1064                 return true;
1065         return false;
1066 }
1067
1068
1069 void BufferView::Pimpl::beforeChange(LyXText * text)
1070 {
1071         toggleSelection();
1072         text->clearSelection();
1073 }
1074
1075
1076 void BufferView::Pimpl::savePosition(unsigned int i)
1077 {
1078         if (i >= saved_positions_num)
1079                 return;
1080         saved_positions[i] = Position(buffer_->fileName(),
1081                                       bv_->text->cursor.par()->id(),
1082                                       bv_->text->cursor.pos());
1083         if (i > 0) {
1084                 ostringstream str;
1085                 str << _("Saved bookmark") << ' ' << i;
1086                 owner_->message(str.str().c_str());
1087         }
1088 }
1089
1090
1091 void BufferView::Pimpl::restorePosition(unsigned int i)
1092 {
1093         if (i >= saved_positions_num)
1094                 return;
1095
1096         string const fname = saved_positions[i].filename;
1097
1098         beforeChange(bv_->text);
1099
1100         if (fname != buffer_->fileName()) {
1101                 Buffer * b = bufferlist.exists(fname) ?
1102                         bufferlist.getBuffer(fname) :
1103                         bufferlist.loadLyXFile(fname); // don't ask, just load it
1104                 if (b != 0) buffer(b);
1105         }
1106
1107         Paragraph * par = buffer_->getParFromID(saved_positions[i].par_id);
1108         if (!par)
1109                 return;
1110
1111         bv_->text->setCursor(bv_, par,
1112                              min(par->size(), saved_positions[i].par_pos));
1113
1114         update(bv_->text, BufferView::SELECT | BufferView::FITCUR);
1115         if (i > 0) {
1116                 ostringstream str;
1117                 str << _("Moved to bookmark") << ' ' << i;
1118                 owner_->message(str.str().c_str());
1119         }
1120 }
1121
1122
1123 bool BufferView::Pimpl::isSavedPosition(unsigned int i)
1124 {
1125         if (i >= saved_positions_num)
1126                 return false;
1127
1128         return !saved_positions[i].filename.empty();
1129 }
1130
1131
1132 void BufferView::Pimpl::switchKeyMap()
1133 {
1134         if (!lyxrc.rtl_support)
1135                 return;
1136
1137         LyXText * text = bv_->getLyXText();
1138         if (text->real_current_font.isRightToLeft()
1139             && !(bv_->theLockingInset()
1140                  && bv_->theLockingInset()->lyxCode() == Inset::ERT_CODE))
1141         {
1142                 if (owner_->getIntl().keymap == Intl::PRIMARY)
1143                         owner_->getIntl().KeyMapSec();
1144         } else {
1145                 if (owner_->getIntl().keymap == Intl::SECONDARY)
1146                         owner_->getIntl().KeyMapPrim();
1147         }
1148 }
1149
1150
1151 void BufferView::Pimpl::insetUnlock()
1152 {
1153         if (bv_->theLockingInset()) {
1154                 bv_->theLockingInset()->insetUnlock(bv_);
1155                 bv_->theLockingInset(0);
1156                 finishUndo();
1157         }
1158 }
1159
1160
1161 void BufferView::Pimpl::showCursor()
1162 {
1163         if (bv_->theLockingInset())
1164                 bv_->theLockingInset()->showInsetCursor(bv_);
1165         else
1166                 screen().showCursor(bv_->text, bv_);
1167 }
1168
1169
1170 void BufferView::Pimpl::hideCursor()
1171 {
1172         if (!bv_->theLockingInset())
1173                 screen().hideCursor();
1174 }
1175
1176
1177 void BufferView::Pimpl::toggleSelection(bool b)
1178 {
1179         if (bv_->theLockingInset())
1180                 bv_->theLockingInset()->toggleSelection(bv_, b);
1181         screen().toggleSelection(bv_->text, bv_, b);
1182 }
1183
1184
1185 void BufferView::Pimpl::toggleToggle()
1186 {
1187         screen().toggleToggle(bv_->text, bv_);
1188 }
1189
1190
1191 void BufferView::Pimpl::center()
1192 {
1193         LyXText * t = bv_->text;
1194
1195         beforeChange(t);
1196         int const half_height = workarea().workHeight() / 2;
1197         int new_y = 0;
1198
1199         if (t->cursor.y() > half_height) {
1200                 new_y = t->cursor.y() - half_height;
1201         }
1202
1203         // FIXME: can we do this w/o calling screen directly ?
1204         // This updates first_y but means the fitCursor() call
1205         // from the update(FITCUR) doesn't realise that we might
1206         // have moved (e.g. from GOTOPARAGRAPH), so doesn't cause
1207         // the scrollbar to be updated as it should, so we have
1208         // to do it manually. Any operation that does a center()
1209         // and also might have moved first_y must make sure to call
1210         // updateScrollbar() currently. Never mind that this is a
1211         // pretty obfuscated way of updating t->first_y
1212         screen().draw(t, bv_, new_y);
1213
1214         update(t, BufferView::SELECT | BufferView::FITCUR);
1215 }
1216
1217
1218 void BufferView::Pimpl::stuffClipboard(string const & stuff) const
1219 {
1220         workarea().putClipboard(stuff);
1221 }
1222
1223
1224 /*
1225  * Dispatch functions for actions which can be valid for BufferView->text
1226  * and/or InsetText->text!!!
1227  */
1228
1229
1230 Inset * BufferView::Pimpl::getInsetByCode(Inset::Code code)
1231 {
1232 #if 0
1233         LyXCursor cursor = bv_->getLyXText()->cursor;
1234         Buffer::inset_iterator it =
1235                 find_if(Buffer::inset_iterator(
1236                         cursor.par(), cursor.pos()),
1237                         buffer_->inset_iterator_end(),
1238                         lyx::compare_memfun(&Inset::lyxCode, code));
1239         return it != buffer_->inset_iterator_end() ? (*it) : 0;
1240 #else
1241         // Ok, this is a little bit too brute force but it
1242         // should work for now. Better infrastructure is comming. (Lgb)
1243
1244         Buffer * b = bv_->buffer();
1245         LyXCursor cursor = bv_->getLyXText()->cursor;
1246
1247         Buffer::inset_iterator beg = b->inset_iterator_begin();
1248         Buffer::inset_iterator end = b->inset_iterator_end();
1249
1250         bool cursor_par_seen = false;
1251
1252         for (; beg != end; ++beg) {
1253                 if (beg.getPar() == cursor.par()) {
1254                         cursor_par_seen = true;
1255                 }
1256                 if (cursor_par_seen) {
1257                         if (beg.getPar() == cursor.par()
1258                             && beg.getPos() >= cursor.pos()) {
1259                                 break;
1260                         } else if (beg.getPar() != cursor.par()) {
1261                                 break;
1262                         }
1263                 }
1264
1265         }
1266         if (beg != end) {
1267                 // Now find the first inset that matches code.
1268                 for (; beg != end; ++beg) {
1269                         if (beg->lyxCode() == code) {
1270                                 return &(*beg);
1271                         }
1272                 }
1273         }
1274         return 0;
1275 #endif
1276 }
1277
1278
1279 void BufferView::Pimpl::MenuInsertLyXFile(string const & filen)
1280 {
1281         string filename = filen;
1282
1283         if (filename.empty()) {
1284                 // Launch a file browser
1285                 string initpath = lyxrc.document_path;
1286
1287                 if (available()) {
1288                         string const trypath = owner_->buffer()->filePath();
1289                         // If directory is writeable, use this as default.
1290                         if (IsDirWriteable(trypath))
1291                                 initpath = trypath;
1292                 }
1293
1294                 FileDialog fileDlg(bv_->owner(),
1295                                    _("Select LyX document to insert"),
1296                         LFUN_FILE_INSERT,
1297                         make_pair(string(_("Documents|#o#O")),
1298                                   string(lyxrc.document_path)),
1299                         make_pair(string(_("Examples|#E#e")),
1300                                   string(AddPath(system_lyxdir, "examples"))));
1301
1302                 FileDialog::Result result =
1303                         fileDlg.Select(initpath,
1304                                        _("*.lyx| LyX Documents (*.lyx)"));
1305
1306                 if (result.first == FileDialog::Later)
1307                         return;
1308
1309                 filename = result.second;
1310
1311                 // check selected filename
1312                 if (filename.empty()) {
1313                         owner_->message(_("Canceled."));
1314                         return;
1315                 }
1316         }
1317
1318         // get absolute path of file and add ".lyx" to the filename if
1319         // necessary
1320         filename = FileSearch(string(), filename, "lyx");
1321
1322         string const disp_fn(MakeDisplayPath(filename));
1323
1324         ostringstream s1;
1325         s1 << _("Inserting document") << ' '
1326            << disp_fn << " ...";
1327         owner_->message(s1.str().c_str());
1328         bool const res = bv_->insertLyXFile(filename);
1329         if (res) {
1330                 ostringstream str;
1331                 str << _("Document") << ' ' << disp_fn
1332                     << ' ' << _("inserted.");
1333                 owner_->message(str.str().c_str());
1334         } else {
1335                 ostringstream str;
1336                 str << _("Could not insert document") << ' '
1337                     << disp_fn;
1338                 owner_->message(str.str().c_str());
1339         }
1340 }
1341
1342
1343 bool BufferView::Pimpl::dispatch(FuncRequest const & ev)
1344 {
1345         lyxerr[Debug::ACTION] << "BufferView::Pimpl::Dispatch: action["
1346           << ev.action <<"] arg[" << ev.argument << "]" << endl;
1347
1348         LyXTextClass const & tclass = buffer_->params.getLyXTextClass();
1349
1350         switch (ev.action) {
1351
1352         case LFUN_TOC_INSERT:
1353         {
1354                 InsetCommandParams p;
1355                 p.setCmdName("tableofcontents");
1356                 Inset * inset = new InsetTOC(p);
1357                 if (!insertInset(inset, tclass.defaultLayoutName()))
1358                         delete inset;
1359                 break;
1360         }
1361
1362         case LFUN_SCROLL_INSET:
1363                 // this is not handled here as this function is only active
1364                 // if we have a locking_inset and that one is (or contains)
1365                 // a tabular-inset
1366                 break;
1367
1368         case LFUN_INSET_GRAPHICS:
1369         {
1370                 Inset * new_inset = new InsetGraphics;
1371                 if (!insertInset(new_inset)) {
1372                         delete new_inset;
1373                 } else {
1374                         // this is need because you don't use a inset->Edit()
1375                         updateInset(new_inset, true);
1376                         new_inset->edit(bv_);
1377                 }
1378                 break;
1379         }
1380
1381         case LFUN_LAYOUT_COPY:
1382                 bv_->copyEnvironment();
1383                 break;
1384
1385         case LFUN_LAYOUT_PASTE:
1386                 bv_->pasteEnvironment();
1387                 switchKeyMap();
1388                 break;
1389
1390         case LFUN_DEPTH_MIN:
1391                 changeDepth(bv_, bv_->getLyXText(), -1);
1392                 break;
1393
1394         case LFUN_DEPTH_PLUS:
1395                 changeDepth(bv_, bv_->getLyXText(), 1);
1396                 break;
1397
1398         case LFUN_FREE:
1399                 owner_->getDialogs().setUserFreeFont();
1400                 break;
1401
1402         case LFUN_FILE_INSERT:
1403                 MenuInsertLyXFile(ev.argument);
1404                 break;
1405
1406         case LFUN_FILE_INSERT_ASCII_PARA:
1407                 InsertAsciiFile(bv_, ev.argument, true);
1408                 break;
1409
1410         case LFUN_FILE_INSERT_ASCII:
1411                 InsertAsciiFile(bv_, ev.argument, false);
1412                 break;
1413
1414         case LFUN_LANGUAGE:
1415                 lang(bv_, ev.argument);
1416                 switchKeyMap();
1417                 owner_->view_state_changed();
1418                 break;
1419
1420         case LFUN_EMPH:
1421                 emph(bv_);
1422                 owner_->view_state_changed();
1423                 break;
1424
1425         case LFUN_BOLD:
1426                 bold(bv_);
1427                 owner_->view_state_changed();
1428                 break;
1429
1430         case LFUN_NOUN:
1431                 noun(bv_);
1432                 owner_->view_state_changed();
1433                 break;
1434
1435         case LFUN_CODE:
1436                 code(bv_);
1437                 owner_->view_state_changed();
1438                 break;
1439
1440         case LFUN_SANS:
1441                 sans(bv_);
1442                 owner_->view_state_changed();
1443                 break;
1444
1445         case LFUN_ROMAN:
1446                 roman(bv_);
1447                 owner_->view_state_changed();
1448                 break;
1449
1450         case LFUN_DEFAULT:
1451                 styleReset(bv_);
1452                 owner_->view_state_changed();
1453                 break;
1454
1455         case LFUN_UNDERLINE:
1456                 underline(bv_);
1457                 owner_->view_state_changed();
1458                 break;
1459
1460         case LFUN_FONT_SIZE:
1461                 fontSize(bv_, ev.argument);
1462                 owner_->view_state_changed();
1463                 break;
1464
1465         case LFUN_FONT_STATE:
1466                 owner_->getLyXFunc().setMessage(currentState(bv_));
1467                 break;
1468
1469         case LFUN_INSERT_LABEL:
1470                 MenuInsertLabel(bv_, ev.argument);
1471                 break;
1472
1473         case LFUN_REF_INSERT:
1474                 if (ev.argument.empty()) {
1475                         InsetCommandParams p("ref");
1476                         owner_->getDialogs().createRef(p.getAsString());
1477                 } else {
1478                         InsetCommandParams p;
1479                         p.setFromString(ev.argument);
1480
1481                         InsetRef * inset = new InsetRef(p, *buffer_);
1482                         if (!insertInset(inset))
1483                                 delete inset;
1484                         else
1485                                 updateInset(inset, true);
1486                 }
1487                 break;
1488
1489         case LFUN_BOOKMARK_SAVE:
1490                 savePosition(strToUnsignedInt(ev.argument));
1491                 break;
1492
1493         case LFUN_BOOKMARK_GOTO:
1494                 restorePosition(strToUnsignedInt(ev.argument));
1495                 break;
1496
1497         case LFUN_REF_GOTO:
1498         {
1499                 string label = ev.argument;
1500                 if (label.empty()) {
1501                         InsetRef * inset =
1502                                 static_cast<InsetRef*>(getInsetByCode(Inset::REF_CODE));
1503                         if (inset) {
1504                                 label = inset->getContents();
1505                                 savePosition(0);
1506                         }
1507                 }
1508
1509                 if (!label.empty()) {
1510                         //bv_->savePosition(0);
1511                         if (!bv_->gotoLabel(label))
1512                                 Alert::alert(_("Error"),
1513                                            _("Couldn't find this label"),
1514                                            _("in current document."));
1515                 }
1516         }
1517         break;
1518
1519         case LFUN_HTMLURL:
1520         case LFUN_URL:
1521         {
1522                 InsetCommandParams p;
1523                 if (ev.action == LFUN_HTMLURL)
1524                         p.setCmdName("htmlurl");
1525                 else
1526                         p.setCmdName("url");
1527                 owner_->getDialogs().createUrl(p.getAsString());
1528         }
1529         break;
1530
1531         case LFUN_INSERT_URL:
1532         {
1533                 InsetCommandParams p;
1534                 p.setFromString(ev.argument);
1535
1536                 InsetUrl * inset = new InsetUrl(p);
1537                 if (!insertInset(inset))
1538                         delete inset;
1539                 else
1540                         updateInset(inset, true);
1541         }
1542         break;
1543
1544 #if 0
1545         case LFUN_INSET_LIST:
1546         case LFUN_INSET_THEOREM:
1547 #endif
1548         case LFUN_INSERT_NOTE:
1549         case LFUN_INSET_ERT:
1550         case LFUN_INSET_EXTERNAL:
1551         case LFUN_INSET_FLOAT:
1552         case LFUN_INSET_FOOTNOTE:
1553         case LFUN_INSET_MARGINAL:
1554         case LFUN_INSET_MINIPAGE:
1555         case LFUN_INSET_OPTARG:
1556         case LFUN_INSET_WIDE_FLOAT:
1557         {
1558                 FuncRequest cmd = ev;
1559                 cmd.setView(bv_);
1560                 Inset * inset = createInset(cmd);
1561                 if (inset) {
1562                         bool gotsel = false;
1563
1564                         if (bv_->getLyXText()->selection.set()) {
1565                                 bv_->getLyXText()->cutSelection(bv_, true, false);
1566                                 gotsel = true;
1567                         }
1568
1569                         if (insertInset(inset)) {
1570                                 inset->edit(bv_);
1571                                 if (gotsel)
1572                                         owner_->dispatch(FuncRequest(LFUN_PASTESELECTION));
1573                         }
1574                         else
1575                                 delete inset;
1576                 }
1577                 break;
1578         }
1579
1580         case LFUN_INSET_CAPTION:
1581         {
1582                 // Do we have a locking inset...
1583                 if (bv_->theLockingInset()) {
1584                         lyxerr << "Locking inset code: "
1585                                << static_cast<int>(bv_->theLockingInset()->lyxCode());
1586                         InsetCaption * new_inset =
1587                                 new InsetCaption(buffer_->params);
1588                         new_inset->setOwner(bv_->theLockingInset());
1589                         new_inset->setAutoBreakRows(true);
1590                         new_inset->setDrawFrame(0, InsetText::LOCKED);
1591                         new_inset->setFrameColor(0, LColor::captionframe);
1592                         if (insertInset(new_inset))
1593                                 new_inset->edit(bv_);
1594                         else
1595                                 delete new_inset;
1596                 }
1597         }
1598         break;
1599
1600         case LFUN_TABULAR_INSERT:
1601         {
1602                 if (ev.argument.empty()) {
1603                         owner_->getDialogs().showTabularCreate();
1604                         break;
1605                 }
1606
1607                 int r = 2;
1608                 int c = 2;
1609                 ::sscanf(ev.argument.c_str(),"%d%d", &r, &c);
1610                 InsetTabular * new_inset =
1611                         new InsetTabular(*buffer_, r, c);
1612                 bool const rtl =
1613                         bv_->getLyXText()->real_current_font.isRightToLeft();
1614                 if (!open_new_inset(new_inset, rtl))
1615                         delete new_inset;
1616         }
1617         break;
1618
1619
1620         // --- accented characters ---------------------------
1621
1622         case LFUN_UMLAUT:
1623         case LFUN_CIRCUMFLEX:
1624         case LFUN_GRAVE:
1625         case LFUN_ACUTE:
1626         case LFUN_TILDE:
1627         case LFUN_CEDILLA:
1628         case LFUN_MACRON:
1629         case LFUN_DOT:
1630         case LFUN_UNDERDOT:
1631         case LFUN_UNDERBAR:
1632         case LFUN_CARON:
1633         case LFUN_SPECIAL_CARON:
1634         case LFUN_BREVE:
1635         case LFUN_TIE:
1636         case LFUN_HUNG_UMLAUT:
1637         case LFUN_CIRCLE:
1638         case LFUN_OGONEK:
1639                 if (ev.argument.empty()) {
1640                         // As always...
1641                         owner_->getLyXFunc().handleKeyFunc(ev.action);
1642                 } else {
1643                         owner_->getLyXFunc().handleKeyFunc(ev.action);
1644                         owner_->getIntl().getTransManager()
1645                                 .TranslateAndInsert(ev.argument[0], bv_->getLyXText());
1646                         update(bv_->getLyXText(),
1647                                BufferView::SELECT
1648                                | BufferView::FITCUR
1649                                | BufferView::CHANGE);
1650                 }
1651                 break;
1652
1653         case LFUN_MATH_MACRO:
1654         case LFUN_MATH_DELIM:
1655         case LFUN_INSERT_MATRIX:
1656         case LFUN_INSERT_MATH:
1657         case LFUN_MATH_IMPORT_SELECTION: // Imports LaTeX from the X selection
1658         case LFUN_MATH_DISPLAY:          // Open or create a displayed math inset
1659         case LFUN_MATH_MODE:             // Open or create an inlined math inset
1660         case LFUN_GREEK:                 // Insert a single greek letter
1661                 mathDispatch(FuncRequest(bv_, ev.action, ev.argument));
1662                 break;
1663
1664         case LFUN_CITATION_INSERT:
1665         {
1666                 InsetCommandParams p;
1667                 p.setFromString(ev.argument);
1668
1669                 InsetCitation * inset = new InsetCitation(p);
1670                 if (!insertInset(inset))
1671                         delete inset;
1672                 else
1673                         updateInset(inset, true);
1674         }
1675         break;
1676
1677         case LFUN_INSERT_BIBTEX:
1678         {
1679                 // ale970405+lasgoutt970425
1680                 // The argument can be up to two tokens separated
1681                 // by a space. The first one is the bibstyle.
1682                 string const db = token(ev.argument, ' ', 0);
1683                 string bibstyle = token(ev.argument, ' ', 1);
1684                 if (bibstyle.empty())
1685                         bibstyle = "plain";
1686
1687                 InsetCommandParams p("BibTeX", db, bibstyle);
1688                 InsetBibtex * inset = new InsetBibtex(p);
1689
1690                 if (insertInset(inset)) {
1691                         if (ev.argument.empty())
1692                                 inset->edit(bv_);
1693                 } else
1694                         delete inset;
1695         }
1696         break;
1697
1698         // BibTeX data bases
1699         case LFUN_BIBDB_ADD:
1700         {
1701                 InsetBibtex * inset =
1702                         static_cast<InsetBibtex*>(getInsetByCode(Inset::BIBTEX_CODE));
1703                 if (inset) {
1704                         inset->addDatabase(ev.argument);
1705                 }
1706         }
1707         break;
1708
1709         case LFUN_BIBDB_DEL:
1710         {
1711                 InsetBibtex * inset =
1712                         static_cast<InsetBibtex*>(getInsetByCode(Inset::BIBTEX_CODE));
1713                 if (inset) {
1714                         inset->delDatabase(ev.argument);
1715                 }
1716         }
1717         break;
1718
1719         case LFUN_BIBTEX_STYLE:
1720         {
1721                 InsetBibtex * inset =
1722                         static_cast<InsetBibtex*>(getInsetByCode(Inset::BIBTEX_CODE));
1723                 if (inset) {
1724                         inset->setOptions(ev.argument);
1725                 }
1726         }
1727         break;
1728
1729         case LFUN_INDEX_INSERT:
1730         {
1731                 string entry = ev.argument;
1732                 if (entry.empty())
1733                         entry = bv_->getLyXText()->getStringToIndex(bv_);
1734
1735                 if (entry.empty()) {
1736                         owner_->getDialogs().createIndex();
1737                         break;
1738                 }
1739
1740                 InsetIndex * inset = new InsetIndex(InsetCommandParams("index", entry));
1741
1742                 if (!insertInset(inset)) {
1743                         delete inset;
1744                 } else {
1745                         updateInset(inset, true);
1746                 }
1747         }
1748         break;
1749
1750         case LFUN_INDEX_PRINT:
1751         {
1752                 InsetCommandParams p("printindex");
1753                 Inset * inset = new InsetPrintIndex(p);
1754                 if (!insertInset(inset, tclass.defaultLayoutName()))
1755                         delete inset;
1756         }
1757         break;
1758
1759         case LFUN_PARENTINSERT:
1760         {
1761                 InsetCommandParams p("lyxparent", ev.argument);
1762                 Inset * inset = new InsetParent(p, *buffer_);
1763                 if (!insertInset(inset, tclass.defaultLayoutName()))
1764                         delete inset;
1765         }
1766
1767         break;
1768
1769         case LFUN_CHILD_INSERT:
1770         {
1771                 InsetInclude::Params p;
1772                 p.cparams.setFromString(ev.argument);
1773                 p.masterFilename_ = buffer_->fileName();
1774
1775                 InsetInclude * inset = new InsetInclude(p);
1776                 if (!insertInset(inset))
1777                         delete inset;
1778                 else {
1779                         updateInset(inset, true);
1780                         bv_->owner()->getDialogs().showInclude(inset);
1781                 }
1782         }
1783         break;
1784
1785         case LFUN_FLOAT_LIST:
1786                 if (floatList.typeExist(ev.argument)) {
1787                         Inset * inset = new InsetFloatList(ev.argument);
1788                         if (!insertInset(inset, tclass.defaultLayoutName()))
1789                                 delete inset;
1790                 } else {
1791                         lyxerr << "Non-existent float type: "
1792                                << ev.argument << endl;
1793                 }
1794                 break;
1795
1796         case LFUN_THESAURUS_ENTRY:
1797         {
1798                 string arg = ev.argument;
1799
1800                 if (arg.empty()) {
1801                         arg = bv_->getLyXText()->selectionAsString(buffer_,
1802                                                                    false);
1803
1804                         // FIXME
1805                         if (arg.size() > 100 || arg.empty()) {
1806                                 // Get word or selection
1807                                 bv_->getLyXText()->selectWordWhenUnderCursor(bv_, LyXText::WHOLE_WORD);
1808                                 arg = bv_->getLyXText()->selectionAsString(buffer_, false);
1809                                 // FIXME: where is getLyXText()->unselect(bv_) ?
1810                         }
1811                 }
1812
1813                 bv_->owner()->getDialogs().showThesaurus(arg);
1814         }
1815                 break;
1816
1817         case LFUN_UNKNOWN_ACTION:
1818                 ev.errorMessage(N_("Unknown function!"));
1819                 break;
1820
1821         default:
1822                 FuncRequest cmd = ev;
1823                 cmd.setView(bv_);
1824                 return bv_->getLyXText()->dispatch(cmd);
1825         } // end of switch
1826
1827         return true;
1828 }
1829
1830
1831 // Open and lock an updatable inset
1832 bool BufferView::Pimpl::open_new_inset(UpdatableInset * new_inset, bool behind)
1833 {
1834         LyXText * lt = bv_->getLyXText();
1835
1836         beforeChange(lt);
1837         finishUndo();
1838         if (!insertInset(new_inset)) {
1839                 delete new_inset;
1840                 return false;
1841         }
1842         new_inset->edit(bv_, !behind);
1843         return true;
1844 }
1845
1846
1847 bool BufferView::Pimpl::insertInset(Inset * inset, string const & lout)
1848 {
1849         // if we are in a locking inset we should try to insert the
1850         // inset there otherwise this is a illegal function now
1851         if (bv_->theLockingInset()) {
1852                 if (bv_->theLockingInset()->insetAllowed(inset))
1853                     return bv_->theLockingInset()->insertInset(bv_, inset);
1854                 return false;
1855         }
1856
1857         // not quite sure if we want this...
1858         setCursorParUndo(bv_);
1859         freezeUndo();
1860
1861         beforeChange(bv_->text);
1862         if (!lout.empty()) {
1863                 update(bv_->text, BufferView::SELECT|BufferView::FITCUR);
1864                 bv_->text->breakParagraph(bv_);
1865                 update(bv_->text, BufferView::SELECT|BufferView::FITCUR|BufferView::CHANGE);
1866
1867                 if (!bv_->text->cursor.par()->empty()) {
1868                         bv_->text->cursorLeft(bv_);
1869
1870                         bv_->text->breakParagraph(bv_);
1871                         update(bv_->text, BufferView::SELECT|BufferView::FITCUR|BufferView::CHANGE);
1872                 }
1873
1874                 string lres = lout;
1875                 LyXTextClass const & tclass =
1876                         buffer_->params.getLyXTextClass();
1877                 bool hasLayout = tclass.hasLayout(lres);
1878                 string lay = tclass.defaultLayoutName();
1879
1880                 if (hasLayout != false) {
1881                         // layout found
1882                         lay = lres;
1883                 } else {
1884                         // layout not fount using default
1885                         lay = tclass.defaultLayoutName();
1886                 }
1887
1888                 bv_->text->setLayout(bv_, lay);
1889
1890                 bv_->text->setParagraph(bv_, 0, 0,
1891                                    0, 0,
1892                                    VSpace(VSpace::NONE), VSpace(VSpace::NONE),
1893                                    Spacing(),
1894                                    LYX_ALIGN_LAYOUT,
1895                                    string(),
1896                                    0);
1897                 update(bv_->text, BufferView::SELECT|BufferView::FITCUR|BufferView::CHANGE);
1898         }
1899
1900         bv_->text->insertInset(bv_, inset);
1901         update(bv_->text, BufferView::SELECT|BufferView::FITCUR|BufferView::CHANGE);
1902
1903         unFreezeUndo();
1904         return true;
1905 }
1906
1907
1908 void BufferView::Pimpl::updateInset(Inset * inset, bool mark_dirty)
1909 {
1910         if (!inset || !available())
1911                 return;
1912
1913         // first check for locking insets
1914         if (bv_->theLockingInset()) {
1915                 if (bv_->theLockingInset() == inset) {
1916                         if (bv_->text->updateInset(bv_, inset)) {
1917                                 update();
1918                                 if (mark_dirty) {
1919                                         buffer_->markDirty();
1920                                 }
1921                                 updateScrollbar();
1922                                 return;
1923                         }
1924                 } else if (bv_->theLockingInset()->updateInsetInInset(bv_, inset)) {
1925                         if (bv_->text->updateInset(bv_,  bv_->theLockingInset())) {
1926                                 update();
1927                                 if (mark_dirty) {
1928                                         buffer_->markDirty();
1929                                 }
1930                                 updateScrollbar();
1931                                 return;
1932                         }
1933                 }
1934         }
1935
1936         // then check if the inset is a top_level inset (has no owner)
1937         // if yes do the update as always otherwise we have to update the
1938         // toplevel inset where this inset is inside
1939         Inset * tl_inset = inset;
1940         while (tl_inset->owner())
1941                 tl_inset = tl_inset->owner();
1942         hideCursor();
1943         if (tl_inset == inset) {
1944                 update(bv_->text, BufferView::UPDATE);
1945                 if (bv_->text->updateInset(bv_, inset)) {
1946                         if (mark_dirty) {
1947                                 update(bv_->text,
1948                                        BufferView::SELECT
1949                                        | BufferView::FITCUR
1950                                        | BufferView::CHANGE);
1951                         } else {
1952                                 update(bv_->text, SELECT);
1953                         }
1954                         return;
1955                 }
1956         } else if (static_cast<UpdatableInset *>(tl_inset)
1957                            ->updateInsetInInset(bv_, inset))
1958         {
1959                 if (bv_->text->updateInset(bv_, tl_inset)) {
1960                         update();
1961                         updateScrollbar();
1962                 }
1963         }
1964 }