]> git.lyx.org Git - lyx.git/blob - src/BufferView_pimpl.C
d4785515517666d6d8ac2304ec28e75c3be653bf
[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 Braustein
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 "cursor.h"
27 #include "debug.h"
28 #include "dispatchresult.h"
29 #include "factory.h"
30 #include "FloatList.h"
31 #include "funcrequest.h"
32 #include "gettext.h"
33 #include "intl.h"
34 #include "iterators.h"
35 #include "lyx_cb.h" // added for Dispatch functions
36 #include "lyx_main.h"
37 #include "lyxfind.h"
38 #include "lyxfunc.h"
39 #include "lyxtext.h"
40 #include "lyxrc.h"
41 #include "lastfiles.h"
42 #include "paragraph.h"
43 #include "paragraph_funcs.h"
44 #include "ParagraphParameters.h"
45 #include "undo.h"
46 #include "vspace.h"
47
48 #include "insets/insetfloatlist.h"
49 #include "insets/insetref.h"
50
51 #include "frontends/Alert.h"
52 #include "frontends/Dialogs.h"
53 #include "frontends/FileDialog.h"
54 #include "frontends/LyXView.h"
55 #include "frontends/LyXScreenFactory.h"
56 #include "frontends/screen.h"
57 #include "frontends/WorkArea.h"
58 #include "frontends/WorkAreaFactory.h"
59
60 #include "graphics/Previews.h"
61
62 #include "mathed/formulabase.h"
63
64 #include "support/filetools.h"
65 #include "support/globbing.h"
66 #include "support/path_defines.h"
67 #include "support/tostr.h"
68
69 #include <boost/bind.hpp>
70
71 using bv_funcs::currentState;
72
73 using lyx::pos_type;
74
75 using lyx::support::AddPath;
76 using lyx::support::bformat;
77 using lyx::support::FileFilterList;
78 using lyx::support::FileSearch;
79 using lyx::support::IsDirWriteable;
80 using lyx::support::MakeDisplayPath;
81 using lyx::support::strToUnsignedInt;
82 using lyx::support::system_lyxdir;
83
84 using std::endl;
85 using std::make_pair;
86 using std::min;
87 using std::string;
88
89
90 extern BufferList bufferlist;
91
92
93 namespace {
94
95 unsigned int const saved_positions_num = 20;
96
97 // All the below connection objects are needed because of a bug in some
98 // versions of GCC (<=2.96 are on the suspects list.) By having and assigning
99 // to these connections we avoid a segfault upon startup, and also at exit.
100 // (Lgb)
101
102 boost::signals::connection dispatchcon;
103 boost::signals::connection timecon;
104 boost::signals::connection doccon;
105 boost::signals::connection resizecon;
106 boost::signals::connection kpresscon;
107 boost::signals::connection selectioncon;
108 boost::signals::connection lostcon;
109
110
111 } // anon namespace
112
113
114 BufferView::Pimpl::Pimpl(BufferView * bv, LyXView * owner,
115              int xpos, int ypos, int width, int height)
116         : bv_(bv), owner_(owner), buffer_(0), cursor_timeout(400),
117           using_xterm_cursor(false), cursor_(bv)
118 {
119         xsel_cache_.set = false;
120
121         workarea_.reset(WorkAreaFactory::create(xpos, ypos, width, height));
122         screen_.reset(LyXScreenFactory::create(workarea()));
123
124         // Setup the signals
125         doccon = workarea().scrollDocView
126                 .connect(boost::bind(&BufferView::Pimpl::scrollDocView, this, _1));
127         resizecon = workarea().workAreaResize
128                 .connect(boost::bind(&BufferView::Pimpl::workAreaResize, this));
129         dispatchcon = workarea().dispatch
130                 .connect(boost::bind(&BufferView::Pimpl::workAreaDispatch, this, _1));
131         kpresscon = workarea().workAreaKeyPress
132                 .connect(boost::bind(&BufferView::Pimpl::workAreaKeyPress, this, _1, _2));
133         selectioncon = workarea().selectionRequested
134                 .connect(boost::bind(&BufferView::Pimpl::selectionRequested, this));
135         lostcon = workarea().selectionLost
136                 .connect(boost::bind(&BufferView::Pimpl::selectionLost, this));
137
138         timecon = cursor_timeout.timeout
139                 .connect(boost::bind(&BufferView::Pimpl::cursorToggle, this));
140         cursor_timeout.start();
141         saved_positions.resize(saved_positions_num);
142 }
143
144
145 void BufferView::Pimpl::addError(ErrorItem const & ei)
146 {
147         errorlist_.push_back(ei);
148 }
149
150
151 void BufferView::Pimpl::showReadonly(bool)
152 {
153         owner_->updateWindowTitle();
154         owner_->getDialogs().updateBufferDependent(false);
155 }
156
157
158 void BufferView::Pimpl::connectBuffer(Buffer & buf)
159 {
160         if (errorConnection_.connected())
161                 disconnectBuffer();
162
163         errorConnection_ = buf.error.connect(boost::bind(&BufferView::Pimpl::addError, this, _1));
164         messageConnection_ = buf.message.connect(boost::bind(&LyXView::message, owner_, _1));
165         busyConnection_ = buf.busy.connect(boost::bind(&LyXView::busy, owner_, _1));
166         titleConnection_ = buf.updateTitles.connect(boost::bind(&LyXView::updateWindowTitle, owner_));
167         timerConnection_ = buf.resetAutosaveTimers.connect(boost::bind(&LyXView::resetAutosaveTimer, owner_));
168         readonlyConnection_ = buf.readonly.connect(boost::bind(&BufferView::Pimpl::showReadonly, this, _1));
169         closingConnection_ = buf.closing.connect(boost::bind(&BufferView::Pimpl::buffer, this, (Buffer *)0));
170 }
171
172
173 void BufferView::Pimpl::disconnectBuffer()
174 {
175         errorConnection_.disconnect();
176         messageConnection_.disconnect();
177         busyConnection_.disconnect();
178         titleConnection_.disconnect();
179         timerConnection_.disconnect();
180         readonlyConnection_.disconnect();
181         closingConnection_.disconnect();
182 }
183
184
185 bool BufferView::Pimpl::newFile(string const & filename,
186                                 string const & tname,
187                                 bool isNamed)
188 {
189         Buffer * b = ::newFile(filename, tname, isNamed);
190         buffer(b);
191         return true;
192 }
193
194
195 bool BufferView::Pimpl::loadLyXFile(string const & filename, bool tolastfiles)
196 {
197         // get absolute path of file and add ".lyx" to the filename if
198         // necessary
199         string s = FileSearch(string(), filename, "lyx");
200
201         bool const found = !s.empty();
202
203         if (!found)
204                 s = filename;
205
206         // file already open?
207         if (bufferlist.exists(s)) {
208                 string const file = MakeDisplayPath(s, 20);
209                 string text = bformat(_("The document %1$s is already "
210                                         "loaded.\n\nDo you want to revert "
211                                         "to the saved version?"), file);
212                 int const ret = Alert::prompt(_("Revert to saved document?"),
213                         text, 0, 1,  _("&Revert"), _("&Switch to document"));
214
215                 if (ret != 0) {
216                         buffer(bufferlist.getBuffer(s));
217                         return true;
218                 } else {
219                         // FIXME: should be LFUN_REVERT
220                         if (!bufferlist.close(bufferlist.getBuffer(s), false))
221                                 return false;
222                         // Fall through to new load. (Asger)
223                 }
224         }
225
226         Buffer * b;
227
228         if (found) {
229                 b = bufferlist.newBuffer(s);
230                 connectBuffer(*b);
231                 if (!::loadLyXFile(b, s)) {
232                         bufferlist.release(b);
233                         return false;
234                 }
235         } else {
236                 string text = bformat(_("The document %1$s does not yet "
237                                         "exist.\n\nDo you want to create "
238                                         "a new document?"), s);
239                 int const ret = Alert::prompt(_("Create new document?"),
240                          text, 0, 1, _("&Create"), _("Cancel"));
241
242                 if (ret == 0)
243                         b = ::newFile(s, string(), true);
244                 else
245                         return false;
246         }
247
248         buffer(b);
249         bv_->showErrorList(_("Parse"));
250
251         if (tolastfiles)
252                 LyX::ref().lastfiles().newFile(b->fileName());
253
254         return true;
255 }
256
257
258 WorkArea & BufferView::Pimpl::workarea() const
259 {
260         return *workarea_.get();
261 }
262
263
264 LyXScreen & BufferView::Pimpl::screen() const
265 {
266         return *screen_.get();
267 }
268
269
270 Painter & BufferView::Pimpl::painter() const
271 {
272         return workarea().getPainter();
273 }
274
275
276 void BufferView::Pimpl::top_y(int y)
277 {
278         top_y_ = y;
279 }
280
281
282 int BufferView::Pimpl::top_y() const
283 {
284         return top_y_;
285 }
286
287
288 void BufferView::Pimpl::buffer(Buffer * b)
289 {
290         lyxerr[Debug::INFO] << "Setting buffer in BufferView ("
291                             << b << ')' << endl;
292         if (buffer_) {
293                 disconnectBuffer();
294                 //delete bv_->text();
295                 //bv_->setText(0);
296         }
297
298         // set current buffer
299         buffer_ = b;
300
301         top_y_ = 0;
302
303         // if we're quitting lyx, don't bother updating stuff
304         if (quitting)
305                 return;
306
307         // if we are closing the buffer, use the first buffer as current
308         if (!buffer_)
309                 buffer_ = bufferlist.first();
310
311         if (buffer_) {
312                 lyxerr[Debug::INFO] << "Buffer addr: " << buffer_ << endl;
313                 connectBuffer(*buffer_);
314
315                 buffer_->text().init(bv_);
316                 buffer_->text().textwidth_ = workarea().workWidth();
317                 buffer_->text().fullRebreak();
318
319                 // If we don't have a text object for this, we make one
320                 if (bv_->text() == 0)
321                         resizeCurrentBuffer();
322
323                 // FIXME: needed when ?
324                 fitCursor();
325
326                 // Buffer-dependent dialogs should be updated or
327                 // hidden. This should go here because some dialogs (eg ToC)
328                 // require bv_->text.
329                 owner_->getDialogs().updateBufferDependent(true);
330         } else {
331                 lyxerr[Debug::INFO] << "  No Buffer!" << endl;
332                 owner_->getDialogs().hideBufferDependent();
333         }
334
335         update();
336         updateScrollbar();
337         owner_->updateMenubar();
338         owner_->updateToolbar();
339         owner_->updateLayoutChoice();
340         owner_->updateWindowTitle();
341
342         // Don't forget to update the Layout
343         if (buffer_)
344                 owner_->setLayout(bv_->text()->cursorPar()->layout()->name());
345
346         if (lyx::graphics::Previews::activated() && buffer_)
347                 lyx::graphics::Previews::get().generateBufferPreviews(*buffer_);
348 }
349
350
351 bool BufferView::Pimpl::fitCursor()
352 {
353         lyxerr << "BufferView::Pimpl::fitCursor." << endl;
354         if (screen().fitCursor(bv_)) {
355                 updateScrollbar();
356                 return true;
357         }
358         return false;
359 }
360
361
362 void BufferView::Pimpl::redoCurrentBuffer()
363 {
364         lyxerr[Debug::INFO] << "BufferView::redoCurrentBuffer" << endl;
365         if (buffer_ && bv_->text()) {
366                 resizeCurrentBuffer();
367                 updateScrollbar();
368                 owner_->updateLayoutChoice();
369         }
370 }
371
372
373 void BufferView::Pimpl::resizeCurrentBuffer()
374 {
375         lyxerr[Debug::INFO] << "resizeCurrentBuffer" << endl;
376
377         int par = -1;
378         int selstartpar = -1;
379         int selendpar = -1;
380
381         pos_type pos = 0;
382         pos_type selstartpos = 0;
383         pos_type selendpos = 0;
384         bool selection = false;
385         bool mark_set  = false;
386
387         owner_->busy(true);
388
389         owner_->message(_("Formatting document..."));
390
391         lyxerr << "### resizeCurrentBuffer: text " << bv_->text() << endl;
392         if (!bv_->text())
393                 return;
394
395         par = bv_->text()->cursor.par();
396         pos = bv_->text()->cursor.pos();
397         selstartpar = bv_->text()->selStart().par();
398         selstartpos = bv_->text()->selStart().pos();
399         selendpar = bv_->text()->selEnd().par();
400         selendpos = bv_->text()->selEnd().pos();
401         selection = bv_->text()->selection.set();
402         mark_set = bv_->text()->selection.mark();
403         bv_->text()->textwidth_ = bv_->workWidth();
404         bv_->text()->fullRebreak();
405         update();
406
407         if (par != -1) {
408                 bv_->text()->selection.set(true);
409                 // At this point just to avoid the Delete-Empty-Paragraph-
410                 // Mechanism when setting the cursor.
411                 bv_->text()->selection.mark(mark_set);
412                 if (selection) {
413                         bv_->text()->setCursor(selstartpar, selstartpos);
414                         bv_->text()->selection.cursor = bv_->text()->cursor;
415                         bv_->text()->setCursor(selendpar, selendpos);
416                         bv_->text()->setSelection();
417                         bv_->text()->setCursor(par, pos);
418                 } else {
419                         bv_->text()->setCursor(par, pos);
420                         bv_->text()->selection.cursor = bv_->text()->cursor;
421                         bv_->text()->selection.set(false);
422                 }
423         }
424
425         fitCursor();
426
427         switchKeyMap();
428         owner_->busy(false);
429
430         // reset the "Formatting..." message
431         owner_->clearMessage();
432
433         updateScrollbar();
434 }
435
436
437 void BufferView::Pimpl::updateScrollbar()
438 {
439         if (!bv_->text()) {
440                 lyxerr[Debug::GUI] << "no text in updateScrollbar" << endl;
441                 workarea().setScrollbarParams(0, 0, 0);
442                 return;
443         }
444
445         LyXText const & t = *bv_->text();
446
447         lyxerr[Debug::GUI] << "Updating scrollbar: h " << t.height << ", top_y() "
448                 << top_y() << ", default height " << defaultRowHeight() << endl;
449
450         workarea().setScrollbarParams(t.height, top_y(), defaultRowHeight());
451 }
452
453
454 void BufferView::Pimpl::scrollDocView(int value)
455 {
456         lyxerr[Debug::GUI] << "scrollDocView of " << value << endl;
457
458         if (!buffer_)
459                 return;
460
461         screen().hideCursor();
462
463         top_y(value);
464         screen().redraw(*bv_);
465
466         if (!lyxrc.cursor_follows_scrollbar)
467                 return;
468
469         int const height = defaultRowHeight();
470         int const first = top_y() + height;
471         int const last = top_y() + workarea().workHeight() - height;
472
473         LyXText * text = bv_->text();
474         if (text->cursorY() < first)
475                 text->setCursorFromCoordinates(0, first);
476         else if (text->cursorY() > last)
477                 text->setCursorFromCoordinates(0, last);
478
479         owner_->updateLayoutChoice();
480 }
481
482
483 void BufferView::Pimpl::scroll(int lines)
484 {
485         if (!buffer_)
486                 return;
487
488         LyXText const * t = bv_->text();
489         int const line_height = defaultRowHeight();
490
491         // The new absolute coordinate
492         int new_top_y = top_y() + lines * line_height;
493
494         // Restrict to a valid value
495         new_top_y = std::min(t->height - 4 * line_height, new_top_y);
496         new_top_y = std::max(0, new_top_y);
497
498         scrollDocView(new_top_y);
499
500         // Update the scrollbar.
501         workarea().setScrollbarParams(t->height, top_y(), defaultRowHeight());
502 }
503
504
505 void BufferView::Pimpl::workAreaKeyPress(LyXKeySymPtr key,
506                                          key_modifier::state state)
507 {
508         bv_->owner()->getLyXFunc().processKeySym(key, state);
509
510         /* This is perhaps a bit of a hack. When we move
511          * around, or type, it's nice to be able to see
512          * the cursor immediately after the keypress. So
513          * we reset the toggle timeout and force the visibility
514          * of the cursor. Note we cannot do this inside
515          * dispatch() itself, because that's called recursively.
516          */
517         if (available()) {
518                 cursor_timeout.restart();
519                 screen().showCursor(*bv_);
520         }
521 }
522
523
524 void BufferView::Pimpl::selectionRequested()
525 {
526         static string sel;
527
528         if (!available())
529                 return;
530
531         LyXText * text = bv_->getLyXText();
532
533         if (!text->selection.set()) {
534                 xsel_cache_.set = false;
535                 return;
536         }
537
538         if (!xsel_cache_.set ||
539             text->cursor != xsel_cache_.cursor ||
540             text->selection.cursor != xsel_cache_.selection_cursor)
541         {
542                 xsel_cache_.cursor = text->cursor;
543                 xsel_cache_.selection_cursor = text->selection.cursor;
544                 xsel_cache_.set = text->selection.set();
545                 sel = text->selectionAsString(*bv_->buffer(), false);
546                 if (!sel.empty())
547                         workarea().putClipboard(sel);
548         } 
549 }
550
551
552 void BufferView::Pimpl::selectionLost()
553 {
554         if (available()) {
555                 screen().hideCursor();
556                 bv_->getLyXText()->clearSelection();
557                 xsel_cache_.set = false;
558         }
559 }
560
561
562 void BufferView::Pimpl::workAreaResize()
563 {
564         static int work_area_width;
565         static int work_area_height;
566
567         bool const widthChange = workarea().workWidth() != work_area_width;
568         bool const heightChange = workarea().workHeight() != work_area_height;
569
570         // update from work area
571         work_area_width = workarea().workWidth();
572         work_area_height = workarea().workHeight();
573
574         if (buffer_ != 0) {
575                 if (widthChange) {
576                         // The visible LyXView need a resize
577                         resizeCurrentBuffer();
578                 }
579         }
580
581         if (widthChange || heightChange)
582                 update();
583
584         // always make sure that the scrollbar is sane.
585         updateScrollbar();
586         owner_->updateLayoutChoice();
587 }
588
589
590 void BufferView::Pimpl::update()
591 {
592         //lyxerr << "BufferView::update()" << endl;
593         // fix cursor coordinate cache in case something went wrong
594
595         // check needed to survive LyX startup
596         if (bv_->getLyXText()) {
597                 // update all 'visible' paragraphs
598                 ParagraphList::iterator beg;
599                 ParagraphList::iterator end;
600                 getParsInRange(buffer_->paragraphs(),
601                                top_y(), top_y() + workarea().workHeight(),
602                                beg, end);
603                 bv_->text()->redoParagraphs(beg, end);
604                 updateScrollbar();
605         }
606         screen().redraw(*bv_);
607 }
608
609
610 // Callback for cursor timer
611 void BufferView::Pimpl::cursorToggle()
612 {
613         if (!buffer_) {
614                 cursor_timeout.restart();
615                 return;
616         }
617
618         screen().toggleCursor(*bv_);
619         cursor_timeout.restart();
620 }
621
622
623 bool BufferView::Pimpl::available() const
624 {
625         return buffer_ && bv_->text();
626 }
627
628
629 Change const BufferView::Pimpl::getCurrentChange()
630 {
631         if (!bv_->buffer()->params().tracking_changes)
632                 return Change(Change::UNCHANGED);
633
634         LyXText * text = bv_->getLyXText();
635
636         if (!text->selection.set())
637                 return Change(Change::UNCHANGED);
638
639         return text->getPar(text->selStart())
640                 ->lookupChangeFull(text->selStart().pos());
641 }
642
643
644 void BufferView::Pimpl::savePosition(unsigned int i)
645 {
646         if (i >= saved_positions_num)
647                 return;
648         saved_positions[i] = Position(buffer_->fileName(),
649                                       bv_->text()->cursorPar()->id(),
650                                       bv_->text()->cursor.pos());
651         if (i > 0)
652                 owner_->message(bformat(_("Saved bookmark %1$s"), tostr(i)));
653 }
654
655
656 void BufferView::Pimpl::restorePosition(unsigned int i)
657 {
658         if (i >= saved_positions_num)
659                 return;
660
661         string const fname = saved_positions[i].filename;
662
663         bv_->text()->clearSelection();
664
665         if (fname != buffer_->fileName()) {
666                 Buffer * b = 0;
667                 if (bufferlist.exists(fname))
668                         b = bufferlist.getBuffer(fname);
669                 else {
670                         b = bufferlist.newBuffer(fname);
671                         ::loadLyXFile(b, fname); // don't ask, just load it
672                 }
673                 if (b)
674                         buffer(b);
675         }
676
677         ParIterator par = buffer_->getParFromID(saved_positions[i].par_id);
678         if (par == buffer_->par_iterator_end())
679                 return;
680
681         bv_->text()->setCursor(par.pit(),
682                              min(par->size(), saved_positions[i].par_pos));
683
684         if (i > 0)
685                 owner_->message(bformat(_("Moved to bookmark %1$s"), tostr(i)));
686 }
687
688
689 bool BufferView::Pimpl::isSavedPosition(unsigned int i)
690 {
691         return i < saved_positions_num && !saved_positions[i].filename.empty();
692 }
693
694
695 void BufferView::Pimpl::switchKeyMap()
696 {
697         if (!lyxrc.rtl_support)
698                 return;
699
700         Intl & intl = owner_->getIntl();
701         if (bv_->getLyXText()->real_current_font.isRightToLeft()) {
702                 if (intl.keymap == Intl::PRIMARY)
703                         intl.KeyMapSec();
704         } else {
705                 if (intl.keymap == Intl::SECONDARY)
706                         intl.KeyMapPrim();
707         }
708 }
709
710
711 void BufferView::Pimpl::center()
712 {
713         LyXText * text = bv_->text();
714
715         text->clearSelection();
716         int const half_height = workarea().workHeight() / 2;
717         int new_y = std::max(0, text->cursorY() - half_height);
718
719         // FIXME: look at this comment again ...
720         // This updates top_y() but means the fitCursor() call
721         // from the update(FITCUR) doesn't realise that we might
722         // have moved (e.g. from GOTOPARAGRAPH), so doesn't cause
723         // the scrollbar to be updated as it should, so we have
724         // to do it manually. Any operation that does a center()
725         // and also might have moved top_y() must make sure to call
726         // updateScrollbar() currently. Never mind that this is a
727         // pretty obfuscated way of updating t->top_y()
728         top_y(new_y);
729 }
730
731
732 void BufferView::Pimpl::stuffClipboard(string const & stuff) const
733 {
734         workarea().putClipboard(stuff);
735 }
736
737
738 InsetOld * BufferView::Pimpl::getInsetByCode(InsetOld::Code code)
739 {
740 #if 0
741         LyXCursor cursor = bv_->getLyXText()->cursor;
742         Buffer::inset_iterator it =
743                 find_if(Buffer::inset_iterator(
744                         cursorPar(), cursor.pos()),
745                         buffer_->inset_iterator_end(),
746                         lyx::compare_memfun(&Inset::lyxCode, code));
747         return it != buffer_->inset_iterator_end() ? (*it) : 0;
748 #else
749         // Ok, this is a little bit too brute force but it
750         // should work for now. Better infrastructure is coming. (Lgb)
751
752         Buffer * b = bv_->buffer();
753         LyXText * text =  bv_->getLyXText();
754
755         Buffer::inset_iterator beg = b->inset_iterator_begin();
756         Buffer::inset_iterator end = b->inset_iterator_end();
757
758         bool cursor_par_seen = false;
759
760         for (; beg != end; ++beg) {
761                 if (beg.getPar() == text->cursorPar()) {
762                         cursor_par_seen = true;
763                 }
764                 if (cursor_par_seen) {
765                         if (beg.getPar() == text->cursorPar()
766                             && beg.getPos() >= text->cursor.pos()) {
767                                 break;
768                         } else if (beg.getPar() != text->cursorPar()) {
769                                 break;
770                         }
771                 }
772
773         }
774         if (beg != end) {
775                 // Now find the first inset that matches code.
776                 for (; beg != end; ++beg) {
777                         if (beg->lyxCode() == code) {
778                                 return &(*beg);
779                         }
780                 }
781         }
782         return 0;
783 #endif
784 }
785
786
787 void BufferView::Pimpl::MenuInsertLyXFile(string const & filen)
788 {
789         string filename = filen;
790
791         if (filename.empty()) {
792                 // Launch a file browser
793                 string initpath = lyxrc.document_path;
794
795                 if (available()) {
796                         string const trypath = owner_->buffer()->filePath();
797                         // If directory is writeable, use this as default.
798                         if (IsDirWriteable(trypath))
799                                 initpath = trypath;
800                 }
801
802                 FileDialog fileDlg(_("Select LyX document to insert"),
803                         LFUN_FILE_INSERT,
804                         make_pair(string(_("Documents|#o#O")),
805                                   string(lyxrc.document_path)),
806                         make_pair(string(_("Examples|#E#e")),
807                                   string(AddPath(system_lyxdir(), "examples"))));
808
809                 FileDialog::Result result =
810                         fileDlg.open(initpath,
811                                      FileFilterList(_("LyX Documents (*.lyx)")),
812                                      string());
813
814                 if (result.first == FileDialog::Later)
815                         return;
816
817                 filename = result.second;
818
819                 // check selected filename
820                 if (filename.empty()) {
821                         owner_->message(_("Canceled."));
822                         return;
823                 }
824         }
825
826         // get absolute path of file and add ".lyx" to the filename if
827         // necessary
828         filename = FileSearch(string(), filename, "lyx");
829
830         string const disp_fn = MakeDisplayPath(filename);
831         owner_->message(bformat(_("Inserting document %1$s..."), disp_fn));
832         if (bv_->insertLyXFile(filename))
833                 owner_->message(bformat(_("Document %1$s inserted."),
834                                         disp_fn));
835         else
836                 owner_->message(bformat(_("Could not insert document %1$s"),
837                                         disp_fn));
838 }
839
840
841 void BufferView::Pimpl::trackChanges()
842 {
843         Buffer * buf(bv_->buffer());
844         bool const tracking(buf->params().tracking_changes);
845
846         if (!tracking) {
847                 ParIterator const end = buf->par_iterator_end();
848                 for (ParIterator it = buf->par_iterator_begin(); it != end; ++it)
849                         it->trackChanges();
850                 buf->params().tracking_changes = true;
851
852                 // we cannot allow undos beyond the freeze point
853                 buf->undostack().clear();
854         } else {
855                 update();
856                 bv_->text()->setCursor(0, 0);
857 #warning changes FIXME
858                 bool found = lyx::find::findNextChange(bv_);
859                 if (found) {
860                         owner_->getDialogs().show("changes");
861                         return;
862                 }
863
864                 ParIterator const end = buf->par_iterator_end();
865                 for (ParIterator it = buf->par_iterator_begin(); it != end; ++it)
866                         it->untrackChanges();
867                 buf->params().tracking_changes = false;
868         }
869
870         buf->redostack().clear();
871 }
872
873 #warning remove me
874 LCursor theTempCursor(0);
875
876 namespace {
877
878         InsetOld * insetFromCoords(BufferView * bv, int x, int y)
879         {
880                 lyxerr << "insetFromCoords" << endl;
881                 LyXText * text = bv->text();
882                 InsetOld * inset = 0;
883                 theTempCursor = LCursor(bv);
884                 while (true) {
885                         InsetOld * const inset_hit = text->checkInsetHit(x, y);
886                         if (!inset_hit) {
887                                 lyxerr << "no further inset hit" << endl;
888                                 break;
889                         }
890                         inset = inset_hit;
891                         if (!inset->descendable()) {
892                                 lyxerr << "not descendable" << endl;
893                                 break;
894                         }
895                         int const cell = inset->getCell(x, y + bv->top_y());
896                         if (cell == -1)
897                                 break;
898                         text = inset_hit->getText(cell);
899                         lyxerr << "Hit inset: " << inset << " at x: " << x
900                                 << " text: " << text << " y: " << y << endl;
901                         theTempCursor.push(static_cast<UpdatableInset*>(inset));
902                 }
903                 lyxerr << "theTempCursor: " << theTempCursor << endl;
904                 return inset;
905         }
906
907 }
908
909
910 bool BufferView::Pimpl::workAreaDispatch(FuncRequest const & cmd)
911 {
912         switch (cmd.action) {
913         case LFUN_MOUSE_MOTION: {
914                 if (!available())
915                         return false;
916                 FuncRequest cmd1(cmd, bv_);
917                 UpdatableInset * inset = bv_->cursor().innerInset();
918                 DispatchResult res;
919                 if (inset) {
920                         cmd1.x -= inset->x();
921                         cmd1.y -= inset->y();
922                         res = inset->dispatch(cmd1);
923                 } else {
924                         cmd1.y += bv_->top_y();
925                         res = bv_->cursor().innerText()->dispatch(cmd1);
926                 }
927
928                 if (bv_->fitCursor() || res.update()) {
929                         bv_->update();
930                         bv_->cursor().updatePos();
931                 }
932
933                 return true;
934         }
935
936         case LFUN_MOUSE_PRESS:
937         case LFUN_MOUSE_RELEASE:
938         case LFUN_MOUSE_DOUBLE:
939         case LFUN_MOUSE_TRIPLE: {
940                 // We pass those directly to the Bufferview, since
941                 // otherwise selection handling breaks down
942
943                 // Doesn't go through lyxfunc, so we need to update
944                 // the layout choice etc. ourselves
945
946                 // e.g. Qt mouse press when no buffer
947                 if (!available())
948                         return false;
949
950                 screen().hideCursor();
951
952                 // either the inset under the cursor or the surrounding LyXText will
953                 // handle this event.
954
955                 // built temporary path to inset
956                 InsetOld * inset = insetFromCoords(bv_, cmd.x, cmd.y);
957                 FuncRequest cmd1(cmd, bv_);
958                 DispatchResult res;
959
960                 // try to dispatch to that inset
961                 if (inset) {
962                         FuncRequest cmd2 = cmd1;
963                         lyxerr << "dispatching action " << cmd2.action
964                                << " to inset " << inset << endl;
965                         cmd2.x -= inset->x();
966                         cmd2.y -= inset->y();
967                         res = inset->dispatch(cmd2);
968                         if (res.update()) {
969                                 bv_->update();
970                                 bv_->cursor().updatePos();
971                         }
972                         res.update(false);
973                         switch (res.val()) {
974                                 case FINISHED:
975                                 case FINISHED_RIGHT:
976                                 case FINISHED_UP:
977                                 case FINISHED_DOWN:
978                                         theTempCursor.pop();
979                                         bv_->cursor() = theTempCursor;
980                                         bv_->cursor().innerText()->setCursorFromCoordinates(cmd.x, top_y() + cmd.y);
981                                         if (bv_->fitCursor())
982                                                 bv_->update();
983                                         return true;
984                                 default:
985                                         lyxerr << "not dispatched by inner inset val: " << res.val() << endl;
986                                         break;
987                         }
988                 }
989
990                 // otherwise set cursor to surrounding LyXText
991                 if (!res.dispatched()) {
992                         lyxerr << "temp cursor is: " << theTempCursor << endl;
993                         lyxerr << "dispatching " << cmd1
994                                << " to surrounding LyXText "
995                                << theTempCursor.innerText() << endl;
996                         bv_->cursor() = theTempCursor;
997                         cmd1.y += bv_->top_y();
998                         res = bv_->cursor().innerText()->dispatch(cmd1);
999                         if (bv_->fitCursor() || res.update())
1000                                 bv_->update();
1001
1002                         //return DispatchResult(true, true);
1003                 }
1004                 // see workAreaKeyPress
1005                 cursor_timeout.restart();
1006                 screen().showCursor(*bv_);
1007
1008                 // skip these when selecting
1009                 if (cmd.action != LFUN_MOUSE_MOTION) {
1010                         owner_->updateLayoutChoice();
1011                         owner_->updateToolbar();
1012                 }
1013
1014                 // slight hack: this is only called currently when we
1015                 // clicked somewhere, so we force through the display
1016                 // of the new status here.
1017                 owner_->clearMessage();
1018                 return true;
1019         }
1020
1021         default:
1022                 owner_->dispatch(cmd);
1023                 return true;
1024         }
1025 }
1026
1027
1028 bool BufferView::Pimpl::dispatch(FuncRequest const & ev_in)
1029 {
1030         // Make sure that the cached BufferView is correct.
1031         FuncRequest ev = ev_in;
1032         ev.setView(bv_);
1033
1034         lyxerr[Debug::ACTION] << "BufferView::Pimpl::Dispatch:"
1035                 << " action[" << ev.action << ']'
1036                 << " arg[" << ev.argument << ']'
1037                 << " x[" << ev.x << ']'
1038                 << " y[" << ev.y << ']'
1039                 << " button[" << ev.button() << ']'
1040                 << endl;
1041
1042         LyXTextClass const & tclass = buffer_->params().getLyXTextClass();
1043
1044         switch (ev.action) {
1045
1046         case LFUN_SCROLL_INSET:
1047                 // this is not handled here as this function is only active
1048                 // if we have a locking_inset and that one is (or contains)
1049                 // a tabular-inset
1050                 break;
1051
1052         case LFUN_FILE_INSERT:
1053                 MenuInsertLyXFile(ev.argument);
1054                 break;
1055
1056         case LFUN_FILE_INSERT_ASCII_PARA:
1057                 InsertAsciiFile(bv_, ev.argument, true);
1058                 break;
1059
1060         case LFUN_FILE_INSERT_ASCII:
1061                 InsertAsciiFile(bv_, ev.argument, false);
1062                 break;
1063
1064         case LFUN_FONT_STATE:
1065                 owner_->getLyXFunc().setMessage(currentState(bv_));
1066                 break;
1067
1068         case LFUN_INSERT_LABEL: {
1069                 // Try and generate a valid label
1070                 string const contents = ev.argument.empty() ?
1071                         getPossibleLabel(*bv_) : ev.argument;
1072                 InsetCommandParams icp("label", contents);
1073                 string data = InsetCommandMailer::params2string("label", icp);
1074                 owner_->getDialogs().show("label", data, 0);
1075                 break;
1076         }
1077
1078         case LFUN_BOOKMARK_SAVE:
1079                 savePosition(strToUnsignedInt(ev.argument));
1080                 break;
1081
1082         case LFUN_BOOKMARK_GOTO:
1083                 restorePosition(strToUnsignedInt(ev.argument));
1084                 break;
1085
1086         case LFUN_REF_GOTO: {
1087                 string label = ev.argument;
1088                 if (label.empty()) {
1089                         InsetRef * inset =
1090                                 static_cast<InsetRef*>(getInsetByCode(InsetOld::REF_CODE));
1091                         if (inset) {
1092                                 label = inset->getContents();
1093                                 savePosition(0);
1094                         }
1095                 }
1096
1097                 if (!label.empty())
1098                         bv_->gotoLabel(label);
1099         }
1100         break;
1101
1102         // --- accented characters ---------------------------
1103
1104         case LFUN_UMLAUT:
1105         case LFUN_CIRCUMFLEX:
1106         case LFUN_GRAVE:
1107         case LFUN_ACUTE:
1108         case LFUN_TILDE:
1109         case LFUN_CEDILLA:
1110         case LFUN_MACRON:
1111         case LFUN_DOT:
1112         case LFUN_UNDERDOT:
1113         case LFUN_UNDERBAR:
1114         case LFUN_CARON:
1115         case LFUN_SPECIAL_CARON:
1116         case LFUN_BREVE:
1117         case LFUN_TIE:
1118         case LFUN_HUNG_UMLAUT:
1119         case LFUN_CIRCLE:
1120         case LFUN_OGONEK:
1121                 if (ev.argument.empty()) {
1122                         // As always...
1123                         owner_->getLyXFunc().handleKeyFunc(ev.action);
1124                 } else {
1125                         owner_->getLyXFunc().handleKeyFunc(ev.action);
1126                         owner_->getIntl().getTransManager()
1127                                 .TranslateAndInsert(ev.argument[0], bv_->getLyXText());
1128                         update();
1129                 }
1130                 break;
1131
1132         case LFUN_MATH_MACRO:
1133         case LFUN_MATH_DELIM:
1134         case LFUN_INSERT_MATRIX:
1135         case LFUN_INSERT_MATH:
1136         case LFUN_MATH_IMPORT_SELECTION: // Imports LaTeX from the X selection
1137         case LFUN_MATH_DISPLAY:          // Open or create a displayed math inset
1138         case LFUN_MATH_MODE:             // Open or create an inlined math inset
1139                 mathDispatch(ev);
1140                 break;
1141
1142         case LFUN_INSET_INSERT: {
1143                 // Same as above.
1144                 BOOST_ASSERT(false);
1145                 InsetOld * inset = createInset(ev);
1146                 if (!inset || !insertInset(inset))
1147                         delete inset;
1148                 break;
1149         }
1150
1151         case LFUN_FLOAT_LIST:
1152                 if (tclass.floats().typeExist(ev.argument)) {
1153                         InsetOld * inset = new InsetFloatList(ev.argument);
1154                         if (!insertInset(inset, tclass.defaultLayoutName()))
1155                                 delete inset;
1156                 } else {
1157                         lyxerr << "Non-existent float type: "
1158                                << ev.argument << endl;
1159                 }
1160                 break;
1161
1162         case LFUN_LAYOUT_PARAGRAPH: {
1163                 string data;
1164                 params2string(*bv_->getLyXText()->cursorPar(), data);
1165                 data = "show\n" + data;
1166                 bv_->owner()->getDialogs().show("paragraph", data);
1167                 break;
1168         }
1169
1170         case LFUN_PARAGRAPH_UPDATE:
1171                 updateParagraphDialog();
1172                 break;
1173
1174         case LFUN_PARAGRAPH_APPLY:
1175                 setParagraphParams(*bv_, ev.argument);
1176                 break;
1177
1178         case LFUN_THESAURUS_ENTRY: {
1179                 string arg = ev.argument;
1180
1181                 if (arg.empty()) {
1182                         arg = bv_->getLyXText()->selectionAsString(*buffer_,
1183                                                                    false);
1184
1185                         // FIXME
1186                         if (arg.size() > 100 || arg.empty()) {
1187                                 // Get word or selection
1188                                 bv_->getLyXText()->selectWordWhenUnderCursor(lyx::WHOLE_WORD);
1189                                 arg = bv_->getLyXText()->selectionAsString(*buffer_, false);
1190                                 // FIXME: where is getLyXText()->unselect(bv_) ?
1191                         }
1192                 }
1193
1194                 bv_->owner()->getDialogs().show("thesaurus", arg);
1195                 break;
1196         }
1197
1198         case LFUN_TRACK_CHANGES:
1199                 trackChanges();
1200                 break;
1201
1202         case LFUN_MERGE_CHANGES:
1203                 owner_->getDialogs().show("changes");
1204                 break;
1205
1206         case LFUN_ACCEPT_ALL_CHANGES: {
1207                 bv_->text()->setCursor(0, 0);
1208 #warning FIXME changes
1209                 while (lyx::find::findNextChange(bv_))
1210                         bv_->getLyXText()->acceptChange();
1211                 update();
1212                 break;
1213         }
1214
1215         case LFUN_REJECT_ALL_CHANGES: {
1216                 bv_->text()->setCursor(0, 0);
1217 #warning FIXME changes
1218                 while (lyx::find::findNextChange(bv_))
1219                         bv_->getLyXText()->rejectChange();
1220                 update();
1221                 break;
1222         }
1223
1224         case LFUN_ACCEPT_CHANGE: {
1225                 bv_->getLyXText()->acceptChange();
1226                 update();
1227                 break;
1228         }
1229
1230         case LFUN_REJECT_CHANGE: {
1231                 bv_->getLyXText()->rejectChange();
1232                 update();
1233                 break;
1234         }
1235
1236         case LFUN_WORD_FIND:
1237                 lyx::find::find(ev);
1238                 break;
1239
1240         case LFUN_WORD_REPLACE:
1241                 lyx::find::replace(ev);
1242                 break;
1243
1244         case LFUN_UNKNOWN_ACTION:
1245                 ev.errorMessage(N_("Unknown function!"));
1246                 break;
1247
1248         default:
1249                 return bv_->getLyXText()->dispatch(FuncRequest(ev, bv_)).dispatched();
1250         } // end of switch
1251
1252         return true;
1253 }
1254
1255
1256 bool BufferView::Pimpl::insertInset(InsetOld * inset, string const & lout)
1257 {
1258         // not quite sure if we want this...
1259         bv_->text()->recUndo(bv_->text()->cursor.par());
1260         freezeUndo();
1261
1262         bv_->text()->clearSelection();
1263         if (!lout.empty()) {
1264                 bv_->text()->breakParagraph(bv_->buffer()->paragraphs());
1265
1266                 if (!bv_->text()->cursorPar()->empty()) {
1267                         bv_->text()->cursorLeft(bv_);
1268                         bv_->text()->breakParagraph(bv_->buffer()->paragraphs());
1269                 }
1270
1271                 string lres = lout;
1272                 LyXTextClass const & tclass = buffer_->params().getLyXTextClass();
1273                 bool hasLayout = tclass.hasLayout(lres);
1274
1275                 bv_->text()->setLayout(hasLayout ? lres : tclass.defaultLayoutName());
1276                 bv_->text()->setParagraph(Spacing(), LYX_ALIGN_LAYOUT, string(), 0);
1277         }
1278         bv_->cursor().innerText()->insertInset(inset);
1279         unFreezeUndo();
1280         return true;
1281 }
1282
1283
1284 bool BufferView::Pimpl::ChangeInsets(InsetOld::Code code,
1285                                      string const & from, string const & to)
1286 {
1287         bool need_update = false;
1288         LyXCursor cursor = bv_->text()->cursor;
1289         LyXCursor tmpcursor = cursor;
1290         cursor.par(tmpcursor.par());
1291         cursor.pos(tmpcursor.pos());
1292
1293         ParIterator end = bv_->buffer()->par_iterator_end();
1294         for (ParIterator it = bv_->buffer()->par_iterator_begin();
1295              it != end; ++it) {
1296                 bool changed_inset = false;
1297                 for (InsetList::iterator it2 = it->insetlist.begin();
1298                      it2 != it->insetlist.end(); ++it2) {
1299                         if (it2->inset->lyxCode() == code) {
1300                                 InsetCommand * inset = static_cast<InsetCommand *>(it2->inset);
1301                                 if (inset->getContents() == from) {
1302                                         inset->setContents(to);
1303                                         changed_inset = true;
1304                                 }
1305                         }
1306                 }
1307                 if (changed_inset) {
1308                         need_update = true;
1309
1310                         // FIXME
1311
1312                         // The test it.size()==1 was needed to prevent crashes.
1313                         // How to set the cursor correctly when it.size()>1 ??
1314                         if (it.size() == 1) {
1315                                 bv_->text()->setCursorIntern(bv_->text()->parOffset(it.pit()), 0);
1316                                 bv_->text()->redoParagraph(bv_->text()->cursorPar());
1317                         }
1318                 }
1319         }
1320         bv_->text()->setCursorIntern(cursor.par(), cursor.pos());
1321         return need_update;
1322 }
1323
1324
1325 void BufferView::Pimpl::updateParagraphDialog()
1326 {
1327         if (!bv_->owner()->getDialogs().visible("paragraph"))
1328                 return;
1329         Paragraph const & par = *bv_->getLyXText()->cursorPar();
1330         string data;
1331         params2string(par, data);
1332
1333         // Will the paragraph accept changes from the dialog?
1334         InsetOld * const inset = par.inInset();
1335         bool const accept =
1336                 !(inset && inset->forceDefaultParagraphs(inset));
1337
1338         data = "update " + tostr(accept) + '\n' + data;
1339         bv_->owner()->getDialogs().update("paragraph", data);
1340 }