]> git.lyx.org Git - lyx.git/blob - src/BufferView_pimpl.C
Darren's patch, reworked
[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 void BufferView::Pimpl::scroll(int lines)
388 {
389         if (!buffer_) {
390                 return;
391         }
392
393         LyXText const * t = bv_->text;
394         int const line_height = t->defaultHeight();
395
396         int const disp = lines * line_height;
397
398         // Restrict to a valid value
399         int new_first_y = std::min(t->height - 4 * line_height, disp);
400         new_first_y = std::max(0, disp);
401
402         scrollDocView(new_first_y);
403
404         // Update the scrollbar.
405         workarea().setScrollbarParams(t->height, t->first_y, t->defaultHeight());
406 }
407
408
409 void BufferView::Pimpl::workAreaKeyPress(LyXKeySymPtr key,
410                                          key_modifier::state state)
411 {
412         bv_->owner()->getLyXFunc().processKeySym(key, state);
413 }
414
415
416 void BufferView::Pimpl::selectionRequested()
417 {
418         static string sel;
419
420         if (!available())
421                 return;
422
423         LyXText * text = bv_->getLyXText();
424
425         if (text->selection.set() &&
426                 (!bv_->text->xsel_cache.set() ||
427                  text->selection.start != bv_->text->xsel_cache.start ||
428                  text->selection.end != bv_->text->xsel_cache.end))
429         {
430                 bv_->text->xsel_cache = text->selection;
431                 sel = text->selectionAsString(bv_->buffer(), false);
432         } else if (!text->selection.set()) {
433                 sel = string();
434                 bv_->text->xsel_cache.set(false);
435         }
436         if (!sel.empty()) {
437                 workarea().putClipboard(sel);
438         }
439 }
440
441
442 void BufferView::Pimpl::selectionLost()
443 {
444         if (available()) {
445                 hideCursor();
446                 toggleSelection();
447                 bv_->getLyXText()->clearSelection();
448                 showCursor();
449                 bv_->text->xsel_cache.set(false);
450         }
451 }
452
453
454 void BufferView::Pimpl::workAreaResize()
455 {
456         static int work_area_width;
457         static int work_area_height;
458
459         bool const widthChange = workarea().workWidth() != work_area_width;
460         bool const heightChange = workarea().workHeight() != work_area_height;
461
462         // update from work area
463         work_area_width = workarea().workWidth();
464         work_area_height = workarea().workHeight();
465
466         if (buffer_ != 0) {
467                 if (widthChange) {
468                         // The visible LyXView need a resize
469                         resizeCurrentBuffer();
470
471                         // Remove all texts from the textcache
472                         // This is not _really_ what we want to do. What
473                         // we really want to do is to delete in textcache
474                         // that does not have a BufferView with matching
475                         // width, but as long as we have only one BufferView
476                         // deleting all gives the same result.
477                         if (lyxerr.debugging())
478                                 textcache.show(lyxerr, "Expose delete all");
479                         textcache.clear();
480                         // FIXME: this is already done in resizeCurrentBuffer() ??
481                         buffer_->resizeInsets(bv_);
482                 } else if (heightChange) {
483                         // fitCursor() ensures we don't jump back
484                         // to the start of the document on vertical
485                         // resize
486                         fitCursor();
487                 }
488         }
489
490         if (widthChange || heightChange) {
491                 repaint();
492         }
493
494         // always make sure that the scrollbar is sane.
495         updateScrollbar();
496         owner_->updateLayoutChoice();
497         return;
498 }
499
500
501 void BufferView::Pimpl::update()
502 {
503         if (!bv_->theLockingInset() || !bv_->theLockingInset()->nodraw()) {
504                 LyXText::text_status st = bv_->text->status();
505                 screen().update(bv_->text, bv_);
506                 bool fitc = false;
507                 while (bv_->text->status() == LyXText::CHANGED_IN_DRAW) {
508                         bv_->text->fullRebreak(bv_);
509                         st = LyXText::NEED_MORE_REFRESH;
510                         bv_->text->setCursor(bv_, bv_->text->cursor.par(),
511                                              bv_->text->cursor.pos());
512                         if (bv_->text->selection.set()) {
513                                 bv_->text->setCursor(bv_, bv_->text->selection.start,
514                                                      bv_->text->selection.start.par(),
515                                                      bv_->text->selection.start.pos());
516                                 bv_->text->setCursor(bv_, bv_->text->selection.end,
517                                                      bv_->text->selection.end.par(),
518                                                      bv_->text->selection.end.pos());
519                         }
520                         fitc = true;
521                         bv_->text->status(bv_, st);
522                         screen().update(bv_->text, bv_);
523                 }
524                 // do this here instead of in the screen::update because of
525                 // the above loop!
526                 bv_->text->status(bv_, LyXText::UNCHANGED);
527                 if (fitc)
528                         fitCursor();
529         }
530 }
531
532 // Values used when calling update:
533 // -3 - update
534 // -2 - update, move sel_cursor if selection, fitcursor
535 // -1 - update, move sel_cursor if selection, fitcursor, mark dirty
536 //  0 - update, move sel_cursor if selection, fitcursor
537 //  1 - update, move sel_cursor if selection, fitcursor, mark dirty
538 //  3 - update, move sel_cursor if selection
539 //
540 // update -
541 // a simple redraw of the parts that need refresh
542 //
543 // move sel_cursor if selection -
544 // the text's sel_cursor is moved if there is selection is progress
545 //
546 // fitcursor -
547 // fitCursor() is called and the scrollbar updated
548 //
549 // mark dirty -
550 // the buffer is marked dirty.
551 //
552 // enum {
553 //       UPDATE = 0,
554 //       SELECT = 1,
555 //       FITCUR = 2,
556 //       CHANGE = 4
557 // };
558 //
559 // UPDATE_ONLY = UPDATE;
560 // UPDATE_SELECT = UPDATE | SELECT;
561 // UPDATE_SELECT_MOVE = UPDATE | SELECT | FITCUR;
562 // UPDATE_SELECT_MOVE_AFTER_CHANGE = UPDATE | SELECT | FITCUR | CHANGE;
563 //
564 // update(-3) -> update(0)         -> update(0) -> update(UPDATE)
565 // update(-2) -> update(1 + 2)     -> update(3) -> update(SELECT|FITCUR)
566 // update(-1) -> update(1 + 2 + 4) -> update(7) -> update(SELECT|FITCUR|CHANGE)
567 // update(1)  -> update(1 + 2 + 4) -> update(7) -> update(SELECT|FITCUR|CHANGE)
568 // update(3)  -> update(1)         -> update(1) -> update(SELECT)
569
570 void BufferView::Pimpl::update(LyXText * text, BufferView::UpdateCodes f)
571 {
572         owner_->updateLayoutChoice();
573
574         if (!text->selection.set() && (f & SELECT)) {
575                 text->selection.cursor = text->cursor;
576         }
577
578         text->fullRebreak(bv_);
579
580         if (text->inset_owner) {
581                 text->inset_owner->setUpdateStatus(bv_, InsetText::NONE);
582                 updateInset(text->inset_owner, false);
583         } else {
584                 update();
585         }
586
587         if ((f & FITCUR)) {
588                 fitCursor();
589         }
590
591         if ((f & CHANGE)) {
592                 buffer_->markDirty();
593         }
594 }
595
596
597 // Callback for cursor timer
598 void BufferView::Pimpl::cursorToggle()
599 {
600         if (!buffer_) {
601                 cursor_timeout.restart();
602                 return;
603         }
604
605         /* FIXME */
606         extern void reapSpellchecker(void);
607         reapSpellchecker();
608
609         if (!bv_->theLockingInset()) {
610                 screen().cursorToggle(bv_);
611         } else {
612                 bv_->theLockingInset()->toggleInsetCursor(bv_);
613         }
614
615         cursor_timeout.restart();
616 }
617
618
619 bool BufferView::Pimpl::available() const
620 {
621         if (buffer_ && bv_->text)
622                 return true;
623         return false;
624 }
625
626
627 void BufferView::Pimpl::beforeChange(LyXText * text)
628 {
629         toggleSelection();
630         text->clearSelection();
631 }
632
633
634 void BufferView::Pimpl::savePosition(unsigned int i)
635 {
636         if (i >= saved_positions_num)
637                 return;
638         saved_positions[i] = Position(buffer_->fileName(),
639                                       bv_->text->cursor.par()->id(),
640                                       bv_->text->cursor.pos());
641         if (i > 0) {
642                 ostringstream str;
643                 str << _("Saved bookmark") << ' ' << i;
644                 owner_->message(str.str().c_str());
645         }
646 }
647
648
649 void BufferView::Pimpl::restorePosition(unsigned int i)
650 {
651         if (i >= saved_positions_num)
652                 return;
653
654         string const fname = saved_positions[i].filename;
655
656         beforeChange(bv_->text);
657
658         if (fname != buffer_->fileName()) {
659                 Buffer * b = bufferlist.exists(fname) ?
660                         bufferlist.getBuffer(fname) :
661                         bufferlist.loadLyXFile(fname); // don't ask, just load it
662                 if (b != 0) buffer(b);
663         }
664
665         Paragraph * par = buffer_->getParFromID(saved_positions[i].par_id);
666         if (!par)
667                 return;
668
669         bv_->text->setCursor(bv_, par,
670                              min(par->size(), saved_positions[i].par_pos));
671
672         update(bv_->text, BufferView::SELECT | BufferView::FITCUR);
673         if (i > 0) {
674                 ostringstream str;
675                 str << _("Moved to bookmark") << ' ' << i;
676                 owner_->message(str.str().c_str());
677         }
678 }
679
680
681 bool BufferView::Pimpl::isSavedPosition(unsigned int i)
682 {
683         if (i >= saved_positions_num)
684                 return false;
685
686         return !saved_positions[i].filename.empty();
687 }
688
689
690 void BufferView::Pimpl::switchKeyMap()
691 {
692         if (!lyxrc.rtl_support)
693                 return;
694
695         LyXText * text = bv_->getLyXText();
696         if (text->real_current_font.isRightToLeft()
697             && !(bv_->theLockingInset()
698                  && bv_->theLockingInset()->lyxCode() == Inset::ERT_CODE))
699         {
700                 if (owner_->getIntl().keymap == Intl::PRIMARY)
701                         owner_->getIntl().KeyMapSec();
702         } else {
703                 if (owner_->getIntl().keymap == Intl::SECONDARY)
704                         owner_->getIntl().KeyMapPrim();
705         }
706 }
707
708
709 void BufferView::Pimpl::insetUnlock()
710 {
711         if (bv_->theLockingInset()) {
712                 bv_->theLockingInset()->insetUnlock(bv_);
713                 bv_->theLockingInset(0);
714                 finishUndo();
715         }
716 }
717
718
719 void BufferView::Pimpl::showCursor()
720 {
721         if (bv_->theLockingInset())
722                 bv_->theLockingInset()->showInsetCursor(bv_);
723         else
724                 screen().showCursor(bv_->text, bv_);
725 }
726
727
728 void BufferView::Pimpl::hideCursor()
729 {
730         if (!bv_->theLockingInset())
731                 screen().hideCursor();
732 }
733
734
735 void BufferView::Pimpl::toggleSelection(bool b)
736 {
737         if (bv_->theLockingInset())
738                 bv_->theLockingInset()->toggleSelection(bv_, b);
739         screen().toggleSelection(bv_->text, bv_, b);
740 }
741
742
743 void BufferView::Pimpl::toggleToggle()
744 {
745         screen().toggleToggle(bv_->text, bv_);
746 }
747
748
749 void BufferView::Pimpl::center()
750 {
751         LyXText * t = bv_->text;
752
753         beforeChange(t);
754         int const half_height = workarea().workHeight() / 2;
755         int new_y = 0;
756
757         if (t->cursor.y() > half_height) {
758                 new_y = t->cursor.y() - half_height;
759         }
760
761         // FIXME: can we do this w/o calling screen directly ?
762         // This updates first_y but means the fitCursor() call
763         // from the update(FITCUR) doesn't realise that we might
764         // have moved (e.g. from GOTOPARAGRAPH), so doesn't cause
765         // the scrollbar to be updated as it should, so we have
766         // to do it manually. Any operation that does a center()
767         // and also might have moved first_y must make sure to call
768         // updateScrollbar() currently. Never mind that this is a
769         // pretty obfuscated way of updating t->first_y
770         screen().draw(t, bv_, new_y);
771
772         update(t, BufferView::SELECT | BufferView::FITCUR);
773 }
774
775
776 void BufferView::Pimpl::stuffClipboard(string const & stuff) const
777 {
778         workarea().putClipboard(stuff);
779 }
780
781
782 /*
783  * Dispatch functions for actions which can be valid for BufferView->text
784  * and/or InsetText->text!!!
785  */
786
787
788 Inset * BufferView::Pimpl::getInsetByCode(Inset::Code code)
789 {
790 #if 0
791         LyXCursor cursor = bv_->getLyXText()->cursor;
792         Buffer::inset_iterator it =
793                 find_if(Buffer::inset_iterator(
794                         cursor.par(), cursor.pos()),
795                         buffer_->inset_iterator_end(),
796                         lyx::compare_memfun(&Inset::lyxCode, code));
797         return it != buffer_->inset_iterator_end() ? (*it) : 0;
798 #else
799         // Ok, this is a little bit too brute force but it
800         // should work for now. Better infrastructure is comming. (Lgb)
801
802         Buffer * b = bv_->buffer();
803         LyXCursor cursor = bv_->getLyXText()->cursor;
804
805         Buffer::inset_iterator beg = b->inset_iterator_begin();
806         Buffer::inset_iterator end = b->inset_iterator_end();
807
808         bool cursor_par_seen = false;
809
810         for (; beg != end; ++beg) {
811                 if (beg.getPar() == cursor.par()) {
812                         cursor_par_seen = true;
813                 }
814                 if (cursor_par_seen) {
815                         if (beg.getPar() == cursor.par()
816                             && beg.getPos() >= cursor.pos()) {
817                                 break;
818                         } else if (beg.getPar() != cursor.par()) {
819                                 break;
820                         }
821                 }
822
823         }
824         if (beg != end) {
825                 // Now find the first inset that matches code.
826                 for (; beg != end; ++beg) {
827                         if (beg->lyxCode() == code) {
828                                 return &(*beg);
829                         }
830                 }
831         }
832         return 0;
833 #endif
834 }
835
836
837 void BufferView::Pimpl::MenuInsertLyXFile(string const & filen)
838 {
839         string filename = filen;
840
841         if (filename.empty()) {
842                 // Launch a file browser
843                 string initpath = lyxrc.document_path;
844
845                 if (available()) {
846                         string const trypath = owner_->buffer()->filePath();
847                         // If directory is writeable, use this as default.
848                         if (IsDirWriteable(trypath))
849                                 initpath = trypath;
850                 }
851
852                 FileDialog fileDlg(bv_->owner(),
853                                    _("Select LyX document to insert"),
854                         LFUN_FILE_INSERT,
855                         make_pair(string(_("Documents|#o#O")),
856                                   string(lyxrc.document_path)),
857                         make_pair(string(_("Examples|#E#e")),
858                                   string(AddPath(system_lyxdir, "examples"))));
859
860                 FileDialog::Result result =
861                         fileDlg.Select(initpath,
862                                        _("*.lyx| LyX Documents (*.lyx)"));
863
864                 if (result.first == FileDialog::Later)
865                         return;
866
867                 filename = result.second;
868
869                 // check selected filename
870                 if (filename.empty()) {
871                         owner_->message(_("Canceled."));
872                         return;
873                 }
874         }
875
876         // get absolute path of file and add ".lyx" to the filename if
877         // necessary
878         filename = FileSearch(string(), filename, "lyx");
879
880         string const disp_fn(MakeDisplayPath(filename));
881
882         ostringstream s1;
883         s1 << _("Inserting document") << ' '
884            << disp_fn << " ...";
885         owner_->message(s1.str().c_str());
886         bool const res = bv_->insertLyXFile(filename);
887         if (res) {
888                 ostringstream str;
889                 str << _("Document") << ' ' << disp_fn
890                     << ' ' << _("inserted.");
891                 owner_->message(str.str().c_str());
892         } else {
893                 ostringstream str;
894                 str << _("Could not insert document") << ' '
895                     << disp_fn;
896                 owner_->message(str.str().c_str());
897         }
898 }
899
900
901 bool BufferView::Pimpl::dispatch(FuncRequest const & ev)
902 {
903         lyxerr[Debug::ACTION] << "BufferView::Pimpl::Dispatch:"
904                 << " action[" << ev.action <<"]"
905                 << " arg[" << ev.argument << "]"
906                 << " x[" << ev.x << "]"
907                 << " y[" << ev.y << "]"
908                 << " button[" << ev.button() << "]"
909                 << endl;
910
911         // e.g. Qt mouse press when no buffer
912         if (!buffer_)
913                 return false;
914  
915         LyXTextClass const & tclass = buffer_->params.getLyXTextClass();
916
917         switch (ev.action) {
918
919         case LFUN_SCROLL_INSET:
920                 // this is not handled here as this function is only active
921                 // if we have a locking_inset and that one is (or contains)
922                 // a tabular-inset
923                 break;
924
925         case LFUN_INSET_GRAPHICS:
926         {
927                 Inset * new_inset = new InsetGraphics;
928                 if (!insertInset(new_inset)) {
929                         delete new_inset;
930                 } else {
931                         // this is need because you don't use a inset->Edit()
932                         updateInset(new_inset, true);
933                         new_inset->edit(bv_);
934                 }
935                 break;
936         }
937
938         case LFUN_LAYOUT_COPY:
939                 bv_->copyEnvironment();
940                 break;
941
942         case LFUN_LAYOUT_PASTE:
943                 bv_->pasteEnvironment();
944                 switchKeyMap();
945                 break;
946
947         case LFUN_DEPTH_MIN:
948                 changeDepth(bv_, bv_->getLyXText(), -1);
949                 break;
950
951         case LFUN_DEPTH_PLUS:
952                 changeDepth(bv_, bv_->getLyXText(), 1);
953                 break;
954
955         case LFUN_FREE:
956                 owner_->getDialogs().setUserFreeFont();
957                 break;
958
959         case LFUN_FILE_INSERT:
960                 MenuInsertLyXFile(ev.argument);
961                 break;
962
963         case LFUN_FILE_INSERT_ASCII_PARA:
964                 InsertAsciiFile(bv_, ev.argument, true);
965                 break;
966
967         case LFUN_FILE_INSERT_ASCII:
968                 InsertAsciiFile(bv_, ev.argument, false);
969                 break;
970
971         case LFUN_LANGUAGE:
972                 lang(bv_, ev.argument);
973                 switchKeyMap();
974                 owner_->view_state_changed();
975                 break;
976
977         case LFUN_EMPH:
978                 emph(bv_);
979                 owner_->view_state_changed();
980                 break;
981
982         case LFUN_BOLD:
983                 bold(bv_);
984                 owner_->view_state_changed();
985                 break;
986
987         case LFUN_NOUN:
988                 noun(bv_);
989                 owner_->view_state_changed();
990                 break;
991
992         case LFUN_CODE:
993                 code(bv_);
994                 owner_->view_state_changed();
995                 break;
996
997         case LFUN_SANS:
998                 sans(bv_);
999                 owner_->view_state_changed();
1000                 break;
1001
1002         case LFUN_ROMAN:
1003                 roman(bv_);
1004                 owner_->view_state_changed();
1005                 break;
1006
1007         case LFUN_DEFAULT:
1008                 styleReset(bv_);
1009                 owner_->view_state_changed();
1010                 break;
1011
1012         case LFUN_UNDERLINE:
1013                 underline(bv_);
1014                 owner_->view_state_changed();
1015                 break;
1016
1017         case LFUN_FONT_SIZE:
1018                 fontSize(bv_, ev.argument);
1019                 owner_->view_state_changed();
1020                 break;
1021
1022         case LFUN_FONT_STATE:
1023                 owner_->getLyXFunc().setMessage(currentState(bv_));
1024                 break;
1025
1026         case LFUN_INSERT_LABEL:
1027                 MenuInsertLabel(bv_, ev.argument);
1028                 break;
1029
1030         case LFUN_REF_INSERT:
1031                 if (ev.argument.empty()) {
1032                         InsetCommandParams p("ref");
1033                         owner_->getDialogs().createRef(p.getAsString());
1034                 } else {
1035                         InsetCommandParams p;
1036                         p.setFromString(ev.argument);
1037
1038                         InsetRef * inset = new InsetRef(p, *buffer_);
1039                         if (!insertInset(inset))
1040                                 delete inset;
1041                         else
1042                                 updateInset(inset, true);
1043                 }
1044                 break;
1045
1046         case LFUN_BOOKMARK_SAVE:
1047                 savePosition(strToUnsignedInt(ev.argument));
1048                 break;
1049
1050         case LFUN_BOOKMARK_GOTO:
1051                 restorePosition(strToUnsignedInt(ev.argument));
1052                 break;
1053
1054         case LFUN_REF_GOTO:
1055         {
1056                 string label = ev.argument;
1057                 if (label.empty()) {
1058                         InsetRef * inset =
1059                                 static_cast<InsetRef*>(getInsetByCode(Inset::REF_CODE));
1060                         if (inset) {
1061                                 label = inset->getContents();
1062                                 savePosition(0);
1063                         }
1064                 }
1065
1066                 if (!label.empty()) {
1067                         //bv_->savePosition(0);
1068                         if (!bv_->gotoLabel(label))
1069                                 Alert::alert(_("Error"),
1070                                            _("Couldn't find this label"),
1071                                            _("in current document."));
1072                 }
1073         }
1074         break;
1075
1076         // --- accented characters ---------------------------
1077
1078         case LFUN_UMLAUT:
1079         case LFUN_CIRCUMFLEX:
1080         case LFUN_GRAVE:
1081         case LFUN_ACUTE:
1082         case LFUN_TILDE:
1083         case LFUN_CEDILLA:
1084         case LFUN_MACRON:
1085         case LFUN_DOT:
1086         case LFUN_UNDERDOT:
1087         case LFUN_UNDERBAR:
1088         case LFUN_CARON:
1089         case LFUN_SPECIAL_CARON:
1090         case LFUN_BREVE:
1091         case LFUN_TIE:
1092         case LFUN_HUNG_UMLAUT:
1093         case LFUN_CIRCLE:
1094         case LFUN_OGONEK:
1095                 if (ev.argument.empty()) {
1096                         // As always...
1097                         owner_->getLyXFunc().handleKeyFunc(ev.action);
1098                 } else {
1099                         owner_->getLyXFunc().handleKeyFunc(ev.action);
1100                         owner_->getIntl().getTransManager()
1101                                 .TranslateAndInsert(ev.argument[0], bv_->getLyXText());
1102                         update(bv_->getLyXText(),
1103                                BufferView::SELECT
1104                                | BufferView::FITCUR
1105                                | BufferView::CHANGE);
1106                 }
1107                 break;
1108
1109         case LFUN_MATH_MACRO:
1110         case LFUN_MATH_DELIM:
1111         case LFUN_INSERT_MATRIX:
1112         case LFUN_INSERT_MATH:
1113         case LFUN_MATH_IMPORT_SELECTION: // Imports LaTeX from the X selection
1114         case LFUN_MATH_DISPLAY:          // Open or create a displayed math inset
1115         case LFUN_MATH_MODE:             // Open or create an inlined math inset
1116         case LFUN_GREEK:                 // Insert a single greek letter
1117                 mathDispatch(FuncRequest(bv_, ev.action, ev.argument));
1118                 break;
1119
1120         case LFUN_CITATION_INSERT:
1121         {
1122                 InsetCommandParams p;
1123                 p.setFromString(ev.argument);
1124
1125                 InsetCitation * inset = new InsetCitation(p);
1126                 if (!insertInset(inset))
1127                         delete inset;
1128                 else {
1129                         inset->setLoadingBuffer(bv_->buffer(), false);
1130                         updateInset(inset, true);
1131                 }
1132                 
1133         }
1134         break;
1135
1136         case LFUN_INSERT_BIBTEX:
1137         {
1138                 // ale970405+lasgoutt970425
1139                 // The argument can be up to two tokens separated
1140                 // by a space. The first one is the bibstyle.
1141                 string const db = token(ev.argument, ' ', 0);
1142                 string bibstyle = token(ev.argument, ' ', 1);
1143                 if (bibstyle.empty())
1144                         bibstyle = "plain";
1145
1146                 InsetCommandParams p("BibTeX", db, bibstyle);
1147                 InsetBibtex * inset = new InsetBibtex(p);
1148
1149                 if (insertInset(inset)) {
1150                         if (ev.argument.empty())
1151                                 inset->edit(bv_);
1152                 } else
1153                         delete inset;
1154         }
1155         break;
1156
1157         // BibTeX data bases
1158         case LFUN_BIBDB_ADD:
1159         {
1160                 InsetBibtex * inset =
1161                         static_cast<InsetBibtex*>(getInsetByCode(Inset::BIBTEX_CODE));
1162                 if (inset) {
1163                         inset->addDatabase(ev.argument);
1164                 }
1165         }
1166         break;
1167
1168         case LFUN_BIBDB_DEL:
1169         {
1170                 InsetBibtex * inset =
1171                         static_cast<InsetBibtex*>(getInsetByCode(Inset::BIBTEX_CODE));
1172                 if (inset)
1173                         inset->delDatabase(ev.argument);
1174         }
1175         break;
1176
1177         case LFUN_BIBTEX_STYLE:
1178         {
1179                 InsetBibtex * inset =
1180                         static_cast<InsetBibtex*>(getInsetByCode(Inset::BIBTEX_CODE));
1181                 if (inset) 
1182                         inset->setOptions(ev.argument);
1183         }
1184         break;
1185
1186         case LFUN_CHILD_INSERT:
1187         {
1188                 InsetInclude::Params p;
1189                 if (!ev.argument.empty())
1190                         p.cparams.setFromString(ev.argument);
1191                 p.masterFilename_ = buffer_->fileName();
1192
1193                 InsetInclude * inset = new InsetInclude(p);
1194                 if (!insertInset(inset))
1195                         delete inset;
1196                 else {
1197                         updateInset(inset, true);
1198                         bv_->owner()->getDialogs().showInclude(inset);
1199                 }
1200         }
1201         break;
1202
1203         case LFUN_FLOAT_LIST:
1204                 if (tclass.floats().typeExist(ev.argument)) {
1205                         Inset * inset = new InsetFloatList(ev.argument);
1206                         if (!insertInset(inset, tclass.defaultLayoutName()))
1207                                 delete inset;
1208                 } else {
1209                         lyxerr << "Non-existent float type: "
1210                                << ev.argument << endl;
1211                 }
1212                 break;
1213
1214         case LFUN_THESAURUS_ENTRY:
1215         {
1216                 string arg = ev.argument;
1217
1218                 if (arg.empty()) {
1219                         arg = bv_->getLyXText()->selectionAsString(buffer_,
1220                                                                    false);
1221
1222                         // FIXME
1223                         if (arg.size() > 100 || arg.empty()) {
1224                                 // Get word or selection
1225                                 bv_->getLyXText()->selectWordWhenUnderCursor(bv_, LyXText::WHOLE_WORD);
1226                                 arg = bv_->getLyXText()->selectionAsString(buffer_, false);
1227                                 // FIXME: where is getLyXText()->unselect(bv_) ?
1228                         }
1229                 }
1230
1231                 bv_->owner()->getDialogs().showThesaurus(arg);
1232         }
1233                 break;
1234
1235         case LFUN_UNKNOWN_ACTION:
1236                 ev.errorMessage(N_("Unknown function!"));
1237                 break;
1238
1239         default:
1240                 return bv_->getLyXText()->dispatch(FuncRequest(ev, bv_));
1241         } // end of switch
1242
1243         return true;
1244 }
1245
1246
1247 bool BufferView::Pimpl::insertInset(Inset * inset, string const & lout)
1248 {
1249         // if we are in a locking inset we should try to insert the
1250         // inset there otherwise this is a illegal function now
1251         if (bv_->theLockingInset()) {
1252                 if (bv_->theLockingInset()->insetAllowed(inset))
1253                         return bv_->theLockingInset()->insertInset(bv_, inset);
1254                 return false;
1255         }
1256
1257         // not quite sure if we want this...
1258         setCursorParUndo(bv_);
1259         freezeUndo();
1260
1261         beforeChange(bv_->text);
1262         if (!lout.empty()) {
1263                 update(bv_->text, BufferView::SELECT|BufferView::FITCUR);
1264                 bv_->text->breakParagraph(bv_);
1265                 update(bv_->text, BufferView::SELECT|BufferView::FITCUR|BufferView::CHANGE);
1266
1267                 if (!bv_->text->cursor.par()->empty()) {
1268                         bv_->text->cursorLeft(bv_);
1269
1270                         bv_->text->breakParagraph(bv_);
1271                         update(bv_->text, BufferView::SELECT|BufferView::FITCUR|BufferView::CHANGE);
1272                 }
1273
1274                 string lres = lout;
1275                 LyXTextClass const & tclass =
1276                         buffer_->params.getLyXTextClass();
1277                 bool hasLayout = tclass.hasLayout(lres);
1278                 string lay = tclass.defaultLayoutName();
1279
1280                 if (hasLayout != false) {
1281                         // layout found
1282                         lay = lres;
1283                 } else {
1284                         // layout not fount using default
1285                         lay = tclass.defaultLayoutName();
1286                 }
1287
1288                 bv_->text->setLayout(bv_, lay);
1289
1290                 bv_->text->setParagraph(bv_, 0, 0,
1291                                    0, 0,
1292                                    VSpace(VSpace::NONE), VSpace(VSpace::NONE),
1293                                    Spacing(),
1294                                    LYX_ALIGN_LAYOUT,
1295                                    string(),
1296                                    0);
1297                 update(bv_->text, BufferView::SELECT|BufferView::FITCUR|BufferView::CHANGE);
1298         }
1299
1300         bv_->text->insertInset(bv_, inset);
1301         update(bv_->text, BufferView::SELECT|BufferView::FITCUR|BufferView::CHANGE);
1302
1303         unFreezeUndo();
1304         return true;
1305 }
1306
1307
1308 void BufferView::Pimpl::updateInset(Inset * inset, bool mark_dirty)
1309 {
1310         if (!inset || !available())
1311                 return;
1312
1313         // first check for locking insets
1314         if (bv_->theLockingInset()) {
1315                 if (bv_->theLockingInset() == inset) {
1316                         if (bv_->text->updateInset(bv_, inset)) {
1317                                 update();
1318                                 if (mark_dirty) {
1319                                         buffer_->markDirty();
1320                                 }
1321                                 updateScrollbar();
1322                                 return;
1323                         }
1324                 } else if (bv_->theLockingInset()->updateInsetInInset(bv_, inset)) {
1325                         if (bv_->text->updateInset(bv_,  bv_->theLockingInset())) {
1326                                 update();
1327                                 if (mark_dirty) {
1328                                         buffer_->markDirty();
1329                                 }
1330                                 updateScrollbar();
1331                                 return;
1332                         }
1333                 }
1334         }
1335
1336         // then check if the inset is a top_level inset (has no owner)
1337         // if yes do the update as always otherwise we have to update the
1338         // toplevel inset where this inset is inside
1339         Inset * tl_inset = inset;
1340         while (tl_inset->owner())
1341                 tl_inset = tl_inset->owner();
1342         hideCursor();
1343         if (tl_inset == inset) {
1344                 update(bv_->text, BufferView::UPDATE);
1345                 if (bv_->text->updateInset(bv_, inset)) {
1346                         if (mark_dirty) {
1347                                 update(bv_->text,
1348                                        BufferView::SELECT
1349                                        | BufferView::FITCUR
1350                                        | BufferView::CHANGE);
1351                         } else {
1352                                 update(bv_->text, SELECT);
1353                         }
1354                         return;
1355                 }
1356         } else if (static_cast<UpdatableInset *>(tl_inset)
1357                            ->updateInsetInInset(bv_, inset))
1358         {
1359                 if (bv_->text->updateInset(bv_, tl_inset)) {
1360                         update();
1361                         updateScrollbar();
1362                 }
1363         }
1364 }