]> git.lyx.org Git - lyx.git/blob - src/BufferView_pimpl.C
27 moved, 19 killed...
[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 void BufferView::Pimpl::moveCursorUpdate(bool selecting, bool fitcur)
1231 {
1232         LyXText * lt = bv_->getLyXText();
1233
1234         if (selecting || lt->selection.mark()) {
1235                 lt->setSelection(bv_);
1236                 if (lt->bv_owner)
1237                         toggleToggle();
1238                 else
1239                         updateInset(lt->inset_owner, false);
1240         }
1241         if (lt->bv_owner) {
1242                 if (fitcur)
1243                         update(lt, BufferView::SELECT|BufferView::FITCUR);
1244                 else
1245                         update(lt, BufferView::SELECT);
1246                 showCursor();
1247         } else if (bv_->text->status() != LyXText::UNCHANGED) {
1248                 bv_->theLockingInset()->hideInsetCursor(bv_);
1249                 update(bv_->text, BufferView::SELECT|BufferView::FITCUR);
1250                 showCursor();
1251         }
1252
1253         if (!lt->selection.set())
1254                 workarea().haveSelection(false);
1255
1256         switchKeyMap();
1257 }
1258
1259
1260 Inset * BufferView::Pimpl::getInsetByCode(Inset::Code code)
1261 {
1262 #if 0
1263         LyXCursor cursor = bv_->getLyXText()->cursor;
1264         Buffer::inset_iterator it =
1265                 find_if(Buffer::inset_iterator(
1266                         cursor.par(), cursor.pos()),
1267                         buffer_->inset_iterator_end(),
1268                         lyx::compare_memfun(&Inset::lyxCode, code));
1269         return it != buffer_->inset_iterator_end() ? (*it) : 0;
1270 #else
1271         // Ok, this is a little bit too brute force but it
1272         // should work for now. Better infrastructure is comming. (Lgb)
1273
1274         Buffer * b = bv_->buffer();
1275         LyXCursor cursor = bv_->getLyXText()->cursor;
1276
1277         Buffer::inset_iterator beg = b->inset_iterator_begin();
1278         Buffer::inset_iterator end = b->inset_iterator_end();
1279
1280         bool cursor_par_seen = false;
1281
1282         for (; beg != end; ++beg) {
1283                 if (beg.getPar() == cursor.par()) {
1284                         cursor_par_seen = true;
1285                 }
1286                 if (cursor_par_seen) {
1287                         if (beg.getPar() == cursor.par()
1288                             && beg.getPos() >= cursor.pos()) {
1289                                 break;
1290                         } else if (beg.getPar() != cursor.par()) {
1291                                 break;
1292                         }
1293                 }
1294
1295         }
1296         if (beg != end) {
1297                 // Now find the first inset that matches code.
1298                 for (; beg != end; ++beg) {
1299                         if (beg->lyxCode() == code) {
1300                                 return &(*beg);
1301                         }
1302                 }
1303         }
1304         return 0;
1305 #endif
1306 }
1307
1308
1309 void BufferView::Pimpl::MenuInsertLyXFile(string const & filen)
1310 {
1311         string filename = filen;
1312
1313         if (filename.empty()) {
1314                 // Launch a file browser
1315                 string initpath = lyxrc.document_path;
1316
1317                 if (available()) {
1318                         string const trypath = owner_->buffer()->filePath();
1319                         // If directory is writeable, use this as default.
1320                         if (IsDirWriteable(trypath))
1321                                 initpath = trypath;
1322                 }
1323
1324                 FileDialog fileDlg(bv_->owner(),
1325                                    _("Select LyX document to insert"),
1326                         LFUN_FILE_INSERT,
1327                         make_pair(string(_("Documents|#o#O")),
1328                                   string(lyxrc.document_path)),
1329                         make_pair(string(_("Examples|#E#e")),
1330                                   string(AddPath(system_lyxdir, "examples"))));
1331
1332                 FileDialog::Result result =
1333                         fileDlg.Select(initpath,
1334                                        _("*.lyx| LyX Documents (*.lyx)"));
1335
1336                 if (result.first == FileDialog::Later)
1337                         return;
1338
1339                 filename = result.second;
1340
1341                 // check selected filename
1342                 if (filename.empty()) {
1343                         owner_->message(_("Canceled."));
1344                         return;
1345                 }
1346         }
1347
1348         // get absolute path of file and add ".lyx" to the filename if
1349         // necessary
1350         filename = FileSearch(string(), filename, "lyx");
1351
1352         string const disp_fn(MakeDisplayPath(filename));
1353
1354         ostringstream s1;
1355         s1 << _("Inserting document") << ' '
1356            << disp_fn << " ...";
1357         owner_->message(s1.str().c_str());
1358         bool const res = bv_->insertLyXFile(filename);
1359         if (res) {
1360                 ostringstream str;
1361                 str << _("Document") << ' ' << disp_fn
1362                     << ' ' << _("inserted.");
1363                 owner_->message(str.str().c_str());
1364         } else {
1365                 ostringstream str;
1366                 str << _("Could not insert document") << ' '
1367                     << disp_fn;
1368                 owner_->message(str.str().c_str());
1369         }
1370 }
1371
1372
1373 bool BufferView::Pimpl::dispatch(FuncRequest const & ev)
1374 {
1375         lyxerr[Debug::ACTION] << "BufferView::Pimpl::Dispatch: action["
1376           << ev.action <<"] arg[" << ev.argument << "]" << endl;
1377
1378         LyXTextClass const & tclass = buffer_->params.getLyXTextClass();
1379
1380         switch (ev.action) {
1381
1382         case LFUN_TOC_INSERT:
1383         {
1384                 InsetCommandParams p;
1385                 p.setCmdName("tableofcontents");
1386                 Inset * inset = new InsetTOC(p);
1387                 if (!insertInset(inset, tclass.defaultLayoutName()))
1388                         delete inset;
1389                 break;
1390         }
1391
1392         case LFUN_SCROLL_INSET:
1393                 // this is not handled here as this function is only active
1394                 // if we have a locking_inset and that one is (or contains)
1395                 // a tabular-inset
1396                 break;
1397
1398         case LFUN_INSET_GRAPHICS:
1399         {
1400                 Inset * new_inset = new InsetGraphics;
1401                 if (!insertInset(new_inset)) {
1402                         delete new_inset;
1403                 } else {
1404                         // this is need because you don't use a inset->Edit()
1405                         updateInset(new_inset, true);
1406                         new_inset->edit(bv_);
1407                 }
1408                 break;
1409         }
1410
1411         case LFUN_LAYOUT_COPY:
1412                 bv_->copyEnvironment();
1413                 break;
1414
1415         case LFUN_LAYOUT_PASTE:
1416                 bv_->pasteEnvironment();
1417                 switchKeyMap();
1418                 break;
1419
1420         case LFUN_DEPTH_MIN:
1421                 changeDepth(bv_, bv_->getLyXText(), -1);
1422                 break;
1423
1424         case LFUN_DEPTH_PLUS:
1425                 changeDepth(bv_, bv_->getLyXText(), 1);
1426                 break;
1427
1428         case LFUN_FREE:
1429                 owner_->getDialogs().setUserFreeFont();
1430                 break;
1431
1432         case LFUN_FILE_INSERT:
1433                 MenuInsertLyXFile(ev.argument);
1434                 break;
1435
1436         case LFUN_FILE_INSERT_ASCII_PARA:
1437                 InsertAsciiFile(bv_, ev.argument, true);
1438                 break;
1439
1440         case LFUN_FILE_INSERT_ASCII:
1441                 InsertAsciiFile(bv_, ev.argument, false);
1442                 break;
1443
1444         case LFUN_LANGUAGE:
1445                 lang(bv_, ev.argument);
1446                 switchKeyMap();
1447                 owner_->view_state_changed();
1448                 break;
1449
1450         case LFUN_EMPH:
1451                 emph(bv_);
1452                 owner_->view_state_changed();
1453                 break;
1454
1455         case LFUN_BOLD:
1456                 bold(bv_);
1457                 owner_->view_state_changed();
1458                 break;
1459
1460         case LFUN_NOUN:
1461                 noun(bv_);
1462                 owner_->view_state_changed();
1463                 break;
1464
1465         case LFUN_CODE:
1466                 code(bv_);
1467                 owner_->view_state_changed();
1468                 break;
1469
1470         case LFUN_SANS:
1471                 sans(bv_);
1472                 owner_->view_state_changed();
1473                 break;
1474
1475         case LFUN_ROMAN:
1476                 roman(bv_);
1477                 owner_->view_state_changed();
1478                 break;
1479
1480         case LFUN_DEFAULT:
1481                 styleReset(bv_);
1482                 owner_->view_state_changed();
1483                 break;
1484
1485         case LFUN_UNDERLINE:
1486                 underline(bv_);
1487                 owner_->view_state_changed();
1488                 break;
1489
1490         case LFUN_FONT_SIZE:
1491                 fontSize(bv_, ev.argument);
1492                 owner_->view_state_changed();
1493                 break;
1494
1495         case LFUN_FONT_STATE:
1496                 owner_->getLyXFunc().setMessage(currentState(bv_));
1497                 break;
1498
1499         case LFUN_INSERT_LABEL:
1500                 MenuInsertLabel(bv_, ev.argument);
1501                 break;
1502
1503         case LFUN_REF_INSERT:
1504                 if (ev.argument.empty()) {
1505                         InsetCommandParams p("ref");
1506                         owner_->getDialogs().createRef(p.getAsString());
1507                 } else {
1508                         InsetCommandParams p;
1509                         p.setFromString(ev.argument);
1510
1511                         InsetRef * inset = new InsetRef(p, *buffer_);
1512                         if (!insertInset(inset))
1513                                 delete inset;
1514                         else
1515                                 updateInset(inset, true);
1516                 }
1517                 break;
1518
1519         case LFUN_BOOKMARK_SAVE:
1520                 savePosition(strToUnsignedInt(ev.argument));
1521                 break;
1522
1523         case LFUN_BOOKMARK_GOTO:
1524                 restorePosition(strToUnsignedInt(ev.argument));
1525                 break;
1526
1527         case LFUN_REF_GOTO:
1528         {
1529                 string label = ev.argument;
1530                 if (label.empty()) {
1531                         InsetRef * inset =
1532                                 static_cast<InsetRef*>(getInsetByCode(Inset::REF_CODE));
1533                         if (inset) {
1534                                 label = inset->getContents();
1535                                 savePosition(0);
1536                         }
1537                 }
1538
1539                 if (!label.empty()) {
1540                         //bv_->savePosition(0);
1541                         if (!bv_->gotoLabel(label))
1542                                 Alert::alert(_("Error"),
1543                                            _("Couldn't find this label"),
1544                                            _("in current document."));
1545                 }
1546         }
1547         break;
1548
1549         case LFUN_HTMLURL:
1550         case LFUN_URL:
1551         {
1552                 InsetCommandParams p;
1553                 if (ev.action == LFUN_HTMLURL)
1554                         p.setCmdName("htmlurl");
1555                 else
1556                         p.setCmdName("url");
1557                 owner_->getDialogs().createUrl(p.getAsString());
1558         }
1559         break;
1560
1561         case LFUN_INSERT_URL:
1562         {
1563                 InsetCommandParams p;
1564                 p.setFromString(ev.argument);
1565
1566                 InsetUrl * inset = new InsetUrl(p);
1567                 if (!insertInset(inset))
1568                         delete inset;
1569                 else
1570                         updateInset(inset, true);
1571         }
1572         break;
1573
1574 #if 0
1575         case LFUN_INSET_LIST:
1576         case LFUN_INSET_THEOREM:
1577 #endif
1578         case LFUN_INSERT_NOTE:
1579         case LFUN_INSET_ERT:
1580         case LFUN_INSET_EXTERNAL:
1581         case LFUN_INSET_FLOAT:
1582         case LFUN_INSET_FOOTNOTE:
1583         case LFUN_INSET_MARGINAL:
1584         case LFUN_INSET_MINIPAGE:
1585         case LFUN_INSET_OPTARG:
1586         case LFUN_INSET_WIDE_FLOAT:
1587         {
1588                 FuncRequest cmd = ev;
1589                 cmd.setView(bv_);
1590                 Inset * inset = createInset(cmd);
1591                 if (inset) {
1592                         bool gotsel = false;
1593
1594                         if (bv_->getLyXText()->selection.set()) {
1595                                 bv_->getLyXText()->cutSelection(bv_, true, false);
1596                                 gotsel = true;
1597                         }
1598
1599                         if (insertInset(inset)) {
1600                                 inset->edit(bv_);
1601                                 if (gotsel)
1602                                         owner_->dispatch(FuncRequest(LFUN_PASTESELECTION));
1603                         }
1604                         else
1605                                 delete inset;
1606                 }
1607                 break;
1608         }
1609
1610         case LFUN_INSET_CAPTION:
1611         {
1612                 // Do we have a locking inset...
1613                 if (bv_->theLockingInset()) {
1614                         lyxerr << "Locking inset code: "
1615                                << static_cast<int>(bv_->theLockingInset()->lyxCode());
1616                         InsetCaption * new_inset =
1617                                 new InsetCaption(buffer_->params);
1618                         new_inset->setOwner(bv_->theLockingInset());
1619                         new_inset->setAutoBreakRows(true);
1620                         new_inset->setDrawFrame(0, InsetText::LOCKED);
1621                         new_inset->setFrameColor(0, LColor::captionframe);
1622                         if (insertInset(new_inset))
1623                                 new_inset->edit(bv_);
1624                         else
1625                                 delete new_inset;
1626                 }
1627         }
1628         break;
1629
1630         case LFUN_TABULAR_INSERT:
1631         {
1632                 if (ev.argument.empty()) {
1633                         owner_->getDialogs().showTabularCreate();
1634                         break;
1635                 }
1636
1637                 int r = 2;
1638                 int c = 2;
1639                 ::sscanf(ev.argument.c_str(),"%d%d", &r, &c);
1640                 InsetTabular * new_inset =
1641                         new InsetTabular(*buffer_, r, c);
1642                 bool const rtl =
1643                         bv_->getLyXText()->real_current_font.isRightToLeft();
1644                 if (!open_new_inset(new_inset, rtl))
1645                         delete new_inset;
1646         }
1647         break;
1648
1649
1650         // --- accented characters ---------------------------
1651
1652         case LFUN_UMLAUT:
1653         case LFUN_CIRCUMFLEX:
1654         case LFUN_GRAVE:
1655         case LFUN_ACUTE:
1656         case LFUN_TILDE:
1657         case LFUN_CEDILLA:
1658         case LFUN_MACRON:
1659         case LFUN_DOT:
1660         case LFUN_UNDERDOT:
1661         case LFUN_UNDERBAR:
1662         case LFUN_CARON:
1663         case LFUN_SPECIAL_CARON:
1664         case LFUN_BREVE:
1665         case LFUN_TIE:
1666         case LFUN_HUNG_UMLAUT:
1667         case LFUN_CIRCLE:
1668         case LFUN_OGONEK:
1669                 if (ev.argument.empty()) {
1670                         // As always...
1671                         owner_->getLyXFunc().handleKeyFunc(ev.action);
1672                 } else {
1673                         owner_->getLyXFunc().handleKeyFunc(ev.action);
1674                         owner_->getIntl().getTransManager()
1675                                 .TranslateAndInsert(ev.argument[0], bv_->getLyXText());
1676                         update(bv_->getLyXText(),
1677                                BufferView::SELECT
1678                                | BufferView::FITCUR
1679                                | BufferView::CHANGE);
1680                 }
1681                 break;
1682
1683         case LFUN_MATH_MACRO:
1684         case LFUN_MATH_DELIM:
1685         case LFUN_INSERT_MATRIX:
1686         case LFUN_INSERT_MATH:
1687         case LFUN_MATH_IMPORT_SELECTION: // Imports LaTeX from the X selection
1688         case LFUN_MATH_DISPLAY:          // Open or create a displayed math inset
1689         case LFUN_MATH_MODE:             // Open or create an inlined math inset
1690         case LFUN_GREEK:                 // Insert a single greek letter
1691                 mathDispatch(FuncRequest(bv_, ev.action, ev.argument));
1692                 break;
1693
1694         case LFUN_CITATION_INSERT:
1695         {
1696                 InsetCommandParams p;
1697                 p.setFromString(ev.argument);
1698
1699                 InsetCitation * inset = new InsetCitation(p);
1700                 if (!insertInset(inset))
1701                         delete inset;
1702                 else
1703                         updateInset(inset, true);
1704         }
1705         break;
1706
1707         case LFUN_INSERT_BIBTEX:
1708         {
1709                 // ale970405+lasgoutt970425
1710                 // The argument can be up to two tokens separated
1711                 // by a space. The first one is the bibstyle.
1712                 string const db = token(ev.argument, ' ', 0);
1713                 string bibstyle = token(ev.argument, ' ', 1);
1714                 if (bibstyle.empty())
1715                         bibstyle = "plain";
1716
1717                 InsetCommandParams p("BibTeX", db, bibstyle);
1718                 InsetBibtex * inset = new InsetBibtex(p);
1719
1720                 if (insertInset(inset)) {
1721                         if (ev.argument.empty())
1722                                 inset->edit(bv_);
1723                 } else
1724                         delete inset;
1725         }
1726         break;
1727
1728         // BibTeX data bases
1729         case LFUN_BIBDB_ADD:
1730         {
1731                 InsetBibtex * inset =
1732                         static_cast<InsetBibtex*>(getInsetByCode(Inset::BIBTEX_CODE));
1733                 if (inset) {
1734                         inset->addDatabase(ev.argument);
1735                 }
1736         }
1737         break;
1738
1739         case LFUN_BIBDB_DEL:
1740         {
1741                 InsetBibtex * inset =
1742                         static_cast<InsetBibtex*>(getInsetByCode(Inset::BIBTEX_CODE));
1743                 if (inset) {
1744                         inset->delDatabase(ev.argument);
1745                 }
1746         }
1747         break;
1748
1749         case LFUN_BIBTEX_STYLE:
1750         {
1751                 InsetBibtex * inset =
1752                         static_cast<InsetBibtex*>(getInsetByCode(Inset::BIBTEX_CODE));
1753                 if (inset) {
1754                         inset->setOptions(ev.argument);
1755                 }
1756         }
1757         break;
1758
1759         case LFUN_INDEX_INSERT:
1760         {
1761                 string entry = ev.argument;
1762                 if (entry.empty())
1763                         entry = bv_->getLyXText()->getStringToIndex(bv_);
1764
1765                 if (entry.empty()) {
1766                         owner_->getDialogs().createIndex();
1767                         break;
1768                 }
1769
1770                 InsetIndex * inset = new InsetIndex(InsetCommandParams("index", entry));
1771
1772                 if (!insertInset(inset)) {
1773                         delete inset;
1774                 } else {
1775                         updateInset(inset, true);
1776                 }
1777         }
1778         break;
1779
1780         case LFUN_INDEX_PRINT:
1781         {
1782                 InsetCommandParams p("printindex");
1783                 Inset * inset = new InsetPrintIndex(p);
1784                 if (!insertInset(inset, tclass.defaultLayoutName()))
1785                         delete inset;
1786         }
1787         break;
1788
1789         case LFUN_PARENTINSERT:
1790         {
1791                 InsetCommandParams p("lyxparent", ev.argument);
1792                 Inset * inset = new InsetParent(p, *buffer_);
1793                 if (!insertInset(inset, tclass.defaultLayoutName()))
1794                         delete inset;
1795         }
1796
1797         break;
1798
1799         case LFUN_CHILD_INSERT:
1800         {
1801                 InsetInclude::Params p;
1802                 p.cparams.setFromString(ev.argument);
1803                 p.masterFilename_ = buffer_->fileName();
1804
1805                 InsetInclude * inset = new InsetInclude(p);
1806                 if (!insertInset(inset))
1807                         delete inset;
1808                 else {
1809                         updateInset(inset, true);
1810                         bv_->owner()->getDialogs().showInclude(inset);
1811                 }
1812         }
1813         break;
1814
1815         case LFUN_FLOAT_LIST:
1816                 if (floatList.typeExist(ev.argument)) {
1817                         Inset * inset = new InsetFloatList(ev.argument);
1818                         if (!insertInset(inset, tclass.defaultLayoutName()))
1819                                 delete inset;
1820                 } else {
1821                         lyxerr << "Non-existent float type: "
1822                                << ev.argument << endl;
1823                 }
1824                 break;
1825
1826         case LFUN_THESAURUS_ENTRY:
1827         {
1828                 string arg = ev.argument;
1829
1830                 if (arg.empty()) {
1831                         arg = bv_->getLyXText()->selectionAsString(buffer_,
1832                                                                    false);
1833
1834                         // FIXME
1835                         if (arg.size() > 100 || arg.empty()) {
1836                                 // Get word or selection
1837                                 bv_->getLyXText()->selectWordWhenUnderCursor(bv_, LyXText::WHOLE_WORD);
1838                                 arg = bv_->getLyXText()->selectionAsString(buffer_, false);
1839                                 // FIXME: where is getLyXText()->unselect(bv_) ?
1840                         }
1841                 }
1842
1843                 bv_->owner()->getDialogs().showThesaurus(arg);
1844         }
1845                 break;
1846
1847         case LFUN_UNKNOWN_ACTION:
1848                 ev.errorMessage(N_("Unknown function!"));
1849                 break;
1850
1851         default:
1852                 FuncRequest cmd = ev;
1853                 cmd.setView(bv_);
1854                 return bv_->getLyXText()->dispatch(cmd);
1855         } // end of switch
1856
1857         return true;
1858 }
1859
1860
1861 // Open and lock an updatable inset
1862 bool BufferView::Pimpl::open_new_inset(UpdatableInset * new_inset, bool behind)
1863 {
1864         LyXText * lt = bv_->getLyXText();
1865
1866         beforeChange(lt);
1867         finishUndo();
1868         if (!insertInset(new_inset)) {
1869                 delete new_inset;
1870                 return false;
1871         }
1872         new_inset->edit(bv_, !behind);
1873         return true;
1874 }
1875
1876
1877 bool BufferView::Pimpl::insertInset(Inset * inset, string const & lout)
1878 {
1879         // if we are in a locking inset we should try to insert the
1880         // inset there otherwise this is a illegal function now
1881         if (bv_->theLockingInset()) {
1882                 if (bv_->theLockingInset()->insetAllowed(inset))
1883                     return bv_->theLockingInset()->insertInset(bv_, inset);
1884                 return false;
1885         }
1886
1887         // not quite sure if we want this...
1888         setCursorParUndo(bv_);
1889         freezeUndo();
1890
1891         beforeChange(bv_->text);
1892         if (!lout.empty()) {
1893                 update(bv_->text, BufferView::SELECT|BufferView::FITCUR);
1894                 bv_->text->breakParagraph(bv_);
1895                 update(bv_->text, BufferView::SELECT|BufferView::FITCUR|BufferView::CHANGE);
1896
1897                 if (!bv_->text->cursor.par()->empty()) {
1898                         bv_->text->cursorLeft(bv_);
1899
1900                         bv_->text->breakParagraph(bv_);
1901                         update(bv_->text, BufferView::SELECT|BufferView::FITCUR|BufferView::CHANGE);
1902                 }
1903
1904                 string lres = lout;
1905                 LyXTextClass const & tclass =
1906                         buffer_->params.getLyXTextClass();
1907                 bool hasLayout = tclass.hasLayout(lres);
1908                 string lay = tclass.defaultLayoutName();
1909
1910                 if (hasLayout != false) {
1911                         // layout found
1912                         lay = lres;
1913                 } else {
1914                         // layout not fount using default
1915                         lay = tclass.defaultLayoutName();
1916                 }
1917
1918                 bv_->text->setLayout(bv_, lay);
1919
1920                 bv_->text->setParagraph(bv_, 0, 0,
1921                                    0, 0,
1922                                    VSpace(VSpace::NONE), VSpace(VSpace::NONE),
1923                                    Spacing(),
1924                                    LYX_ALIGN_LAYOUT,
1925                                    string(),
1926                                    0);
1927                 update(bv_->text, BufferView::SELECT|BufferView::FITCUR|BufferView::CHANGE);
1928         }
1929
1930         bv_->text->insertInset(bv_, inset);
1931         update(bv_->text, BufferView::SELECT|BufferView::FITCUR|BufferView::CHANGE);
1932
1933         unFreezeUndo();
1934         return true;
1935 }
1936
1937
1938 void BufferView::Pimpl::updateInset(Inset * inset, bool mark_dirty)
1939 {
1940         if (!inset || !available())
1941                 return;
1942
1943         // first check for locking insets
1944         if (bv_->theLockingInset()) {
1945                 if (bv_->theLockingInset() == inset) {
1946                         if (bv_->text->updateInset(bv_, inset)) {
1947                                 update();
1948                                 if (mark_dirty) {
1949                                         buffer_->markDirty();
1950                                 }
1951                                 updateScrollbar();
1952                                 return;
1953                         }
1954                 } else if (bv_->theLockingInset()->updateInsetInInset(bv_, inset)) {
1955                         if (bv_->text->updateInset(bv_,  bv_->theLockingInset())) {
1956                                 update();
1957                                 if (mark_dirty) {
1958                                         buffer_->markDirty();
1959                                 }
1960                                 updateScrollbar();
1961                                 return;
1962                         }
1963                 }
1964         }
1965
1966         // then check if the inset is a top_level inset (has no owner)
1967         // if yes do the update as always otherwise we have to update the
1968         // toplevel inset where this inset is inside
1969         Inset * tl_inset = inset;
1970         while (tl_inset->owner())
1971                 tl_inset = tl_inset->owner();
1972         hideCursor();
1973         if (tl_inset == inset) {
1974                 update(bv_->text, BufferView::UPDATE);
1975                 if (bv_->text->updateInset(bv_, inset)) {
1976                         if (mark_dirty) {
1977                                 update(bv_->text,
1978                                        BufferView::SELECT
1979                                        | BufferView::FITCUR
1980                                        | BufferView::CHANGE);
1981                         } else {
1982                                 update(bv_->text, SELECT);
1983                         }
1984                         return;
1985                 }
1986         } else if (static_cast<UpdatableInset *>(tl_inset)
1987                            ->updateInsetInInset(bv_, inset))
1988         {
1989                 if (bv_->text->updateInset(bv_, tl_inset)) {
1990                         update();
1991                         updateScrollbar();
1992                 }
1993         }
1994 }