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