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