]> git.lyx.org Git - lyx.git/blob - src/BufferView_pimpl.C
Add outlining functionality to the TOC dialog.
[lyx.git] / src / BufferView_pimpl.C
1 /**
2  * \file BufferView_pimpl.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Asger Alstrup
7  * \author Alfredo Braunstein
8  * \author Lars Gullik Bjønnes
9  * \author Jean-Marc Lasgouttes
10  * \author Angus Leeming
11  * \author John Levon
12  * \author André Pönitz
13  * \author Dekel Tsur
14  * \author Jürgen Vigna
15  *
16  * Full author contact details are available in file CREDITS.
17  */
18
19 #include <config.h>
20
21 #include "BufferView_pimpl.h"
22 #include "buffer.h"
23 #include "buffer_funcs.h"
24 #include "bufferlist.h"
25 #include "bufferparams.h"
26 #include "coordcache.h"
27 #include "cursor.h"
28 #include "CutAndPaste.h"
29 #include "debug.h"
30 #include "dispatchresult.h"
31 #include "factory.h"
32 #include "FloatList.h"
33 #include "funcrequest.h"
34 #include "FuncStatus.h"
35 #include "gettext.h"
36 #include "intl.h"
37 #include "insetiterator.h"
38 #include "LaTeXFeatures.h"
39 #include "lyx_cb.h" // added for Dispatch functions
40 #include "lyx_main.h"
41 #include "lyxfind.h"
42 #include "lyxfunc.h"
43 #include "lyxtext.h"
44 #include "lyxrc.h"
45 #include "lastfiles.h"
46 #include "metricsinfo.h"
47 #include "paragraph.h"
48 #include "paragraph_funcs.h"
49 #include "ParagraphParameters.h"
50 #include "pariterator.h"
51 #include "rowpainter.h"
52 #include "toc.h"
53 #include "undo.h"
54 #include "vspace.h"
55
56 #include "insets/insetbibtex.h"
57 #include "insets/insetref.h"
58 #include "insets/insettext.h"
59
60 #include "frontends/Alert.h"
61 #include "frontends/Dialogs.h"
62 #include "frontends/FileDialog.h"
63 #include "frontends/font_metrics.h"
64 #include "frontends/LyXView.h"
65 #include "frontends/LyXScreenFactory.h"
66 #include "frontends/screen.h"
67 #include "frontends/WorkArea.h"
68 #include "frontends/WorkAreaFactory.h"
69
70 #include "graphics/Previews.h"
71
72 #include "support/convert.h"
73 #include "support/filefilterlist.h"
74 #include "support/filetools.h"
75 #include "support/forkedcontr.h"
76 #include "support/package.h"
77 #include "support/types.h"
78
79 #include <boost/bind.hpp>
80 #include <boost/current_function.hpp>
81
82 #include <functional>
83 #include <vector>
84
85 using lyx::pos_type;
86
87 using lyx::support::AddPath;
88 using lyx::support::bformat;
89 using lyx::support::FileFilterList;
90 using lyx::support::FileSearch;
91 using lyx::support::ForkedcallsController;
92 using lyx::support::IsDirWriteable;
93 using lyx::support::MakeDisplayPath;
94 using lyx::support::MakeAbsPath;
95 using lyx::support::package;
96
97 using std::endl;
98 using std::istringstream;
99 using std::make_pair;
100 using std::min;
101 using std::max;
102 using std::string;
103 using std::mem_fun_ref;
104 using std::vector;
105
106
107 extern BufferList bufferlist;
108
109
110 namespace {
111
112 unsigned int const saved_positions_num = 20;
113
114 // All the below connection objects are needed because of a bug in some
115 // versions of GCC (<=2.96 are on the suspects list.) By having and assigning
116 // to these connections we avoid a segfault upon startup, and also at exit.
117 // (Lgb)
118
119 boost::signals::connection dispatchcon;
120 boost::signals::connection timecon;
121 boost::signals::connection doccon;
122 boost::signals::connection resizecon;
123 boost::signals::connection kpresscon;
124 boost::signals::connection selectioncon;
125 boost::signals::connection lostcon;
126
127
128 /// Return an inset of this class if it exists at the current cursor position
129 template <class T>
130 T * getInsetByCode(LCursor & cur, InsetBase::Code code)
131 {
132         T * inset = 0;
133         DocIterator it = cur;
134         if (it.nextInset() &&
135             it.nextInset()->lyxCode() == code) {
136                 inset = static_cast<T*>(it.nextInset());
137         }
138         return inset;
139 }
140
141 } // anon namespace
142
143
144 BufferView::Pimpl::Pimpl(BufferView & bv, LyXView * owner,
145                          int width, int height)
146         : bv_(&bv), owner_(owner), buffer_(0), wh_(0), cursor_timeout(400),
147           using_xterm_cursor(false), cursor_(bv), 
148           anchor_ref_(0), offset_ref_(0)
149 {
150         xsel_cache_.set = false;
151
152         workarea_.reset(WorkAreaFactory::create(*owner_, width, height));
153         screen_.reset(LyXScreenFactory::create(workarea()));
154
155         // Setup the signals
156         doccon = workarea().scrollDocView
157                 .connect(boost::bind(&BufferView::Pimpl::scrollDocView, this, _1));
158         resizecon = workarea().workAreaResize
159                 .connect(boost::bind(&BufferView::Pimpl::workAreaResize, this));
160         dispatchcon = workarea().dispatch
161                 .connect(boost::bind(&BufferView::Pimpl::workAreaDispatch, this, _1));
162         kpresscon = workarea().workAreaKeyPress
163                 .connect(boost::bind(&BufferView::Pimpl::workAreaKeyPress, this, _1, _2));
164         selectioncon = workarea().selectionRequested
165                 .connect(boost::bind(&BufferView::Pimpl::selectionRequested, this));
166         lostcon = workarea().selectionLost
167                 .connect(boost::bind(&BufferView::Pimpl::selectionLost, this));
168
169         timecon = cursor_timeout.timeout
170                 .connect(boost::bind(&BufferView::Pimpl::cursorToggle, this));
171         cursor_timeout.start();
172         saved_positions.resize(saved_positions_num);
173 }
174
175
176 void BufferView::Pimpl::addError(ErrorItem const & ei)
177 {
178         errorlist_.push_back(ei);
179 }
180
181
182 void BufferView::Pimpl::showReadonly(bool)
183 {
184         owner_->updateWindowTitle();
185         owner_->getDialogs().updateBufferDependent(false);
186 }
187
188
189 void BufferView::Pimpl::connectBuffer(Buffer & buf)
190 {
191         if (errorConnection_.connected())
192                 disconnectBuffer();
193
194         errorConnection_ =
195                 buf.error.connect(
196                         boost::bind(&BufferView::Pimpl::addError, this, _1));
197
198         messageConnection_ =
199                 buf.message.connect(
200                         boost::bind(&LyXView::message, owner_, _1));
201
202         busyConnection_ =
203                 buf.busy.connect(
204                         boost::bind(&LyXView::busy, owner_, _1));
205
206         titleConnection_ =
207                 buf.updateTitles.connect(
208                         boost::bind(&LyXView::updateWindowTitle, owner_));
209
210         timerConnection_ =
211                 buf.resetAutosaveTimers.connect(
212                         boost::bind(&LyXView::resetAutosaveTimer, owner_));
213
214         readonlyConnection_ =
215                 buf.readonly.connect(
216                         boost::bind(&BufferView::Pimpl::showReadonly, this, _1));
217
218         closingConnection_ =
219                 buf.closing.connect(
220                         boost::bind(&BufferView::Pimpl::setBuffer, this, (Buffer *)0));
221 }
222
223
224 void BufferView::Pimpl::disconnectBuffer()
225 {
226         errorConnection_.disconnect();
227         messageConnection_.disconnect();
228         busyConnection_.disconnect();
229         titleConnection_.disconnect();
230         timerConnection_.disconnect();
231         readonlyConnection_.disconnect();
232         closingConnection_.disconnect();
233 }
234
235
236 void BufferView::Pimpl::newFile(string const & filename, string const & tname,
237         bool isNamed)
238 {
239         setBuffer(::newFile(filename, tname, isNamed));
240 }
241
242
243 bool BufferView::Pimpl::loadLyXFile(string const & filename, bool tolastfiles)
244 {
245         // Get absolute path of file and add ".lyx"
246         // to the filename if necessary
247         string s = FileSearch(string(), filename, "lyx");
248
249         bool const found = !s.empty();
250
251         if (!found)
252                 s = filename;
253
254         // File already open?
255         if (bufferlist.exists(s)) {
256                 string const file = MakeDisplayPath(s, 20);
257                 string text = bformat(_("The document %1$s is already "
258                                         "loaded.\n\nDo you want to revert "
259                                         "to the saved version?"), file);
260                 int const ret = Alert::prompt(_("Revert to saved document?"),
261                         text, 0, 1,  _("&Revert"), _("&Switch to document"));
262
263                 if (ret != 0) {
264                         setBuffer(bufferlist.getBuffer(s));
265                         return true;
266                 }
267                 // FIXME: should be LFUN_REVERT
268                 if (!bufferlist.close(bufferlist.getBuffer(s), false))
269                         return false;
270                 // Fall through to new load. (Asger)
271         }
272
273         Buffer * b = 0;
274
275         if (found) {
276                 b = bufferlist.newBuffer(s);
277                 connectBuffer(*b);
278                 if (!::loadLyXFile(b, s)) {
279                         bufferlist.release(b);
280                         return false;
281                 }
282         } else {
283                 string text = bformat(_("The document %1$s does not yet "
284                                         "exist.\n\nDo you want to create "
285                                         "a new document?"), s);
286                 int const ret = Alert::prompt(_("Create new document?"),
287                          text, 0, 1, _("&Create"), _("Cancel"));
288
289                 if (ret == 0)
290                         b = ::newFile(s, string(), true);
291                 else
292                         return false;
293         }
294
295         setBuffer(b);
296         bv_->showErrorList(_("Parse"));
297
298         if (tolastfiles)
299                 LyX::ref().lastfiles().newFile(b->fileName());
300
301         return true;
302 }
303
304
305 WorkArea & BufferView::Pimpl::workarea() const
306 {
307         return *workarea_.get();
308 }
309
310
311 LyXScreen & BufferView::Pimpl::screen() const
312 {
313         return *screen_.get();
314 }
315
316
317 Painter & BufferView::Pimpl::painter() const
318 {
319         return workarea().getPainter();
320 }
321
322
323 void BufferView::Pimpl::setBuffer(Buffer * b)
324 {
325         lyxerr[Debug::INFO] << BOOST_CURRENT_FUNCTION
326                             << "[ b = " << b << "]" << endl;
327
328         if (buffer_) {
329                 disconnectBuffer();
330                 // Save the actual cursor position and anchor inside the
331                 // buffer so that it can be restored in case we rechange
332                 // to this buffer later on.
333                 buffer_->saveCursor(cursor_.selectionBegin(),
334                                     cursor_.selectionEnd());
335         }
336
337         // If we are closing current buffer, switch to the first in
338         // buffer list.
339         if (!b) {
340                 lyxerr[Debug::INFO] << BOOST_CURRENT_FUNCTION
341                                     << " No Buffer!" << endl;
342                 // We are closing the buffer, use the first buffer as current
343                 buffer_ = bufferlist.first();
344                 owner_->getDialogs().hideBufferDependent();
345         } else {
346                 // Set current buffer
347                 buffer_ = b;
348         }
349
350         // Reset old cursor
351         cursor_ = LCursor(*bv_);
352         anchor_ref_ = 0;
353         offset_ref_ = 0;
354
355
356         // If we're quitting lyx, don't bother updating stuff
357         if (quitting)
358                 return;
359
360         if (buffer_) {
361                 lyxerr[Debug::INFO] << BOOST_CURRENT_FUNCTION
362                                     << "Buffer addr: " << buffer_ << endl;
363                 connectBuffer(*buffer_);
364                 cursor_.push(buffer_->inset());
365                 cursor_.resetAnchor();
366                 buffer_->text().init(bv_);
367                 buffer_->text().setCurrentFont(cursor_);
368                 if (buffer_->getCursor().size() > 0 &&
369                     buffer_->getAnchor().size() > 0)
370                 {
371                         cursor_.setCursor(buffer_->getAnchor().asDocIterator(&(buffer_->inset())));
372                         cursor_.resetAnchor();
373                         cursor_.setCursor(buffer_->getCursor().asDocIterator(&(buffer_->inset())));
374                         cursor_.setSelection();
375                 }
376
377                 // Buffer-dependent dialogs should be updated or
378                 // hidden. This should go here because some dialogs (eg ToC)
379                 // require bv_->text.
380                 owner_->getDialogs().updateBufferDependent(true);
381         }
382
383         update();
384         updateScrollbar();
385         owner_->updateMenubar();
386         owner_->updateToolbars();
387         owner_->updateLayoutChoice();
388         owner_->updateWindowTitle();
389
390         // This is done after the layout combox has been populated
391         if (buffer_) {
392                 size_t i = cursor_.depth() - 1;
393                 // we know we'll eventually find a paragraph
394                 while (true) {
395                         CursorSlice const & slice = cursor_[i];
396                         if (!slice.inset().inMathed()) {
397                                 LyXLayout_ptr const layout = slice.paragraph().layout();
398                                 owner_->setLayout(layout->name());
399                                 break;
400                         }
401                         BOOST_ASSERT(i>0);
402                         --i;
403                 }
404         }       
405
406         if (buffer_ && lyx::graphics::Previews::status() != LyXRC::PREVIEW_OFF)
407                 lyx::graphics::Previews::get().generateBufferPreviews(*buffer_);
408 }
409
410
411 void BufferView::Pimpl::resizeCurrentBuffer()
412 {
413         lyxerr[Debug::DEBUG] << BOOST_CURRENT_FUNCTION << endl;
414         owner_->busy(true);
415         owner_->message(_("Formatting document..."));
416
417         LyXText * text = bv_->text();
418         if (!text)
419                 return;
420
421         text->init(bv_);
422         update();
423
424         switchKeyMap();
425         owner_->busy(false);
426
427         // Reset the "Formatting..." message
428         owner_->clearMessage();
429
430         updateScrollbar();
431 }
432
433
434 void BufferView::Pimpl::updateScrollbar()
435 {
436         if (!bv_->text()) {
437                 lyxerr[Debug::DEBUG] << BOOST_CURRENT_FUNCTION
438                                      << " no text in updateScrollbar" << endl;
439                 workarea().setScrollbarParams(0, 0, 0);
440                 return;
441         }
442
443         LyXText & t = *bv_->text();
444         int const parsize = int(t.paragraphs().size() - 1);
445         if (anchor_ref_ >  parsize)  {
446                 anchor_ref_ = parsize;
447                 offset_ref_ = 0;
448         }
449
450         lyxerr[Debug::GUI]
451                 << BOOST_CURRENT_FUNCTION
452                 << " Updating scrollbar: height: " << t.paragraphs().size()
453                 << " curr par: " << cursor_.bottom().pit()
454                 << " default height " << defaultRowHeight() << endl;
455
456         // It would be better to fix the scrollbar to understand
457         // values in [0..1] and divide everything by wh
458
459         // estimated average paragraph height:
460         if (wh_ == 0)
461                 wh_ = workarea().workHeight() / 4; 
462         int h = t.getPar(anchor_ref_).height();
463
464         // Normalize anchor/offset (MV):
465         while (offset_ref_ > h && anchor_ref_ < parsize) {
466                 anchor_ref_++;
467                 offset_ref_ -= h;
468                 h = t.getPar(anchor_ref_).height();
469         }
470         // Look at paragraph heights on-screen
471         int sumh = 0;
472         int nh = 0;
473         for (lyx::pit_type pit = anchor_ref_; pit <= parsize; ++pit) {
474                 if (sumh > workarea().workHeight())
475                         break;
476                 int const h2 = t.getPar(pit).height(); 
477                 sumh += h2;
478                 nh++;
479         }
480         int const hav = sumh / nh;
481         // More realistic average paragraph height
482         if (hav > wh_)
483                 wh_ = hav;
484         
485         workarea().setScrollbarParams((parsize + 1) * wh_, 
486                 anchor_ref_ * wh_ + int(offset_ref_ * wh_ / float(h)), 
487                 int(wh_ * defaultRowHeight() / float(h)));
488 }
489
490
491 void BufferView::Pimpl::scrollDocView(int value)
492 {
493         lyxerr[Debug::GUI] << BOOST_CURRENT_FUNCTION
494                            << "[ value = " << value << "]" << endl;
495
496         if (!buffer_)
497                 return;
498
499         screen().hideCursor();
500
501         LyXText & t = *bv_->text();
502
503         float const bar = value / float(wh_ * t.paragraphs().size());
504
505         anchor_ref_ = int(bar * t.paragraphs().size());
506         if (anchor_ref_ >  int(t.paragraphs().size()) - 1)
507                 anchor_ref_ = int(t.paragraphs().size()) - 1;
508         t.redoParagraph(anchor_ref_);
509         int const h = t.getPar(anchor_ref_).height();
510         offset_ref_ = int((bar * t.paragraphs().size() - anchor_ref_) * h);
511         update();
512
513         if (!lyxrc.cursor_follows_scrollbar)
514                 return;
515
516         int const height = 2 * defaultRowHeight();
517         int const first = height;
518         int const last = workarea().workHeight() - height;
519         LCursor & cur = cursor_;
520
521         bv_funcs::CurStatus st = bv_funcs::status(bv_, cur);
522
523         switch (st) {
524         case bv_funcs::CUR_ABOVE:
525                 t.setCursorFromCoordinates(cur, 0, first);
526                 cur.clearSelection();
527                 break;
528         case bv_funcs::CUR_BELOW:
529                 t.setCursorFromCoordinates(cur, 0, last);
530                 cur.clearSelection();
531                 break;
532         case bv_funcs::CUR_INSIDE:
533                 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
534                 int const newy = min(last, max(y, first));
535                 if (y != newy) {
536                         cur.reset(buffer_->inset());
537                         t.setCursorFromCoordinates(cur, 0, newy);
538                 }
539         }
540         owner_->updateLayoutChoice();
541 }
542
543
544 void BufferView::Pimpl::scroll(int /*lines*/)
545 {
546 //      if (!buffer_)
547 //              return;
548 //
549 //      LyXText const * t = bv_->text();
550 //      int const line_height = defaultRowHeight();
551 //
552 //      // The new absolute coordinate
553 //      int new_top_y = top_y() + lines * line_height;
554 //
555 //      // Restrict to a valid value
556 //      new_top_y = std::min(t->height() - 4 * line_height, new_top_y);
557 //      new_top_y = std::max(0, new_top_y);
558 //
559 //      scrollDocView(new_top_y);
560 //
561 //      // Update the scrollbar.
562 //      workarea().setScrollbarParams(t->height(), top_y(), defaultRowHeight());
563 }
564
565
566 void BufferView::Pimpl::workAreaKeyPress(LyXKeySymPtr key,
567                                          key_modifier::state state)
568 {
569         owner_->getLyXFunc().processKeySym(key, state);
570
571         /* This is perhaps a bit of a hack. When we move
572          * around, or type, it's nice to be able to see
573          * the cursor immediately after the keypress. So
574          * we reset the toggle timeout and force the visibility
575          * of the cursor. Note we cannot do this inside
576          * dispatch() itself, because that's called recursively.
577          */
578         if (available())
579                 screen().showCursor(*bv_);
580 }
581
582
583 void BufferView::Pimpl::selectionRequested()
584 {
585         static string sel;
586
587         if (!available())
588                 return;
589
590         LCursor & cur = cursor_;
591
592         if (!cur.selection()) {
593                 xsel_cache_.set = false;
594                 return;
595         }
596
597         if (!xsel_cache_.set ||
598             cur.top() != xsel_cache_.cursor ||
599             cur.anchor_.top() != xsel_cache_.anchor)
600         {
601                 xsel_cache_.cursor = cur.top();
602                 xsel_cache_.anchor = cur.anchor_.top();
603                 xsel_cache_.set = cur.selection();
604                 sel = cur.selectionAsString(false);
605                 if (!sel.empty())
606                         workarea().putClipboard(sel);
607         }
608 }
609
610
611 void BufferView::Pimpl::selectionLost()
612 {
613         if (available()) {
614                 screen().hideCursor();
615                 cursor_.clearSelection();
616                 xsel_cache_.set = false;
617         }
618 }
619
620
621 void BufferView::Pimpl::workAreaResize()
622 {
623         static int work_area_width;
624         static int work_area_height;
625
626         bool const widthChange = workarea().workWidth() != work_area_width;
627         bool const heightChange = workarea().workHeight() != work_area_height;
628
629         // Update from work area
630         work_area_width = workarea().workWidth();
631         work_area_height = workarea().workHeight();
632
633         if (buffer_ && widthChange) {
634                 // The visible LyXView need a resize
635                 resizeCurrentBuffer();
636         }
637
638         if (widthChange || heightChange)
639                 update();
640
641         // Always make sure that the scrollbar is sane.
642         updateScrollbar();
643         owner_->updateLayoutChoice();
644 }
645
646
647 bool BufferView::Pimpl::fitCursor()
648 {
649         if (bv_funcs::status(bv_, cursor_) == bv_funcs::CUR_INSIDE) {
650                 LyXFont const font = cursor_.getFont();
651                 int const asc = font_metrics::maxAscent(font);
652                 int const des = font_metrics::maxDescent(font);
653                 Point const p = bv_funcs::getPos(cursor_, cursor_.boundary());
654                 if (p.y_ - asc >= 0 && p.y_ + des < workarea().workHeight())
655                         return false;
656         }
657         center();
658         return true;
659 }
660
661
662 bool BufferView::Pimpl::multiParSel()
663 {
664         if (!cursor_.selection())
665                 return false;
666         bool ret = multiparsel_cache_;
667         multiparsel_cache_ = cursor_.selBegin().pit() != cursor_.selEnd().pit();
668         // Either this, or previous selection spans paragraphs
669         return ret || multiparsel_cache_;
670 }
671
672
673 void BufferView::Pimpl::update(Update::flags flags)
674 {
675         lyxerr[Debug::DEBUG]
676                 << BOOST_CURRENT_FUNCTION
677                 << "[fitcursor = " << (flags & Update::FitCursor)
678                 << ", forceupdate = " << (flags & Update::Force)
679                 << ", singlepar = " << (flags & Update::SinglePar)
680                 << "]  buffer: " << buffer_ << endl;
681
682         // Check needed to survive LyX startup
683         if (buffer_) {
684                 // Update macro store
685                 buffer_->buildMacros();
686
687                 CoordCache backup;
688                 std::swap(theCoords, backup);
689
690                 // This, together with doneUpdating(), verifies (using
691                 // asserts) that screen redraw is not called from
692                 // within itself.
693                 theCoords.startUpdating();
694
695                 // First drawing step
696                 ViewMetricsInfo vi = metrics(flags & Update::SinglePar);
697                 bool forceupdate(flags & (Update::Force | Update::SinglePar));
698
699                 if ((flags & Update::FitCursor) && fitCursor()) {
700                         forceupdate = true;
701                         vi = metrics();
702                 }
703                 if ((flags & Update::MultiParSel) && multiParSel()) {
704                         forceupdate = true;
705                         vi = metrics();
706                 }
707                 if (forceupdate) {
708                         // Second drawing step
709                         screen().redraw(*bv_, vi);
710                 } else {
711                         // Abort updating of the coord
712                         // cache - just restore the old one
713                         std::swap(theCoords, backup);
714                 }
715         } else
716                 screen().greyOut();
717
718         // And the scrollbar
719         updateScrollbar();
720         owner_->view_state_changed();
721 }
722
723
724 // Callback for cursor timer
725 void BufferView::Pimpl::cursorToggle()
726 {
727         if (buffer_) {
728                 screen().toggleCursor(*bv_);
729
730                 // Use this opportunity to deal with any child processes that
731                 // have finished but are waiting to communicate this fact
732                 // to the rest of LyX.
733                 ForkedcallsController & fcc = ForkedcallsController::get();
734                 fcc.handleCompletedProcesses();
735         }
736
737         cursor_timeout.restart();
738 }
739
740
741 bool BufferView::Pimpl::available() const
742 {
743         return buffer_ && bv_->text();
744 }
745
746
747 Change const BufferView::Pimpl::getCurrentChange()
748 {
749         if (!buffer_->params().tracking_changes)
750                 return Change(Change::UNCHANGED);
751
752         LyXText * text = bv_->getLyXText();
753         LCursor & cur = cursor_;
754
755         if (!cur.selection())
756                 return Change(Change::UNCHANGED);
757
758         return text->getPar(cur.selBegin().pit()).
759                         lookupChangeFull(cur.selBegin().pos());
760 }
761
762
763 void BufferView::Pimpl::savePosition(unsigned int i)
764 {
765         if (i >= saved_positions_num)
766                 return;
767         BOOST_ASSERT(cursor_.inTexted());
768         saved_positions[i] = Position(buffer_->fileName(),
769                                       cursor_.paragraph().id(),
770                                       cursor_.pos());
771         if (i > 0)
772                 owner_->message(bformat(_("Saved bookmark %1$d"), i));
773 }
774
775
776 void BufferView::Pimpl::restorePosition(unsigned int i)
777 {
778         if (i >= saved_positions_num)
779                 return;
780
781         string const fname = saved_positions[i].filename;
782
783         cursor_.clearSelection();
784
785         if (fname != buffer_->fileName()) {
786                 Buffer * b = 0;
787                 if (bufferlist.exists(fname))
788                         b = bufferlist.getBuffer(fname);
789                 else {
790                         b = bufferlist.newBuffer(fname);
791                         // Don't ask, just load it
792                         ::loadLyXFile(b, fname);
793                 }
794                 if (b)
795                         setBuffer(b);
796         }
797
798         ParIterator par = buffer_->getParFromID(saved_positions[i].par_id);
799         if (par == buffer_->par_iterator_end())
800                 return;
801
802         bv_->setCursor(makeDocIterator(par, min(par->size(), saved_positions[i].par_pos)));
803
804         if (i > 0)
805                 owner_->message(bformat(_("Moved to bookmark %1$d"), i));
806 }
807
808
809 bool BufferView::Pimpl::isSavedPosition(unsigned int i)
810 {
811         return i < saved_positions_num && !saved_positions[i].filename.empty();
812 }
813
814
815 void BufferView::Pimpl::switchKeyMap()
816 {
817         if (!lyxrc.rtl_support)
818                 return;
819
820         Intl & intl = owner_->getIntl();
821         if (bv_->getLyXText()->real_current_font.isRightToLeft()) {
822                 if (intl.keymap == Intl::PRIMARY)
823                         intl.KeyMapSec();
824         } else {
825                 if (intl.keymap == Intl::SECONDARY)
826                         intl.KeyMapPrim();
827         }
828 }
829
830
831 void BufferView::Pimpl::center()
832 {
833         CursorSlice & bot = cursor_.bottom();
834         lyx::pit_type const pit = bot.pit();
835         bot.text()->redoParagraph(pit);
836         Paragraph const & par = bot.text()->paragraphs()[pit];
837         anchor_ref_ = pit;
838         offset_ref_ = bv_funcs::coordOffset(cursor_, cursor_.boundary()).y_
839                 + par.ascent() - workarea().workHeight() / 2;
840 }
841
842
843 void BufferView::Pimpl::stuffClipboard(string const & content) const
844 {
845         workarea().putClipboard(content);
846 }
847
848
849 void BufferView::Pimpl::MenuInsertLyXFile(string const & filenm)
850 {
851         BOOST_ASSERT(cursor_.inTexted());
852         string filename = filenm;
853
854         if (filename.empty()) {
855                 // Launch a file browser
856                 string initpath = lyxrc.document_path;
857
858                 if (available()) {
859                         string const trypath = owner_->buffer()->filePath();
860                         // If directory is writeable, use this as default.
861                         if (IsDirWriteable(trypath))
862                                 initpath = trypath;
863                 }
864
865                 FileDialog fileDlg(_("Select LyX document to insert"),
866                         LFUN_FILE_INSERT,
867                         make_pair(string(_("Documents|#o#O")),
868                                   string(lyxrc.document_path)),
869                         make_pair(string(_("Examples|#E#e")),
870                                   string(AddPath(package().system_support(), "examples"))));
871
872                 FileDialog::Result result =
873                         fileDlg.open(initpath,
874                                      FileFilterList(_("LyX Documents (*.lyx)")),
875                                      string());
876
877                 if (result.first == FileDialog::Later)
878                         return;
879
880                 filename = result.second;
881
882                 // check selected filename
883                 if (filename.empty()) {
884                         owner_->message(_("Canceled."));
885                         return;
886                 }
887         }
888
889         // Get absolute path of file and add ".lyx"
890         // to the filename if necessary
891         filename = FileSearch(string(), filename, "lyx");
892
893         string const disp_fn = MakeDisplayPath(filename);
894         owner_->message(bformat(_("Inserting document %1$s..."), disp_fn));
895
896         string res;
897         Buffer buf("", false);
898         buf.error.connect(boost::bind(&BufferView::Pimpl::addError, this, _1));
899         if (::loadLyXFile(&buf, MakeAbsPath(filename))) {
900                 lyx::cap::pasteParagraphList(cursor_, buf.paragraphs(), 
901                                              buf.params().textclass);
902                 res = _("Document %1$s inserted.");
903         } else
904                 res = _("Could not insert document %1$s");
905
906         owner_->message(bformat(res, disp_fn));
907         bv_->showErrorList(_("Document insertion"));
908         resizeCurrentBuffer();
909 }
910
911
912 void BufferView::Pimpl::trackChanges()
913 {
914         bool const tracking = buffer_->params().tracking_changes;
915
916         if (!tracking) {
917                 for_each(buffer_->par_iterator_begin(),
918                          buffer_->par_iterator_end(),
919                          bind(&Paragraph::trackChanges, _1, Change::UNCHANGED));
920                 buffer_->params().tracking_changes = true;
921
922                 // We cannot allow undos beyond the freeze point
923                 buffer_->undostack().clear();
924         } else {
925                 cursor_.setCursor(doc_iterator_begin(buffer_->inset()));
926                 if (lyx::find::findNextChange(bv_)) {
927                         owner_->getDialogs().show("changes");
928                         return;
929                 }
930
931                 for_each(buffer_->par_iterator_begin(),
932                          buffer_->par_iterator_end(),
933                          mem_fun_ref(&Paragraph::untrackChanges));
934
935                 buffer_->params().tracking_changes = false;
936         }
937
938         buffer_->redostack().clear();
939 }
940
941
942 bool BufferView::Pimpl::workAreaDispatch(FuncRequest const & cmd0)
943 {
944         //lyxerr << BOOST_CURRENT_FUNCTION << "[ cmd0 " << cmd0 << "]" << endl;
945
946         // This is only called for mouse related events including
947         // LFUN_FILE_OPEN generated by drag-and-drop.
948         FuncRequest cmd = cmd0;
949
950         // Handle drag&drop
951         if (cmd.action == LFUN_FILE_OPEN) {
952                 owner_->dispatch(cmd);
953                 return true;
954         }
955
956         if (!buffer_)
957                 return false;
958
959         LCursor cur(*bv_);
960         cur.push(buffer_->inset());
961         cur.selection() = cursor_.selection();
962
963         // Doesn't go through lyxfunc, so we need to update
964         // the layout choice etc. ourselves
965
966         // E.g. Qt mouse press when no buffer
967         if (!available())
968                 return false;
969
970         screen().hideCursor();
971
972         // Either the inset under the cursor or the
973         // surrounding LyXText will handle this event.
974
975         // Build temporary cursor.
976         cmd.y = min(max(cmd.y,-1), workarea().workHeight());
977         InsetBase * inset = bv_->text()->editXY(cur, cmd.x, cmd.y);
978         //lyxerr << BOOST_CURRENT_FUNCTION
979         //       << " * hit inset at tip: " << inset << endl;
980         //lyxerr << BOOST_CURRENT_FUNCTION
981         //       << " * created temp cursor:" << cur << endl;
982
983         // Put anchor at the same position.
984         cur.resetAnchor();
985
986         // Try to dispatch to an non-editable inset near this position
987         // via the temp cursor. If the inset wishes to change the real
988         // cursor it has to do so explicitly by using
989         //  cur.bv().cursor() = cur;  (or similar)
990         if (inset)
991                 inset->dispatch(cur, cmd);
992
993         // Now dispatch to the temporary cursor. If the real cursor should
994         // be modified, the inset's dispatch has to do so explicitly.
995         if (!cur.result().dispatched())
996                 cur.dispatch(cmd);
997
998         if (cur.result().dispatched()) {
999                 // Redraw if requested or necessary.
1000                 if (cur.result().update())
1001                         update(Update::FitCursor | Update::Force);
1002                 else
1003                         update(Update::FitCursor | Update::MultiParSel);
1004         }
1005
1006         // See workAreaKeyPress
1007         cursor_timeout.restart();
1008         screen().showCursor(*bv_);
1009
1010         // Skip these when selecting
1011         if (cmd.action != LFUN_MOUSE_MOTION) {
1012                 owner_->updateLayoutChoice();
1013                 owner_->updateToolbars();
1014         }
1015
1016         // Slight hack: this is only called currently when we
1017         // clicked somewhere, so we force through the display
1018         // of the new status here.
1019         owner_->clearMessage();
1020         return true;
1021 }
1022
1023
1024 FuncStatus BufferView::Pimpl::getStatus(FuncRequest const & cmd)
1025 {
1026         FuncStatus flag;
1027
1028         switch (cmd.action) {
1029
1030         case LFUN_UNDO:
1031                 flag.enabled(!buffer_->undostack().empty());
1032                 break;
1033         case LFUN_REDO:
1034                 flag.enabled(!buffer_->redostack().empty());
1035                 break;
1036         case LFUN_FILE_INSERT:
1037         case LFUN_FILE_INSERT_ASCII_PARA:
1038         case LFUN_FILE_INSERT_ASCII:
1039                 // FIXME: Actually, these LFUNS should be moved to LyXText
1040                 flag.enabled(cursor_.inTexted());
1041                 break;
1042         case LFUN_FONT_STATE:
1043         case LFUN_INSERT_LABEL:
1044         case LFUN_BOOKMARK_SAVE:
1045         case LFUN_GOTO_PARAGRAPH:
1046         case LFUN_OUTLINE:
1047         case LFUN_GOTOERROR:
1048         case LFUN_GOTONOTE:
1049         case LFUN_REFERENCE_GOTO:
1050         case LFUN_WORD_FIND:
1051         case LFUN_WORD_REPLACE:
1052         case LFUN_MARK_OFF:
1053         case LFUN_MARK_ON:
1054         case LFUN_SETMARK:
1055         case LFUN_CENTER:
1056         case LFUN_BIBDB_ADD:
1057         case LFUN_BIBDB_DEL:
1058         case LFUN_WORDS_COUNT:
1059                 flag.enabled(true);
1060                 break;
1061
1062         case LFUN_LABEL_GOTO: {
1063                 flag.enabled(!cmd.argument.empty()
1064                     || getInsetByCode<InsetRef>(cursor_, InsetBase::REF_CODE));
1065                 break;
1066         }
1067
1068         case LFUN_BOOKMARK_GOTO:
1069                 flag.enabled(isSavedPosition(convert<unsigned int>(cmd.argument)));
1070                 break;
1071         case LFUN_TRACK_CHANGES:
1072                 flag.enabled(true);
1073                 flag.setOnOff(buffer_->params().tracking_changes);
1074                 break;
1075
1076         case LFUN_OUTPUT_CHANGES: {
1077                 OutputParams runparams;
1078                 LaTeXFeatures features(*buffer_, buffer_->params(), runparams);
1079                 flag.enabled(buffer_ && buffer_->params().tracking_changes
1080                         && features.isAvailable("dvipost"));
1081                 flag.setOnOff(buffer_->params().output_changes);
1082                 break;
1083         }
1084
1085         case LFUN_MERGE_CHANGES:
1086         case LFUN_ACCEPT_CHANGE: // what about these two
1087         case LFUN_REJECT_CHANGE: // what about these two
1088         case LFUN_ACCEPT_ALL_CHANGES:
1089         case LFUN_REJECT_ALL_CHANGES:
1090                 flag.enabled(buffer_ && buffer_->params().tracking_changes);
1091                 break;
1092         default:
1093                 flag.enabled(false);
1094         }
1095
1096         return flag;
1097 }
1098
1099
1100
1101 bool BufferView::Pimpl::dispatch(FuncRequest const & cmd)
1102 {
1103         //lyxerr << BOOST_CURRENT_FUNCTION
1104         //       << [ cmd = " << cmd << "]" << endl;
1105
1106         // Make sure that the cached BufferView is correct.
1107         lyxerr[Debug::ACTION] << BOOST_CURRENT_FUNCTION
1108                 << " action[" << cmd.action << ']'
1109                 << " arg[" << cmd.argument << ']'
1110                 << " x[" << cmd.x << ']'
1111                 << " y[" << cmd.y << ']'
1112                 << " button[" << cmd.button() << ']'
1113                 << endl;
1114
1115         LCursor & cur = cursor_;
1116
1117         switch (cmd.action) {
1118
1119         case LFUN_UNDO:
1120                 if (available()) {
1121                         cur.message(_("Undo"));
1122                         cur.clearSelection();
1123                         if (!textUndo(*bv_))
1124                                 cur.message(_("No further undo information"));
1125                         update();
1126                         switchKeyMap();
1127                 }
1128                 break;
1129
1130         case LFUN_REDO:
1131                 if (available()) {
1132                         cur.message(_("Redo"));
1133                         cur.clearSelection();
1134                         if (!textRedo(*bv_))
1135                                 cur.message(_("No further redo information"));
1136                         update();
1137                         switchKeyMap();
1138                 }
1139                 break;
1140
1141         case LFUN_FILE_INSERT:
1142                 MenuInsertLyXFile(cmd.argument);
1143                 break;
1144
1145         case LFUN_FILE_INSERT_ASCII_PARA:
1146                 InsertAsciiFile(bv_, cmd.argument, true);
1147                 break;
1148
1149         case LFUN_FILE_INSERT_ASCII:
1150                 InsertAsciiFile(bv_, cmd.argument, false);
1151                 break;
1152
1153         case LFUN_FONT_STATE:
1154                 cur.message(cur.currentState());
1155                 break;
1156
1157         case LFUN_BOOKMARK_SAVE:
1158                 savePosition(convert<unsigned int>(cmd.argument));
1159                 break;
1160
1161         case LFUN_BOOKMARK_GOTO:
1162                 restorePosition(convert<unsigned int>(cmd.argument));
1163                 break;
1164
1165         case LFUN_LABEL_GOTO: {
1166                 string label = cmd.argument;
1167                 if (label.empty()) {
1168                         InsetRef * inset =
1169                                 getInsetByCode<InsetRef>(cursor_,
1170                                                          InsetBase::REF_CODE);
1171                         if (inset) {
1172                                 label = inset->getContents();
1173                                 savePosition(0);
1174                         }
1175                 }
1176
1177                 if (!label.empty())
1178                         bv_->gotoLabel(label);
1179                 break;
1180         }
1181
1182         case LFUN_GOTO_PARAGRAPH: {
1183                 int const id = convert<int>(cmd.argument);
1184                 ParIterator par = buffer_->getParFromID(id);
1185                 if (par == buffer_->par_iterator_end()) {
1186                         lyxerr[Debug::INFO] << "No matching paragraph found! ["
1187                                             << id << ']' << endl;
1188                         break;
1189                 } else {
1190                         lyxerr[Debug::INFO] << "Paragraph " << par->id()
1191                                             << " found." << endl;
1192                 }
1193
1194                 // Set the cursor
1195                 bv_->setCursor(makeDocIterator(par, 0));
1196
1197                 update();
1198                 switchKeyMap();
1199                 break;
1200         }
1201
1202         case LFUN_OUTLINE: {
1203                 lyx::toc::OutlineOp const op =
1204                     static_cast<lyx::toc::OutlineOp>(convert<int>(cmd.argument));
1205                 lyx::toc::Outline(op, buffer_, cursor_.pit());
1206                 bv_->text()->setCursor(cursor_, cursor_.pit(), 0);
1207                 buffer_->markDirty();
1208                 updateCounters(*buffer_);
1209                 update();
1210         }
1211                                   
1212         case LFUN_GOTOERROR:
1213                 bv_funcs::gotoInset(bv_, InsetBase::ERROR_CODE, false);
1214                 break;
1215
1216         case LFUN_GOTONOTE:
1217                 bv_funcs::gotoInset(bv_, InsetBase::NOTE_CODE, false);
1218                 break;
1219
1220         case LFUN_REFERENCE_GOTO: {
1221                 vector<InsetBase_code> tmp;
1222                 tmp.push_back(InsetBase::LABEL_CODE);
1223                 tmp.push_back(InsetBase::REF_CODE);
1224                 bv_funcs::gotoInset(bv_, tmp, true);
1225                 break;
1226         }
1227
1228         case LFUN_TRACK_CHANGES:
1229                 trackChanges();
1230                 break;
1231
1232         case LFUN_OUTPUT_CHANGES: {
1233                 bool const state = buffer_->params().output_changes;
1234                 buffer_->params().output_changes = !state;
1235                 break;
1236         }
1237
1238         case LFUN_MERGE_CHANGES:
1239                 if (lyx::find::findNextChange(bv_))
1240                         owner_->getDialogs().show("changes");
1241                 break;
1242
1243         case LFUN_ACCEPT_ALL_CHANGES: {
1244                 cursor_.reset(buffer_->inset());
1245 #ifdef WITH_WARNINGS
1246 #warning FIXME changes
1247 #endif
1248                 while (lyx::find::findNextChange(bv_))
1249                         bv_->getLyXText()->acceptChange(cursor_);
1250                 update();
1251                 break;
1252         }
1253
1254         case LFUN_REJECT_ALL_CHANGES: {
1255                 cursor_.reset(buffer_->inset());
1256 #ifdef WITH_WARNINGS
1257 #warning FIXME changes
1258 #endif
1259                 while (lyx::find::findNextChange(bv_))
1260                         bv_->getLyXText()->rejectChange(cursor_);
1261                 break;
1262         }
1263
1264         case LFUN_WORD_FIND:
1265                 lyx::find::find(bv_, cmd);
1266                 break;
1267
1268         case LFUN_WORD_REPLACE:
1269                 lyx::find::replace(bv_, cmd);
1270                 break;
1271
1272         case LFUN_MARK_OFF:
1273                 cur.clearSelection();
1274                 cur.resetAnchor();
1275                 cur.message(N_("Mark off"));
1276                 break;
1277
1278         case LFUN_MARK_ON:
1279                 cur.clearSelection();
1280                 cur.mark() = true;
1281                 cur.resetAnchor();
1282                 cur.message(N_("Mark on"));
1283                 break;
1284
1285         case LFUN_SETMARK:
1286                 cur.clearSelection();
1287                 if (cur.mark()) {
1288                         cur.mark() = false;
1289                         cur.message(N_("Mark removed"));
1290                 } else {
1291                         cur.mark() = true;
1292                         cur.message(N_("Mark set"));
1293                 }
1294                 cur.resetAnchor();
1295                 break;
1296
1297         case LFUN_CENTER:
1298                 center();
1299                 break;
1300
1301         case LFUN_BIBDB_ADD: {
1302                 LCursor tmpcur = cursor_;
1303                 bv_funcs::findInset(tmpcur, InsetBase::BIBTEX_CODE, false);
1304                 InsetBibtex * inset = getInsetByCode<InsetBibtex>(tmpcur,
1305                                                 InsetBase::BIBTEX_CODE);
1306                 if (inset)
1307                         inset->addDatabase(cmd.argument);
1308                 break;
1309         }
1310
1311         case LFUN_BIBDB_DEL: {
1312                 LCursor tmpcur = cursor_;
1313                 bv_funcs::findInset(tmpcur, InsetBase::BIBTEX_CODE, false);
1314                 InsetBibtex * inset = getInsetByCode<InsetBibtex>(tmpcur,
1315                                                 InsetBase::BIBTEX_CODE);
1316                 if (inset)
1317                         inset->delDatabase(cmd.argument);
1318                 break;
1319         }
1320
1321         case LFUN_WORDS_COUNT: {
1322                 DocIterator from, to;
1323                 if (cur.selection()) {
1324                         from = cur.selectionBegin();
1325                         to = cur.selectionEnd();
1326                 } else {
1327                         from = doc_iterator_begin(buffer_->inset());
1328                         to = doc_iterator_end(buffer_->inset());
1329                 }
1330                 int const count = countWords(from, to);
1331                 string message;
1332                 if (count != 1) {
1333                         if (cur.selection())
1334                                 message = bformat(_("%1$d words in selection."),
1335                                           count);
1336                                 else
1337                                         message = bformat(_("%1$d words in document."),
1338                                                           count);
1339                 }
1340                 else {
1341                         if (cur.selection())
1342                                 message = _("One word in selection.");
1343                         else
1344                                 message = _("One word in document.");
1345                 }
1346
1347                 Alert::information(_("Count words"), message);
1348         }
1349                 break;
1350         default:
1351                 return false;
1352         }
1353
1354         return true;
1355 }
1356
1357
1358 ViewMetricsInfo BufferView::Pimpl::metrics(bool singlepar)
1359 {
1360         // Remove old position cache
1361         theCoords.clear();
1362         BufferView & bv = *bv_;
1363         LyXText * const text = bv.text();
1364         lyx::pit_type size = int(text->paragraphs().size());
1365
1366         if (anchor_ref_ > int(text->paragraphs().size() - 1)) {
1367                 anchor_ref_ = int(text->paragraphs().size() - 1);
1368                 offset_ref_ = 0;
1369         }
1370
1371         lyx::pit_type const pit = anchor_ref_;
1372         int pit1 = pit;
1373         int pit2 = pit;
1374         size_t const npit = text->paragraphs().size();
1375
1376         // Rebreak anchor paragraph. In Single Paragraph mode, rebreak only
1377         // the (main text, not inset!) paragraph containing the cursor.
1378         // (if this paragraph contains insets etc., rebreaking will 
1379         // recursively descend)
1380         if (!singlepar || pit == cursor_.bottom().pit())
1381                 text->redoParagraph(pit);
1382         int y0 = text->getPar(pit).ascent() - offset_ref_;
1383
1384         // Redo paragraphs above anchor if necessary; again, in Single Par
1385         // mode, only if we encounter the (main text) one having the cursor.
1386         int y1 = y0;
1387         while (y1 > 0 && pit1 > 0) {
1388                 y1 -= text->getPar(pit1).ascent();
1389                 --pit1;
1390                 if (!singlepar || pit1 == cursor_.bottom().pit())
1391                         text->redoParagraph(pit1);
1392                 y1 -= text->getPar(pit1).descent();
1393         }
1394
1395
1396         // Take care of ascent of first line
1397         y1 -= text->getPar(pit1).ascent();
1398
1399         // Normalize anchor for next time
1400         anchor_ref_ = pit1;
1401         offset_ref_ = -y1;
1402
1403         // Grey at the beginning is ugly
1404         if (pit1 == 0 && y1 > 0) {
1405                 y0 -= y1;
1406                 y1 = 0;
1407                 anchor_ref_ = 0;
1408         }
1409
1410         // Redo paragraphs below the anchor if necessary. Single par mode:
1411         // only the one containing the cursor if encountered.
1412         int y2 = y0;
1413         while (y2 < bv.workHeight() && pit2 < int(npit) - 1) {
1414                 y2 += text->getPar(pit2).descent();
1415                 ++pit2;
1416                 if (!singlepar || pit2 == cursor_.bottom().pit())
1417                         text->redoParagraph(pit2);
1418                 y2 += text->getPar(pit2).ascent();
1419         }
1420
1421         // Take care of descent of last line
1422         y2 += text->getPar(pit2).descent();
1423
1424         // The coordinates of all these paragraphs are correct, cache them
1425         int y = y1;
1426         for (lyx::pit_type pit = pit1; pit <= pit2; ++pit) {
1427                 y += text->getPar(pit).ascent();
1428                 theCoords.parPos()[text][pit] = Point(0, y);
1429                 if (singlepar && pit == cursor_.bottom().pit()) {
1430                         // In Single Paragraph mode, collect here the 
1431                         // y1 and y2 of the (one) paragraph the cursor is in
1432                         y1 = y - text->getPar(pit).ascent();
1433                         y2 = y + text->getPar(pit).descent();
1434                 }
1435                 y += text->getPar(pit).descent();
1436         }
1437
1438         if (singlepar) {
1439                 // collect cursor paragraph iter bounds
1440                 pit1 = cursor_.bottom().pit();
1441                 pit2 = cursor_.bottom().pit();
1442         }
1443         
1444         lyxerr[Debug::DEBUG]
1445                 << BOOST_CURRENT_FUNCTION
1446                 << " y1: " << y1
1447                 << " y2: " << y2
1448                 << " pit1: " << pit1
1449                 << " pit2: " << pit2
1450                 << " npit: " << npit
1451                 << " singlepar: " << singlepar
1452                 << "size: " << size
1453                 << endl;
1454
1455         return ViewMetricsInfo(pit1, pit2, y1, y2, singlepar, size);
1456 }