]> git.lyx.org Git - lyx.git/blob - src/BufferView_pimpl.C
another fix
[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 "frontends/mouse_state.h"
24 #include "lyxtext.h"
25 #include "lyxrow.h"
26 #include "paragraph.h"
27 #include "frontends/LyXView.h"
28 #include "commandtags.h"
29 #include "lyxfunc.h"
30 #include "debug.h"
31 #include "bufferview_funcs.h"
32 #include "TextCache.h"
33 #include "bufferlist.h"
34 #include "lyxrc.h"
35 #include "intl.h"
36 // added for Dispatch functions
37 #include "lyx_cb.h"
38 #include "lyx_main.h"
39 #include "FloatList.h"
40 #include "gettext.h"
41 #include "ParagraphParameters.h"
42 #include "undo_funcs.h"
43 #include "funcrequest.h"
44
45 #include "insets/insetbib.h"
46 #include "insets/insettext.h"
47 #include "insets/insetlatexaccent.h"
48 #include "insets/insettoc.h"
49 #include "insets/insetindex.h"
50 #include "insets/insetref.h"
51 #include "insets/insetinclude.h"
52 #include "insets/insetcite.h"
53 #include "insets/insetgraphics.h"
54 #include "insets/insetmarginal.h"
55 #include "insets/insetfloatlist.h"
56
57 #include "mathed/formulabase.h"
58
59 #include "graphics/Previews.h"
60
61 #include "support/LAssert.h"
62 #include "support/lstrings.h"
63 #include "support/filetools.h"
64
65 #include <boost/bind.hpp>
66 #include <boost/signals/connection.hpp>
67
68 #include <unistd.h>
69 #include <sys/wait.h>
70
71
72 using std::vector;
73 using std::find_if;
74 using std::find;
75 using std::pair;
76 using std::endl;
77 using std::make_pair;
78 using std::min;
79
80 using lyx::pos_type;
81
82 extern BufferList bufferlist;
83
84
85 namespace {
86
87 unsigned int const saved_positions_num = 20;
88
89 // All the below connection objects are needed because of a bug in some
90 // versions of GCC (<=2.96 are on the suspects list.) By having and assigning
91 // to these connections we avoid a segfault upon startup, and also at exit.
92 // (Lgb)
93
94 boost::signals::connection dispatchcon;
95 boost::signals::connection timecon;
96 boost::signals::connection doccon;
97 boost::signals::connection resizecon;
98 boost::signals::connection kpresscon;
99 boost::signals::connection selectioncon;
100 boost::signals::connection lostcon;
101
102
103 } // anon namespace
104
105
106 BufferView::Pimpl::Pimpl(BufferView * bv, LyXView * owner,
107              int xpos, int ypos, int width, int height)
108         : bv_(bv), owner_(owner), buffer_(0), cursor_timeout(400),
109           using_xterm_cursor(false)
110 {
111         workarea_.reset(WorkAreaFactory::create(xpos, ypos, width, height));
112         screen_.reset(LyXScreenFactory::create(workarea()));
113
114         // Setup the signals
115         doccon = workarea().scrollDocView
116                 .connect(boost::bind(&BufferView::Pimpl::scrollDocView, this, _1));
117         resizecon = workarea().workAreaResize
118                 .connect(boost::bind(&BufferView::Pimpl::workAreaResize, this));
119         dispatchcon = workarea().dispatch
120                 .connect(boost::bind(&BufferView::Pimpl::dispatch, this, _1));
121         kpresscon = workarea().workAreaKeyPress
122                 .connect(boost::bind(&BufferView::Pimpl::workAreaKeyPress, this, _1, _2));
123         selectioncon = workarea().selectionRequested
124                 .connect(boost::bind(&BufferView::Pimpl::selectionRequested, this));
125         lostcon = workarea().selectionLost
126                 .connect(boost::bind(&BufferView::Pimpl::selectionLost, this));
127
128         timecon = cursor_timeout.timeout
129                 .connect(boost::bind(&BufferView::Pimpl::cursorToggle, this));
130         cursor_timeout.start();
131         saved_positions.resize(saved_positions_num);
132 }
133
134
135 WorkArea & BufferView::Pimpl::workarea() const
136 {
137         return *workarea_.get();
138 }
139
140
141 LyXScreen & BufferView::Pimpl::screen() const
142 {
143         return *screen_.get();
144 }
145
146
147 Painter & BufferView::Pimpl::painter() const
148 {
149         return workarea().getPainter();
150 }
151
152
153 void BufferView::Pimpl::buffer(Buffer * b)
154 {
155         lyxerr[Debug::INFO] << "Setting buffer in BufferView ("
156                             << b << ")" << endl;
157         if (buffer_) {
158                 buffer_->delUser(bv_);
159
160                 // Put the old text into the TextCache, but
161                 // only if the buffer is still loaded.
162                 // Also set the owner of the test to 0
163                 //              bv_->text->owner(0);
164                 textcache.add(buffer_, workarea().workWidth(), bv_->text);
165                 if (lyxerr.debugging())
166                         textcache.show(lyxerr, "BufferView::buffer");
167
168                 bv_->text = 0;
169         }
170
171         // set current buffer
172         buffer_ = b;
173
174         if (bufferlist.getState() == BufferList::CLOSING) return;
175
176         // if we are closing the buffer, use the first buffer as current
177         if (!buffer_) {
178                 buffer_ = bufferlist.first();
179         }
180
181         if (buffer_) {
182                 lyxerr[Debug::INFO] << "Buffer addr: " << buffer_ << endl;
183                 buffer_->addUser(bv_);
184
185                 // If we don't have a text object for this, we make one
186                 if (bv_->text == 0) {
187                         resizeCurrentBuffer();
188                 }
189
190                 // FIXME: needed when ?
191                 bv_->text->first_y =
192                         screen().topCursorVisible(bv_->text->cursor, bv_->text->first_y);
193
194                 // Similarly, buffer-dependent dialogs should be updated or
195                 // hidden. This should go here because some dialogs (eg ToC)
196                 // require bv_->text.
197                 owner_->getDialogs().updateBufferDependent(true);
198         } else {
199                 lyxerr[Debug::INFO] << "  No Buffer!" << endl;
200                 owner_->getDialogs().hideBufferDependent();
201
202                 // Also remove all remaining text's from the testcache.
203                 // (there should not be any!) (if there is any it is a
204                 // bug!)
205                 if (lyxerr.debugging())
206                         textcache.show(lyxerr, "buffer delete all");
207                 textcache.clear();
208         }
209
210         repaint();
211         updateScrollbar();
212         owner_->updateMenubar();
213         owner_->updateToolbar();
214         owner_->updateLayoutChoice();
215         owner_->updateWindowTitle();
216
217         if (grfx::Previews::activated() && buffer_)
218                 grfx::Previews::get().generateBufferPreviews(*buffer_);
219 }
220
221
222 bool BufferView::Pimpl::fitCursor()
223 {
224         bool ret;
225
226         if (bv_->theLockingInset()) {
227                 bv_->theLockingInset()->fitInsetCursor(bv_);
228                 ret = true;
229         } else {
230                 ret = screen().fitCursor(bv_->text, bv_);
231         }
232
233         bv_->owner()->getDialogs().updateParagraph();
234         if (ret)
235                 updateScrollbar();
236         return ret;
237 }
238
239
240 void BufferView::Pimpl::redoCurrentBuffer()
241 {
242         lyxerr[Debug::INFO] << "BufferView::redoCurrentBuffer" << endl;
243         if (buffer_ && bv_->text) {
244                 resizeCurrentBuffer();
245                 updateScrollbar();
246                 owner_->updateLayoutChoice();
247                 repaint();
248         }
249 }
250
251
252 int BufferView::Pimpl::resizeCurrentBuffer()
253 {
254         lyxerr[Debug::INFO] << "resizeCurrentBuffer" << endl;
255
256         Paragraph * par = 0;
257         Paragraph * selstartpar = 0;
258         Paragraph * selendpar = 0;
259         UpdatableInset * the_locking_inset = 0;
260
261         pos_type pos = 0;
262         pos_type selstartpos = 0;
263         pos_type selendpos = 0;
264         bool selection = false;
265         bool mark_set  = false;
266
267         owner_->prohibitInput();
268
269         owner_->message(_("Formatting document..."));
270
271         if (bv_->text) {
272                 par = bv_->text->cursor.par();
273                 pos = bv_->text->cursor.pos();
274                 selstartpar = bv_->text->selection.start.par();
275                 selstartpos = bv_->text->selection.start.pos();
276                 selendpar = bv_->text->selection.end.par();
277                 selendpos = bv_->text->selection.end.pos();
278                 selection = bv_->text->selection.set();
279                 mark_set = bv_->text->selection.mark();
280                 the_locking_inset = bv_->theLockingInset();
281                 buffer_->resizeInsets(bv_);
282                 // I don't think the delete and new are necessary here we just could
283                 // call only init! (Jug 20020419)
284                 delete bv_->text;
285                 bv_->text = new LyXText(bv_);
286                 bv_->text->init(bv_);
287         } else {
288                 // See if we have a text in TextCache that fits
289                 // the new buffer_ with the correct width.
290                 bv_->text = textcache.findFit(buffer_, workarea().workWidth());
291                 if (bv_->text) {
292                         if (lyxerr.debugging()) {
293                                 lyxerr << "Found a LyXText that fits:\n";
294                                 textcache.show(lyxerr, make_pair(buffer_, make_pair(workarea().workWidth(), bv_->text)));
295                         }
296                         // Set the owner of the newly found text
297                         //      bv_->text->owner(bv_);
298                         if (lyxerr.debugging())
299                                 textcache.show(lyxerr, "resizeCurrentBuffer");
300                 } else {
301                         bv_->text = new LyXText(bv_);
302                         bv_->text->init(bv_);
303                         //buffer_->resizeInsets(bv_);
304                 }
305         }
306
307         if (par) {
308                 bv_->text->selection.set(true);
309                 // At this point just to avoid the Delete-Empty-Paragraph-
310                 // Mechanism when setting the cursor.
311                 bv_->text->selection.mark(mark_set);
312                 if (selection) {
313                         bv_->text->setCursor(bv_, selstartpar, selstartpos);
314                         bv_->text->selection.cursor = bv_->text->cursor;
315                         bv_->text->setCursor(bv_, selendpar, selendpos);
316                         bv_->text->setSelection(bv_);
317                         bv_->text->setCursor(bv_, par, pos);
318                 } else {
319                         bv_->text->setCursor(bv_, par, pos);
320                         bv_->text->selection.cursor = bv_->text->cursor;
321                         bv_->text->selection.set(false);
322                 }
323                 // remake the inset locking
324                 bv_->theLockingInset(the_locking_inset);
325         }
326
327         bv_->text->first_y = screen().topCursorVisible(bv_->text->cursor, bv_->text->first_y);
328
329         switchKeyMap();
330         owner_->allowInput();
331
332         updateScrollbar();
333
334         return 0;
335 }
336
337
338 void BufferView::Pimpl::repaint()
339 {
340         // Regenerate the screen.
341         screen().redraw(bv_->text, bv_);
342 }
343
344
345 void BufferView::Pimpl::updateScrollbar()
346 {
347         if (!bv_->text) {
348                 lyxerr[Debug::GUI] << "no text in updateScrollbar" << endl;
349                 workarea().setScrollbarParams(0, 0, 0);
350                 return;
351         }
352
353         LyXText const & t = *bv_->text;
354
355         lyxerr[Debug::GUI] << "Updating scrollbar: h " << t.height << ", first_y "
356                 << t.first_y << ", default height " << t.defaultHeight() << endl;
357
358         workarea().setScrollbarParams(t.height, t.first_y, t.defaultHeight());
359 }
360
361
362 void BufferView::Pimpl::scrollDocView(int value)
363 {
364         lyxerr[Debug::GUI] << "scrollDocView of " << value << endl;
365
366         if (!buffer_)
367                 return;
368
369         screen().draw(bv_->text, bv_, value);
370
371         if (!lyxrc.cursor_follows_scrollbar)
372                 return;
373
374         LyXText * vbt = bv_->text;
375
376         int const height = vbt->defaultHeight();
377         int const first = static_cast<int>((bv_->text->first_y + height));
378         int const last = static_cast<int>((bv_->text->first_y + workarea().workHeight() - height));
379
380         if (vbt->cursor.y() < first)
381                 vbt->setCursorFromCoordinates(bv_, 0, first);
382         else if (vbt->cursor.y() > last)
383                 vbt->setCursorFromCoordinates(bv_, 0, last);
384 }
385
386
387 int BufferView::Pimpl::scroll(long time)
388 {
389         if (!buffer_)
390                 return 0;
391
392         LyXText const * t = bv_->text;
393
394         double const diff = t->defaultHeight()
395                 + double(time) * double(time) * 0.125;
396
397         scrollDocView(int(diff));
398         workarea().setScrollbarParams(t->height, t->first_y, t->defaultHeight());
399         return 0;
400 }
401
402
403 void BufferView::Pimpl::workAreaKeyPress(LyXKeySymPtr key,
404                                          key_modifier::state state)
405 {
406         bv_->owner()->getLyXFunc().processKeySym(key, state);
407 }
408
409
410 void BufferView::Pimpl::selectionRequested()
411 {
412         static string sel;
413
414         if (!available())
415                 return;
416
417         LyXText * text = bv_->getLyXText();
418
419         if (text->selection.set() &&
420                 (!bv_->text->xsel_cache.set() ||
421                  text->selection.start != bv_->text->xsel_cache.start ||
422                  text->selection.end != bv_->text->xsel_cache.end))
423         {
424                 bv_->text->xsel_cache = text->selection;
425                 sel = text->selectionAsString(bv_->buffer(), false);
426         } else if (!text->selection.set()) {
427                 sel = string();
428                 bv_->text->xsel_cache.set(false);
429         }
430         if (!sel.empty()) {
431                 workarea().putClipboard(sel);
432         }
433 }
434
435
436 void BufferView::Pimpl::selectionLost()
437 {
438         if (available()) {
439                 hideCursor();
440                 toggleSelection();
441                 bv_->getLyXText()->clearSelection();
442                 showCursor();
443                 bv_->text->xsel_cache.set(false);
444         }
445 }
446
447
448 void BufferView::Pimpl::workAreaResize()
449 {
450         static int work_area_width;
451         static int work_area_height;
452
453         bool const widthChange = workarea().workWidth() != work_area_width;
454         bool const heightChange = workarea().workHeight() != work_area_height;
455
456         // update from work area
457         work_area_width = workarea().workWidth();
458         work_area_height = workarea().workHeight();
459
460         if (buffer_ != 0) {
461                 if (widthChange) {
462                         // The visible LyXView need a resize
463                         resizeCurrentBuffer();
464
465                         // Remove all texts from the textcache
466                         // This is not _really_ what we want to do. What
467                         // we really want to do is to delete in textcache
468                         // that does not have a BufferView with matching
469                         // width, but as long as we have only one BufferView
470                         // deleting all gives the same result.
471                         if (lyxerr.debugging())
472                                 textcache.show(lyxerr, "Expose delete all");
473                         textcache.clear();
474                         // FIXME: this is already done in resizeCurrentBuffer() ??
475                         buffer_->resizeInsets(bv_);
476                 } else if (heightChange) {
477                         // fitCursor() ensures we don't jump back
478                         // to the start of the document on vertical
479                         // resize
480                         fitCursor();
481                 }
482         }
483
484         if (widthChange || heightChange) {
485                 repaint();
486         }
487
488         // always make sure that the scrollbar is sane.
489         updateScrollbar();
490         owner_->updateLayoutChoice();
491         return;
492 }
493
494
495 void BufferView::Pimpl::update()
496 {
497         if (!bv_->theLockingInset() || !bv_->theLockingInset()->nodraw()) {
498                 LyXText::text_status st = bv_->text->status();
499                 screen().update(bv_->text, bv_);
500                 bool fitc = false;
501                 while (bv_->text->status() == LyXText::CHANGED_IN_DRAW) {
502                         bv_->text->fullRebreak(bv_);
503                         st = LyXText::NEED_MORE_REFRESH;
504                         bv_->text->setCursor(bv_, bv_->text->cursor.par(),
505                                              bv_->text->cursor.pos());
506                         if (bv_->text->selection.set()) {
507                                 bv_->text->setCursor(bv_, bv_->text->selection.start,
508                                                      bv_->text->selection.start.par(),
509                                                      bv_->text->selection.start.pos());
510                                 bv_->text->setCursor(bv_, bv_->text->selection.end,
511                                                      bv_->text->selection.end.par(),
512                                                      bv_->text->selection.end.pos());
513                         }
514                         fitc = true;
515                         bv_->text->status(bv_, st);
516                         screen().update(bv_->text, bv_);
517                 }
518                 // do this here instead of in the screen::update because of
519                 // the above loop!
520                 bv_->text->status(bv_, LyXText::UNCHANGED);
521                 if (fitc)
522                         fitCursor();
523         }
524 }
525
526 // Values used when calling update:
527 // -3 - update
528 // -2 - update, move sel_cursor if selection, fitcursor
529 // -1 - update, move sel_cursor if selection, fitcursor, mark dirty
530 //  0 - update, move sel_cursor if selection, fitcursor
531 //  1 - update, move sel_cursor if selection, fitcursor, mark dirty
532 //  3 - update, move sel_cursor if selection
533 //
534 // update -
535 // a simple redraw of the parts that need refresh
536 //
537 // move sel_cursor if selection -
538 // the text's sel_cursor is moved if there is selection is progress
539 //
540 // fitcursor -
541 // fitCursor() is called and the scrollbar updated
542 //
543 // mark dirty -
544 // the buffer is marked dirty.
545 //
546 // enum {
547 //       UPDATE = 0,
548 //       SELECT = 1,
549 //       FITCUR = 2,
550 //       CHANGE = 4
551 // };
552 //
553 // UPDATE_ONLY = UPDATE;
554 // UPDATE_SELECT = UPDATE | SELECT;
555 // UPDATE_SELECT_MOVE = UPDATE | SELECT | FITCUR;
556 // UPDATE_SELECT_MOVE_AFTER_CHANGE = UPDATE | SELECT | FITCUR | CHANGE;
557 //
558 // update(-3) -> update(0)         -> update(0) -> update(UPDATE)
559 // update(-2) -> update(1 + 2)     -> update(3) -> update(SELECT|FITCUR)
560 // update(-1) -> update(1 + 2 + 4) -> update(7) -> update(SELECT|FITCUR|CHANGE)
561 // update(1)  -> update(1 + 2 + 4) -> update(7) -> update(SELECT|FITCUR|CHANGE)
562 // update(3)  -> update(1)         -> update(1) -> update(SELECT)
563
564 void BufferView::Pimpl::update(LyXText * text, BufferView::UpdateCodes f)
565 {
566         owner_->updateLayoutChoice();
567
568         if (!text->selection.set() && (f & SELECT)) {
569                 text->selection.cursor = text->cursor;
570         }
571
572         text->fullRebreak(bv_);
573
574         if (text->inset_owner) {
575                 text->inset_owner->setUpdateStatus(bv_, InsetText::NONE);
576                 updateInset(text->inset_owner, false);
577         } else {
578                 update();
579         }
580
581         if ((f & FITCUR)) {
582                 fitCursor();
583         }
584
585         if ((f & CHANGE)) {
586                 buffer_->markDirty();
587         }
588 }
589
590
591 // Callback for cursor timer
592 void BufferView::Pimpl::cursorToggle()
593 {
594         if (!buffer_) {
595                 cursor_timeout.restart();
596                 return;
597         }
598
599         /* FIXME */
600         extern void reapSpellchecker(void);
601         reapSpellchecker();
602
603         if (!bv_->theLockingInset()) {
604                 screen().cursorToggle(bv_);
605         } else {
606                 bv_->theLockingInset()->toggleInsetCursor(bv_);
607         }
608
609         cursor_timeout.restart();
610 }
611
612
613 bool BufferView::Pimpl::available() const
614 {
615         if (buffer_ && bv_->text)
616                 return true;
617         return false;
618 }
619
620
621 void BufferView::Pimpl::beforeChange(LyXText * text)
622 {
623         toggleSelection();
624         text->clearSelection();
625 }
626
627
628 void BufferView::Pimpl::savePosition(unsigned int i)
629 {
630         if (i >= saved_positions_num)
631                 return;
632         saved_positions[i] = Position(buffer_->fileName(),
633                                       bv_->text->cursor.par()->id(),
634                                       bv_->text->cursor.pos());
635         if (i > 0) {
636                 ostringstream str;
637                 str << _("Saved bookmark") << ' ' << i;
638                 owner_->message(str.str().c_str());
639         }
640 }
641
642
643 void BufferView::Pimpl::restorePosition(unsigned int i)
644 {
645         if (i >= saved_positions_num)
646                 return;
647
648         string const fname = saved_positions[i].filename;
649
650         beforeChange(bv_->text);
651
652         if (fname != buffer_->fileName()) {
653                 Buffer * b = bufferlist.exists(fname) ?
654                         bufferlist.getBuffer(fname) :
655                         bufferlist.loadLyXFile(fname); // don't ask, just load it
656                 if (b != 0) buffer(b);
657         }
658
659         Paragraph * par = buffer_->getParFromID(saved_positions[i].par_id);
660         if (!par)
661                 return;
662
663         bv_->text->setCursor(bv_, par,
664                              min(par->size(), saved_positions[i].par_pos));
665
666         update(bv_->text, BufferView::SELECT | BufferView::FITCUR);
667         if (i > 0) {
668                 ostringstream str;
669                 str << _("Moved to bookmark") << ' ' << i;
670                 owner_->message(str.str().c_str());
671         }
672 }
673
674
675 bool BufferView::Pimpl::isSavedPosition(unsigned int i)
676 {
677         if (i >= saved_positions_num)
678                 return false;
679
680         return !saved_positions[i].filename.empty();
681 }
682
683
684 void BufferView::Pimpl::switchKeyMap()
685 {
686         if (!lyxrc.rtl_support)
687                 return;
688
689         LyXText * text = bv_->getLyXText();
690         if (text->real_current_font.isRightToLeft()
691             && !(bv_->theLockingInset()
692                  && bv_->theLockingInset()->lyxCode() == Inset::ERT_CODE))
693         {
694                 if (owner_->getIntl().keymap == Intl::PRIMARY)
695                         owner_->getIntl().KeyMapSec();
696         } else {
697                 if (owner_->getIntl().keymap == Intl::SECONDARY)
698                         owner_->getIntl().KeyMapPrim();
699         }
700 }
701
702
703 void BufferView::Pimpl::insetUnlock()
704 {
705         if (bv_->theLockingInset()) {
706                 bv_->theLockingInset()->insetUnlock(bv_);
707                 bv_->theLockingInset(0);
708                 finishUndo();
709         }
710 }
711
712
713 void BufferView::Pimpl::showCursor()
714 {
715         if (bv_->theLockingInset())
716                 bv_->theLockingInset()->showInsetCursor(bv_);
717         else
718                 screen().showCursor(bv_->text, bv_);
719 }
720
721
722 void BufferView::Pimpl::hideCursor()
723 {
724         if (!bv_->theLockingInset())
725                 screen().hideCursor();
726 }
727
728
729 void BufferView::Pimpl::toggleSelection(bool b)
730 {
731         if (bv_->theLockingInset())
732                 bv_->theLockingInset()->toggleSelection(bv_, b);
733         screen().toggleSelection(bv_->text, bv_, b);
734 }
735
736
737 void BufferView::Pimpl::toggleToggle()
738 {
739         screen().toggleToggle(bv_->text, bv_);
740 }
741
742
743 void BufferView::Pimpl::center()
744 {
745         LyXText * t = bv_->text;
746
747         beforeChange(t);
748         int const half_height = workarea().workHeight() / 2;
749         int new_y = 0;
750
751         if (t->cursor.y() > half_height) {
752                 new_y = t->cursor.y() - half_height;
753         }
754
755         // FIXME: can we do this w/o calling screen directly ?
756         // This updates first_y but means the fitCursor() call
757         // from the update(FITCUR) doesn't realise that we might
758         // have moved (e.g. from GOTOPARAGRAPH), so doesn't cause
759         // the scrollbar to be updated as it should, so we have
760         // to do it manually. Any operation that does a center()
761         // and also might have moved first_y must make sure to call
762         // updateScrollbar() currently. Never mind that this is a
763         // pretty obfuscated way of updating t->first_y
764         screen().draw(t, bv_, new_y);
765
766         update(t, BufferView::SELECT | BufferView::FITCUR);
767 }
768
769
770 void BufferView::Pimpl::stuffClipboard(string const & stuff) const
771 {
772         workarea().putClipboard(stuff);
773 }
774
775
776 /*
777  * Dispatch functions for actions which can be valid for BufferView->text
778  * and/or InsetText->text!!!
779  */
780
781
782 Inset * BufferView::Pimpl::getInsetByCode(Inset::Code code)
783 {
784 #if 0
785         LyXCursor cursor = bv_->getLyXText()->cursor;
786         Buffer::inset_iterator it =
787                 find_if(Buffer::inset_iterator(
788                         cursor.par(), cursor.pos()),
789                         buffer_->inset_iterator_end(),
790                         lyx::compare_memfun(&Inset::lyxCode, code));
791         return it != buffer_->inset_iterator_end() ? (*it) : 0;
792 #else
793         // Ok, this is a little bit too brute force but it
794         // should work for now. Better infrastructure is comming. (Lgb)
795
796         Buffer * b = bv_->buffer();
797         LyXCursor cursor = bv_->getLyXText()->cursor;
798
799         Buffer::inset_iterator beg = b->inset_iterator_begin();
800         Buffer::inset_iterator end = b->inset_iterator_end();
801
802         bool cursor_par_seen = false;
803
804         for (; beg != end; ++beg) {
805                 if (beg.getPar() == cursor.par()) {
806                         cursor_par_seen = true;
807                 }
808                 if (cursor_par_seen) {
809                         if (beg.getPar() == cursor.par()
810                             && beg.getPos() >= cursor.pos()) {
811                                 break;
812                         } else if (beg.getPar() != cursor.par()) {
813                                 break;
814                         }
815                 }
816
817         }
818         if (beg != end) {
819                 // Now find the first inset that matches code.
820                 for (; beg != end; ++beg) {
821                         if (beg->lyxCode() == code) {
822                                 return &(*beg);
823                         }
824                 }
825         }
826         return 0;
827 #endif
828 }
829
830
831 void BufferView::Pimpl::MenuInsertLyXFile(string const & filen)
832 {
833         string filename = filen;
834
835         if (filename.empty()) {
836                 // Launch a file browser
837                 string initpath = lyxrc.document_path;
838
839                 if (available()) {
840                         string const trypath = owner_->buffer()->filePath();
841                         // If directory is writeable, use this as default.
842                         if (IsDirWriteable(trypath))
843                                 initpath = trypath;
844                 }
845
846                 FileDialog fileDlg(bv_->owner(),
847                                    _("Select LyX document to insert"),
848                         LFUN_FILE_INSERT,
849                         make_pair(string(_("Documents|#o#O")),
850                                   string(lyxrc.document_path)),
851                         make_pair(string(_("Examples|#E#e")),
852                                   string(AddPath(system_lyxdir, "examples"))));
853
854                 FileDialog::Result result =
855                         fileDlg.Select(initpath,
856                                        _("*.lyx| LyX Documents (*.lyx)"));
857
858                 if (result.first == FileDialog::Later)
859                         return;
860
861                 filename = result.second;
862
863                 // check selected filename
864                 if (filename.empty()) {
865                         owner_->message(_("Canceled."));
866                         return;
867                 }
868         }
869
870         // get absolute path of file and add ".lyx" to the filename if
871         // necessary
872         filename = FileSearch(string(), filename, "lyx");
873
874         string const disp_fn(MakeDisplayPath(filename));
875
876         ostringstream s1;
877         s1 << _("Inserting document") << ' '
878            << disp_fn << " ...";
879         owner_->message(s1.str().c_str());
880         bool const res = bv_->insertLyXFile(filename);
881         if (res) {
882                 ostringstream str;
883                 str << _("Document") << ' ' << disp_fn
884                     << ' ' << _("inserted.");
885                 owner_->message(str.str().c_str());
886         } else {
887                 ostringstream str;
888                 str << _("Could not insert document") << ' '
889                     << disp_fn;
890                 owner_->message(str.str().c_str());
891         }
892 }
893
894
895 bool BufferView::Pimpl::dispatch(FuncRequest const & ev)
896 {
897         lyxerr[Debug::ACTION] << "BufferView::Pimpl::Dispatch:"
898                 << " action[" << ev.action <<"]"
899                 << " arg[" << ev.argument << "]"
900                 << " x[" << ev.x << "]"
901                 << " y[" << ev.y << "]"
902                 << " button[" << ev.button() << "]"
903                 << endl;
904
905         // e.g. Qt mouse press when no buffer
906         if (!buffer_)
907                 return false;
908  
909         LyXTextClass const & tclass = buffer_->params.getLyXTextClass();
910
911         switch (ev.action) {
912
913         case LFUN_SCROLL_INSET:
914                 // this is not handled here as this function is only active
915                 // if we have a locking_inset and that one is (or contains)
916                 // a tabular-inset
917                 break;
918
919         case LFUN_INSET_GRAPHICS:
920         {
921                 Inset * new_inset = new InsetGraphics;
922                 if (!insertInset(new_inset)) {
923                         delete new_inset;
924                 } else {
925                         // this is need because you don't use a inset->Edit()
926                         updateInset(new_inset, true);
927                         new_inset->edit(bv_);
928                 }
929                 break;
930         }
931
932         case LFUN_LAYOUT_COPY:
933                 bv_->copyEnvironment();
934                 break;
935
936         case LFUN_LAYOUT_PASTE:
937                 bv_->pasteEnvironment();
938                 switchKeyMap();
939                 break;
940
941         case LFUN_DEPTH_MIN:
942                 changeDepth(bv_, bv_->getLyXText(), -1);
943                 break;
944
945         case LFUN_DEPTH_PLUS:
946                 changeDepth(bv_, bv_->getLyXText(), 1);
947                 break;
948
949         case LFUN_FREE:
950                 owner_->getDialogs().setUserFreeFont();
951                 break;
952
953         case LFUN_FILE_INSERT:
954                 MenuInsertLyXFile(ev.argument);
955                 break;
956
957         case LFUN_FILE_INSERT_ASCII_PARA:
958                 InsertAsciiFile(bv_, ev.argument, true);
959                 break;
960
961         case LFUN_FILE_INSERT_ASCII:
962                 InsertAsciiFile(bv_, ev.argument, false);
963                 break;
964
965         case LFUN_LANGUAGE:
966                 lang(bv_, ev.argument);
967                 switchKeyMap();
968                 owner_->view_state_changed();
969                 break;
970
971         case LFUN_EMPH:
972                 emph(bv_);
973                 owner_->view_state_changed();
974                 break;
975
976         case LFUN_BOLD:
977                 bold(bv_);
978                 owner_->view_state_changed();
979                 break;
980
981         case LFUN_NOUN:
982                 noun(bv_);
983                 owner_->view_state_changed();
984                 break;
985
986         case LFUN_CODE:
987                 code(bv_);
988                 owner_->view_state_changed();
989                 break;
990
991         case LFUN_SANS:
992                 sans(bv_);
993                 owner_->view_state_changed();
994                 break;
995
996         case LFUN_ROMAN:
997                 roman(bv_);
998                 owner_->view_state_changed();
999                 break;
1000
1001         case LFUN_DEFAULT:
1002                 styleReset(bv_);
1003                 owner_->view_state_changed();
1004                 break;
1005
1006         case LFUN_UNDERLINE:
1007                 underline(bv_);
1008                 owner_->view_state_changed();
1009                 break;
1010
1011         case LFUN_FONT_SIZE:
1012                 fontSize(bv_, ev.argument);
1013                 owner_->view_state_changed();
1014                 break;
1015
1016         case LFUN_FONT_STATE:
1017                 owner_->getLyXFunc().setMessage(currentState(bv_));
1018                 break;
1019
1020         case LFUN_INSERT_LABEL:
1021                 MenuInsertLabel(bv_, ev.argument);
1022                 break;
1023
1024         case LFUN_REF_INSERT:
1025                 if (ev.argument.empty()) {
1026                         InsetCommandParams p("ref");
1027                         owner_->getDialogs().createRef(p.getAsString());
1028                 } else {
1029                         InsetCommandParams p;
1030                         p.setFromString(ev.argument);
1031
1032                         InsetRef * inset = new InsetRef(p, *buffer_);
1033                         if (!insertInset(inset))
1034                                 delete inset;
1035                         else
1036                                 updateInset(inset, true);
1037                 }
1038                 break;
1039
1040         case LFUN_BOOKMARK_SAVE:
1041                 savePosition(strToUnsignedInt(ev.argument));
1042                 break;
1043
1044         case LFUN_BOOKMARK_GOTO:
1045                 restorePosition(strToUnsignedInt(ev.argument));
1046                 break;
1047
1048         case LFUN_REF_GOTO:
1049         {
1050                 string label = ev.argument;
1051                 if (label.empty()) {
1052                         InsetRef * inset =
1053                                 static_cast<InsetRef*>(getInsetByCode(Inset::REF_CODE));
1054                         if (inset) {
1055                                 label = inset->getContents();
1056                                 savePosition(0);
1057                         }
1058                 }
1059
1060                 if (!label.empty()) {
1061                         //bv_->savePosition(0);
1062                         if (!bv_->gotoLabel(label))
1063                                 Alert::alert(_("Error"),
1064                                            _("Couldn't find this label"),
1065                                            _("in current document."));
1066                 }
1067         }
1068         break;
1069
1070         // --- accented characters ---------------------------
1071
1072         case LFUN_UMLAUT:
1073         case LFUN_CIRCUMFLEX:
1074         case LFUN_GRAVE:
1075         case LFUN_ACUTE:
1076         case LFUN_TILDE:
1077         case LFUN_CEDILLA:
1078         case LFUN_MACRON:
1079         case LFUN_DOT:
1080         case LFUN_UNDERDOT:
1081         case LFUN_UNDERBAR:
1082         case LFUN_CARON:
1083         case LFUN_SPECIAL_CARON:
1084         case LFUN_BREVE:
1085         case LFUN_TIE:
1086         case LFUN_HUNG_UMLAUT:
1087         case LFUN_CIRCLE:
1088         case LFUN_OGONEK:
1089                 if (ev.argument.empty()) {
1090                         // As always...
1091                         owner_->getLyXFunc().handleKeyFunc(ev.action);
1092                 } else {
1093                         owner_->getLyXFunc().handleKeyFunc(ev.action);
1094                         owner_->getIntl().getTransManager()
1095                                 .TranslateAndInsert(ev.argument[0], bv_->getLyXText());
1096                         update(bv_->getLyXText(),
1097                                BufferView::SELECT
1098                                | BufferView::FITCUR
1099                                | BufferView::CHANGE);
1100                 }
1101                 break;
1102
1103         case LFUN_MATH_MACRO:
1104         case LFUN_MATH_DELIM:
1105         case LFUN_INSERT_MATRIX:
1106         case LFUN_INSERT_MATH:
1107         case LFUN_MATH_IMPORT_SELECTION: // Imports LaTeX from the X selection
1108         case LFUN_MATH_DISPLAY:          // Open or create a displayed math inset
1109         case LFUN_MATH_MODE:             // Open or create an inlined math inset
1110         case LFUN_GREEK:                 // Insert a single greek letter
1111                 mathDispatch(FuncRequest(bv_, ev.action, ev.argument));
1112                 break;
1113
1114         case LFUN_CITATION_INSERT:
1115         {
1116                 InsetCommandParams p;
1117                 p.setFromString(ev.argument);
1118
1119                 InsetCitation * inset = new InsetCitation(p);
1120                 if (!insertInset(inset))
1121                         delete inset;
1122                 else
1123                         updateInset(inset, true);
1124         }
1125         break;
1126
1127         case LFUN_INSERT_BIBTEX:
1128         {
1129                 // ale970405+lasgoutt970425
1130                 // The argument can be up to two tokens separated
1131                 // by a space. The first one is the bibstyle.
1132                 string const db = token(ev.argument, ' ', 0);
1133                 string bibstyle = token(ev.argument, ' ', 1);
1134                 if (bibstyle.empty())
1135                         bibstyle = "plain";
1136
1137                 InsetCommandParams p("BibTeX", db, bibstyle);
1138                 InsetBibtex * inset = new InsetBibtex(p);
1139
1140                 if (insertInset(inset)) {
1141                         if (ev.argument.empty())
1142                                 inset->edit(bv_);
1143                 } else
1144                         delete inset;
1145         }
1146         break;
1147
1148         // BibTeX data bases
1149         case LFUN_BIBDB_ADD:
1150         {
1151                 InsetBibtex * inset =
1152                         static_cast<InsetBibtex*>(getInsetByCode(Inset::BIBTEX_CODE));
1153                 if (inset) {
1154                         inset->addDatabase(ev.argument);
1155                 }
1156         }
1157         break;
1158
1159         case LFUN_BIBDB_DEL:
1160         {
1161                 InsetBibtex * inset =
1162                         static_cast<InsetBibtex*>(getInsetByCode(Inset::BIBTEX_CODE));
1163                 if (inset)
1164                         inset->delDatabase(ev.argument);
1165         }
1166         break;
1167
1168         case LFUN_BIBTEX_STYLE:
1169         {
1170                 InsetBibtex * inset =
1171                         static_cast<InsetBibtex*>(getInsetByCode(Inset::BIBTEX_CODE));
1172                 if (inset) 
1173                         inset->setOptions(ev.argument);
1174         }
1175         break;
1176
1177         case LFUN_CHILD_INSERT:
1178         {
1179                 InsetInclude::Params p;
1180                 p.cparams.setFromString(ev.argument);
1181                 p.masterFilename_ = buffer_->fileName();
1182
1183                 InsetInclude * inset = new InsetInclude(p);
1184                 if (!insertInset(inset))
1185                         delete inset;
1186                 else {
1187                         updateInset(inset, true);
1188                         bv_->owner()->getDialogs().showInclude(inset);
1189                 }
1190         }
1191         break;
1192
1193         case LFUN_FLOAT_LIST:
1194                 if (tclass.floats().typeExist(ev.argument)) {
1195                         Inset * inset = new InsetFloatList(ev.argument);
1196                         if (!insertInset(inset, tclass.defaultLayoutName()))
1197                                 delete inset;
1198                 } else {
1199                         lyxerr << "Non-existent float type: "
1200                                << ev.argument << endl;
1201                 }
1202                 break;
1203
1204         case LFUN_THESAURUS_ENTRY:
1205         {
1206                 string arg = ev.argument;
1207
1208                 if (arg.empty()) {
1209                         arg = bv_->getLyXText()->selectionAsString(buffer_,
1210                                                                    false);
1211
1212                         // FIXME
1213                         if (arg.size() > 100 || arg.empty()) {
1214                                 // Get word or selection
1215                                 bv_->getLyXText()->selectWordWhenUnderCursor(bv_, LyXText::WHOLE_WORD);
1216                                 arg = bv_->getLyXText()->selectionAsString(buffer_, false);
1217                                 // FIXME: where is getLyXText()->unselect(bv_) ?
1218                         }
1219                 }
1220
1221                 bv_->owner()->getDialogs().showThesaurus(arg);
1222         }
1223                 break;
1224
1225         case LFUN_UNKNOWN_ACTION:
1226                 ev.errorMessage(N_("Unknown function!"));
1227                 break;
1228
1229         default:
1230                 return bv_->getLyXText()->dispatch(FuncRequest(ev, bv_));
1231         } // end of switch
1232
1233         return true;
1234 }
1235
1236
1237 bool BufferView::Pimpl::insertInset(Inset * inset, string const & lout)
1238 {
1239         // if we are in a locking inset we should try to insert the
1240         // inset there otherwise this is a illegal function now
1241         if (bv_->theLockingInset()) {
1242                 if (bv_->theLockingInset()->insetAllowed(inset))
1243                         return bv_->theLockingInset()->insertInset(bv_, inset);
1244                 return false;
1245         }
1246
1247         // not quite sure if we want this...
1248         setCursorParUndo(bv_);
1249         freezeUndo();
1250
1251         beforeChange(bv_->text);
1252         if (!lout.empty()) {
1253                 update(bv_->text, BufferView::SELECT|BufferView::FITCUR);
1254                 bv_->text->breakParagraph(bv_);
1255                 update(bv_->text, BufferView::SELECT|BufferView::FITCUR|BufferView::CHANGE);
1256
1257                 if (!bv_->text->cursor.par()->empty()) {
1258                         bv_->text->cursorLeft(bv_);
1259
1260                         bv_->text->breakParagraph(bv_);
1261                         update(bv_->text, BufferView::SELECT|BufferView::FITCUR|BufferView::CHANGE);
1262                 }
1263
1264                 string lres = lout;
1265                 LyXTextClass const & tclass =
1266                         buffer_->params.getLyXTextClass();
1267                 bool hasLayout = tclass.hasLayout(lres);
1268                 string lay = tclass.defaultLayoutName();
1269
1270                 if (hasLayout != false) {
1271                         // layout found
1272                         lay = lres;
1273                 } else {
1274                         // layout not fount using default
1275                         lay = tclass.defaultLayoutName();
1276                 }
1277
1278                 bv_->text->setLayout(bv_, lay);
1279
1280                 bv_->text->setParagraph(bv_, 0, 0,
1281                                    0, 0,
1282                                    VSpace(VSpace::NONE), VSpace(VSpace::NONE),
1283                                    Spacing(),
1284                                    LYX_ALIGN_LAYOUT,
1285                                    string(),
1286                                    0);
1287                 update(bv_->text, BufferView::SELECT|BufferView::FITCUR|BufferView::CHANGE);
1288         }
1289
1290         bv_->text->insertInset(bv_, inset);
1291         update(bv_->text, BufferView::SELECT|BufferView::FITCUR|BufferView::CHANGE);
1292
1293         unFreezeUndo();
1294         return true;
1295 }
1296
1297
1298 void BufferView::Pimpl::updateInset(Inset * inset, bool mark_dirty)
1299 {
1300         if (!inset || !available())
1301                 return;
1302
1303         // first check for locking insets
1304         if (bv_->theLockingInset()) {
1305                 if (bv_->theLockingInset() == inset) {
1306                         if (bv_->text->updateInset(bv_, inset)) {
1307                                 update();
1308                                 if (mark_dirty) {
1309                                         buffer_->markDirty();
1310                                 }
1311                                 updateScrollbar();
1312                                 return;
1313                         }
1314                 } else if (bv_->theLockingInset()->updateInsetInInset(bv_, inset)) {
1315                         if (bv_->text->updateInset(bv_,  bv_->theLockingInset())) {
1316                                 update();
1317                                 if (mark_dirty) {
1318                                         buffer_->markDirty();
1319                                 }
1320                                 updateScrollbar();
1321                                 return;
1322                         }
1323                 }
1324         }
1325
1326         // then check if the inset is a top_level inset (has no owner)
1327         // if yes do the update as always otherwise we have to update the
1328         // toplevel inset where this inset is inside
1329         Inset * tl_inset = inset;
1330         while (tl_inset->owner())
1331                 tl_inset = tl_inset->owner();
1332         hideCursor();
1333         if (tl_inset == inset) {
1334                 update(bv_->text, BufferView::UPDATE);
1335                 if (bv_->text->updateInset(bv_, inset)) {
1336                         if (mark_dirty) {
1337                                 update(bv_->text,
1338                                        BufferView::SELECT
1339                                        | BufferView::FITCUR
1340                                        | BufferView::CHANGE);
1341                         } else {
1342                                 update(bv_->text, SELECT);
1343                         }
1344                         return;
1345                 }
1346         } else if (static_cast<UpdatableInset *>(tl_inset)
1347                            ->updateInsetInInset(bv_, inset))
1348         {
1349                 if (bv_->text->updateInset(bv_, tl_inset)) {
1350                         update();
1351                         updateScrollbar();
1352                 }
1353         }
1354 }