]> git.lyx.org Git - lyx.git/blob - src/text2.C
reduce some debug output
[lyx.git] / src / text2.C
1 /**
2  * \file text2.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 Lars Gullik Bjønnes
8  * \author Alfredo Braunstein
9  * \author Jean-Marc Lasgouttes
10  * \author Angus Leeming
11  * \author John Levon
12  * \author André Pönitz
13  * \author Allan Rae
14  * \author Dekel Tsur
15  * \author Jürgen Vigna
16  *
17  * Full author contact details are available in file CREDITS.
18  */
19
20 #include <config.h>
21
22 #include "lyxtext.h"
23
24 #include "buffer.h"
25 #include "buffer_funcs.h"
26 #include "bufferparams.h"
27 #include "BufferView.h"
28 #include "Bullet.h"
29 #include "coordcache.h"
30 #include "cursor.h"
31 #include "CutAndPaste.h"
32 #include "debug.h"
33 #include "dispatchresult.h"
34 #include "errorlist.h"
35 #include "funcrequest.h"
36 #include "gettext.h"
37 #include "language.h"
38 #include "LColor.h"
39 #include "lyxrc.h"
40 #include "lyxrow.h"
41 #include "lyxrow_funcs.h"
42 #include "paragraph.h"
43 #include "paragraph_funcs.h"
44 #include "ParagraphParameters.h"
45 #include "pariterator.h"
46 #include "undo.h"
47 #include "vspace.h"
48
49 #include "frontends/font_metrics.h"
50 #include "frontends/LyXView.h"
51
52 #include "insets/insetenv.h"
53
54 #include "support/textutils.h"
55
56 #include <sstream>
57
58 using lyx::pit_type;
59 using lyx::pos_type;
60
61 using std::endl;
62 using std::ostringstream;
63 using std::string;
64
65
66 LyXText::LyXText(BufferView * bv)
67         : maxwidth_(bv ? bv->workWidth() : 100),
68           background_color_(LColor::background),
69           bv_owner(bv),
70           autoBreakRows_(false)
71 {}
72
73
74 void LyXText::init(BufferView * bv)
75 {
76         BOOST_ASSERT(bv);
77         bv_owner = bv;
78         maxwidth_ = bv->workWidth();
79         dim_.wid = maxwidth_;
80         dim_.asc = 10;
81         dim_.des = 10;
82
83         pit_type const end = paragraphs().size();
84         for (pit_type pit = 0; pit != end; ++pit)
85                 pars_[pit].rows().clear();
86
87         current_font = getFont(pars_[0], 0);
88         updateCounters(*bv->buffer());
89 }
90
91
92 bool LyXText::isMainText() const
93 {
94         return &bv()->buffer()->text() == this;
95 }
96
97
98 //takes screen x,y coordinates
99 InsetBase * LyXText::checkInsetHit(int x, int y) const
100 {
101         pit_type pit = getPitNearY(y);
102         BOOST_ASSERT(pit != -1);
103
104         Paragraph const & par = pars_[pit];
105
106         lyxerr[Debug::DEBUG]
107                 << BOOST_CURRENT_FUNCTION
108                 << ": x: " << x
109                 << " y: " << y
110                 << "  pit: " << pit
111                 << endl;
112         InsetList::const_iterator iit = par.insetlist.begin();
113         InsetList::const_iterator iend = par.insetlist.end();
114         for (; iit != iend; ++iit) {
115                 InsetBase * inset = iit->inset;
116 #if 1
117                 lyxerr[Debug::DEBUG]
118                         << BOOST_CURRENT_FUNCTION
119                         << ": examining inset " << inset << endl;
120                 
121                 if (theCoords.getInsets().has(inset))
122                         lyxerr[Debug::DEBUG]
123                                 << BOOST_CURRENT_FUNCTION
124                                 << ": xo: " << inset->xo() << "..."
125                                 << inset->xo() + inset->width()
126                                 << " yo: " << inset->yo() - inset->ascent()
127                                 << "..."
128                                 << inset->yo() + inset->descent()
129                                 << endl;
130                 else
131                         lyxerr[Debug::DEBUG]
132                                 << BOOST_CURRENT_FUNCTION
133                                 << ": inset has no cached position" << endl;
134 #endif
135                 if (inset->covers(x, y)) {
136                         lyxerr[Debug::DEBUG]
137                                 << BOOST_CURRENT_FUNCTION
138                                 << ": Hit inset: " << inset << endl;
139                         return inset;
140                 }
141         }
142         lyxerr[Debug::DEBUG]
143                 << BOOST_CURRENT_FUNCTION
144                 << ": No inset hit. " << endl;
145         return 0;
146 }
147
148
149
150 // Gets the fully instantiated font at a given position in a paragraph
151 // Basically the same routine as Paragraph::getFont() in paragraph.C.
152 // The difference is that this one is used for displaying, and thus we
153 // are allowed to make cosmetic improvements. For instance make footnotes
154 // smaller. (Asger)
155 LyXFont LyXText::getFont(Paragraph const & par, pos_type const pos) const
156 {
157         BOOST_ASSERT(pos >= 0);
158
159         LyXLayout_ptr const & layout = par.layout();
160 #ifdef WITH_WARNINGS
161 #warning broken?
162 #endif
163         BufferParams const & params = bv()->buffer()->params();
164         pos_type const body_pos = par.beginOfBody();
165
166         // We specialize the 95% common case:
167         if (!par.getDepth()) {
168                 LyXFont f = par.getFontSettings(params, pos);
169                 if (!isMainText())
170                         applyOuterFont(f);
171                 if (layout->labeltype == LABEL_MANUAL && pos < body_pos)
172                         return f.realize(layout->reslabelfont);
173                 else
174                         return f.realize(layout->resfont);
175         }
176
177         // The uncommon case need not be optimized as much
178         LyXFont layoutfont;
179         if (pos < body_pos)
180                 layoutfont = layout->labelfont;
181         else
182                 layoutfont = layout->font;
183
184         LyXFont font = par.getFontSettings(params, pos);
185         font.realize(layoutfont);
186
187         if (!isMainText())
188                 applyOuterFont(font);
189
190         // Realize with the fonts of lesser depth.
191         font.realize(defaultfont_);
192
193         return font;
194 }
195
196 // There are currently two font mechanisms in LyX:
197 // 1. The font attributes in a lyxtext, and
198 // 2. The inset-specific font properties, defined in an inset's
199 // metrics() and draw() methods and handed down the inset chain through
200 // the pi/mi parameters, and stored locally in a lyxtext in font_.
201 // This is where the two are integrated in the final fully realized
202 // font.
203 void LyXText::applyOuterFont(LyXFont & font) const {
204         LyXFont lf(font_);
205         lf.reduce(defaultfont_);
206         lf.realize(font);
207         lf.setLanguage(font.language());
208         font = lf;
209 }
210
211
212 LyXFont LyXText::getLayoutFont(pit_type const pit) const
213 {
214         LyXLayout_ptr const & layout = pars_[pit].layout();
215
216         if (!pars_[pit].getDepth())
217                 return layout->resfont;
218
219         LyXFont font = layout->font;
220         // Realize with the fonts of lesser depth.
221         //font.realize(outerFont(pit, paragraphs()));
222         font.realize(defaultfont_);
223
224         return font;
225 }
226
227
228 LyXFont LyXText::getLabelFont(Paragraph const & par) const
229 {
230         LyXLayout_ptr const & layout = par.layout();
231
232         if (!par.getDepth())
233                 return layout->reslabelfont;
234
235         LyXFont font = layout->labelfont;
236         // Realize with the fonts of lesser depth.
237         font.realize(defaultfont_);
238
239         return font;
240 }
241
242
243 void LyXText::setCharFont(pit_type pit, pos_type pos, LyXFont const & fnt)
244 {
245         LyXFont font = fnt;
246         LyXLayout_ptr const & layout = pars_[pit].layout();
247
248         // Get concrete layout font to reduce against
249         LyXFont layoutfont;
250
251         if (pos < pars_[pit].beginOfBody())
252                 layoutfont = layout->labelfont;
253         else
254                 layoutfont = layout->font;
255
256         // Realize against environment font information
257         if (pars_[pit].getDepth()) {
258                 pit_type tp = pit;
259                 while (!layoutfont.resolved() &&
260                        tp != pit_type(paragraphs().size()) &&
261                        pars_[tp].getDepth()) {
262                         tp = outerHook(tp, paragraphs());
263                         if (tp != pit_type(paragraphs().size()))
264                                 layoutfont.realize(pars_[tp].layout()->font);
265                 }
266         }
267
268         layoutfont.realize(defaultfont_);
269
270         // Now, reduce font against full layout font
271         font.reduce(layoutfont);
272
273         pars_[pit].setFont(pos, font);
274 }
275
276
277 // used in setLayout
278 // Asger is not sure we want to do this...
279 void LyXText::makeFontEntriesLayoutSpecific(BufferParams const & params,
280                                             Paragraph & par)
281 {
282         LyXLayout_ptr const & layout = par.layout();
283         pos_type const psize = par.size();
284
285         LyXFont layoutfont;
286         for (pos_type pos = 0; pos < psize; ++pos) {
287                 if (pos < par.beginOfBody())
288                         layoutfont = layout->labelfont;
289                 else
290                         layoutfont = layout->font;
291
292                 LyXFont tmpfont = par.getFontSettings(params, pos);
293                 tmpfont.reduce(layoutfont);
294                 par.setFont(pos, tmpfont);
295         }
296 }
297
298
299 // return past-the-last paragraph influenced by a layout change on pit
300 pit_type LyXText::undoSpan(pit_type pit)
301 {
302         pit_type end = paragraphs().size();
303         pit_type nextpit = pit + 1;
304         if (nextpit == end)
305                 return nextpit;
306         //because of parindents
307         if (!pars_[pit].getDepth())
308                 return boost::next(nextpit);
309         //because of depth constrains
310         for (; nextpit != end; ++pit, ++nextpit) {
311                 if (!pars_[pit].getDepth())
312                         break;
313         }
314         return nextpit;
315 }
316
317
318 pit_type LyXText::setLayout(pit_type start, pit_type end, string const & layout)
319 {
320         BOOST_ASSERT(start != end);
321         pit_type undopit = undoSpan(end - 1);
322         recUndo(start, undopit - 1);
323
324         BufferParams const & bufparams = bv()->buffer()->params();
325         LyXLayout_ptr const & lyxlayout = bufparams.getLyXTextClass()[layout];
326
327         for (pit_type pit = start; pit != end; ++pit) {
328                 pars_[pit].applyLayout(lyxlayout);
329                 makeFontEntriesLayoutSpecific(bufparams, pars_[pit]);
330                 if (lyxlayout->margintype == MARGIN_MANUAL)
331                         pars_[pit].setLabelWidthString(lyxlayout->labelstring());
332         }
333
334         return undopit;
335 }
336
337
338 // set layout over selection and make a total rebreak of those paragraphs
339 void LyXText::setLayout(LCursor & cur, string const & layout)
340 {
341         BOOST_ASSERT(this == cur.text());
342         // special handling of new environment insets
343         BufferView & bv = cur.bv();
344         BufferParams const & params = bv.buffer()->params();
345         LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
346         if (lyxlayout->is_environment) {
347                 // move everything in a new environment inset
348                 lyxerr[Debug::DEBUG] << "setting layout " << layout << endl;
349                 bv.owner()->dispatch(FuncRequest(LFUN_HOME));
350                 bv.owner()->dispatch(FuncRequest(LFUN_ENDSEL));
351                 bv.owner()->dispatch(FuncRequest(LFUN_CUT));
352                 InsetBase * inset = new InsetEnvironment(params, layout);
353                 insertInset(cur, inset);
354                 //inset->edit(cur, true);
355                 //bv.owner()->dispatch(FuncRequest(LFUN_PASTE));
356                 return;
357         }
358
359         pit_type start = cur.selBegin().pit();
360         pit_type end = cur.selEnd().pit() + 1;
361         setLayout(start, end, layout);
362         updateCounters(cur.buffer());
363 }
364
365
366 namespace {
367
368
369 bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
370         Paragraph const & par, int max_depth)
371 {
372         if (par.layout()->labeltype == LABEL_BIBLIO)
373                 return false;
374         int const depth = par.params().depth();
375         if (type == LyXText::INC_DEPTH && depth < max_depth)
376                 return true;
377         if (type == LyXText::DEC_DEPTH && depth > 0)
378                 return true;
379         return false;
380 }
381
382
383 }
384
385
386 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
387 {
388         BOOST_ASSERT(this == cur.text());
389         pit_type const beg = cur.selBegin().pit();
390         pit_type const end = cur.selEnd().pit() + 1;
391         int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
392
393         for (pit_type pit = beg; pit != end; ++pit) {
394                 if (::changeDepthAllowed(type, pars_[pit], max_depth))
395                         return true;
396                 max_depth = pars_[pit].getMaxDepthAfter();
397         }
398         return false;
399 }
400
401
402 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
403 {
404         BOOST_ASSERT(this == cur.text());
405         pit_type const beg = cur.selBegin().pit();
406         pit_type const end = cur.selEnd().pit() + 1;
407         recordUndoSelection(cur);
408         int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
409
410         for (pit_type pit = beg; pit != end; ++pit) {
411                 Paragraph & par = pars_[pit];
412                 if (::changeDepthAllowed(type, par, max_depth)) {
413                         int const depth = par.params().depth();
414                         if (type == INC_DEPTH)
415                                 par.params().depth(depth + 1);
416                         else
417                                 par.params().depth(depth - 1);
418                 }
419                 max_depth = par.getMaxDepthAfter();
420         }
421         // this handles the counter labels, and also fixes up
422         // depth values for follow-on (child) paragraphs
423         updateCounters(cur.buffer());
424 }
425
426
427 // set font over selection
428 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
429 {
430         BOOST_ASSERT(this == cur.text());
431         // if there is no selection just set the current_font
432         if (!cur.selection()) {
433                 // Determine basis font
434                 LyXFont layoutfont;
435                 pit_type pit = cur.pit();
436                 if (cur.pos() < pars_[pit].beginOfBody())
437                         layoutfont = getLabelFont(pars_[pit]);
438                 else
439                         layoutfont = getLayoutFont(pit);
440
441                 // Update current font
442                 real_current_font.update(font,
443                                          cur.buffer().params().language,
444                                          toggleall);
445
446                 // Reduce to implicit settings
447                 current_font = real_current_font;
448                 current_font.reduce(layoutfont);
449                 // And resolve it completely
450                 real_current_font.realize(layoutfont);
451
452                 return;
453         }
454
455         // Ok, we have a selection.
456         recordUndoSelection(cur);
457
458         DocIterator dit = cur.selectionBegin();
459         DocIterator ditend = cur.selectionEnd();
460
461         BufferParams const & params = cur.buffer().params();
462
463         // Don't use forwardChar here as ditend might have
464         // pos() == lastpos() and forwardChar would miss it.
465         // Can't use forwardPos either as this descends into
466         // nested insets.
467         for (; dit != ditend; dit.forwardPosNoDescend()) {
468                 if (dit.pos() != dit.lastpos()) {
469                         LyXFont f = getFont(dit.paragraph(), dit.pos());
470                         f.update(font, params.language, toggleall);
471                         setCharFont(dit.pit(), dit.pos(), f);
472                 }
473         }
474 }
475
476
477 // the cursor set functions have a special mechanism. When they
478 // realize you left an empty paragraph, they will delete it.
479
480 void LyXText::cursorHome(LCursor & cur)
481 {
482         BOOST_ASSERT(this == cur.text());
483         setCursor(cur, cur.pit(), cur.textRow().pos());
484 }
485
486
487 void LyXText::cursorEnd(LCursor & cur)
488 {
489         BOOST_ASSERT(this == cur.text());
490         // if not on the last row of the par, put the cursor before
491         // the final space
492 // FIXME: does this final space exist?
493         pos_type const end = cur.textRow().endpos();
494         setCursor(cur, cur.pit(), end == cur.lastpos() ? end : end - 1);
495 }
496
497
498 void LyXText::cursorTop(LCursor & cur)
499 {
500         BOOST_ASSERT(this == cur.text());
501         setCursor(cur, 0, 0);
502 }
503
504
505 void LyXText::cursorBottom(LCursor & cur)
506 {
507         BOOST_ASSERT(this == cur.text());
508         setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
509 }
510
511
512 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
513 {
514         BOOST_ASSERT(this == cur.text());
515         // If the mask is completely neutral, tell user
516         if (font == LyXFont(LyXFont::ALL_IGNORE)) {
517                 // Could only happen with user style
518                 cur.message(_("No font change defined. "
519                         "Use Character under the Layout menu to define font change."));
520                 return;
521         }
522
523         // Try implicit word selection
524         // If there is a change in the language the implicit word selection
525         // is disabled.
526         CursorSlice resetCursor = cur.top();
527         bool implicitSelection =
528                 font.language() == ignore_language
529                 && font.number() == LyXFont::IGNORE
530                 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
531
532         // Set font
533         setFont(cur, font, toggleall);
534
535         // Implicit selections are cleared afterwards
536         // and cursor is set to the original position.
537         if (implicitSelection) {
538                 cur.clearSelection();
539                 cur.top() = resetCursor;
540                 cur.resetAnchor();
541         }
542 }
543
544
545 string LyXText::getStringToIndex(LCursor const & cur)
546 {
547         BOOST_ASSERT(this == cur.text());
548
549         string idxstring;
550         if (cur.selection()) {
551                 idxstring = cur.selectionAsString(false);
552         } else {
553                 // Try implicit word selection. If there is a change
554                 // in the language the implicit word selection is
555                 // disabled.
556                 LCursor tmpcur = cur;
557                 selectWord(tmpcur, lyx::PREVIOUS_WORD);
558
559                 if (!tmpcur.selection())
560                         cur.message(_("Nothing to index!"));
561                 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
562                         cur.message(_("Cannot index more than one paragraph!"));
563                 else
564                         idxstring = tmpcur.selectionAsString(false);
565         }
566
567         return idxstring;
568 }
569
570
571 void LyXText::setParagraph(LCursor & cur,
572         Spacing const & spacing, LyXAlignment align,
573         string const & labelwidthstring, bool noindent)
574 {
575         BOOST_ASSERT(cur.text());
576         // make sure that the depth behind the selection are restored, too
577         pit_type undopit = undoSpan(cur.selEnd().pit());
578         recUndo(cur.selBegin().pit(), undopit - 1);
579
580         for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
581                         pit <= end; ++pit) {
582                 Paragraph & par = pars_[pit];
583                 ParagraphParameters & params = par.params();
584                 params.spacing(spacing);
585
586                 // does the layout allow the new alignment?
587                 LyXLayout_ptr const & layout = par.layout();
588
589                 if (align == LYX_ALIGN_LAYOUT)
590                         align = layout->align;
591                 if (align & layout->alignpossible) {
592                         if (align == layout->align)
593                                 params.align(LYX_ALIGN_LAYOUT);
594                         else
595                                 params.align(align);
596                 }
597                 par.setLabelWidthString(labelwidthstring);
598                 params.noindent(noindent);
599         }
600 }
601
602
603 // this really should just insert the inset and not move the cursor.
604 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
605 {
606         BOOST_ASSERT(this == cur.text());
607         BOOST_ASSERT(inset);
608         cur.paragraph().insertInset(cur.pos(), inset);
609 }
610
611
612 // needed to insert the selection
613 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
614 {
615         pit_type pit = cur.pit();
616         pos_type pos = cur.pos();
617         recordUndo(cur);
618
619         // only to be sure, should not be neccessary
620         cur.clearSelection();
621         cur.buffer().insertStringAsLines(pars_, pit, pos, current_font, str,
622                 autoBreakRows_);
623
624         cur.resetAnchor();
625         setCursor(cur, cur.pit(), pos);
626         cur.setSelection();
627 }
628
629
630 // turn double CR to single CR, others are converted into one
631 // blank. Then insertStringAsLines is called
632 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
633 {
634         string linestr = str;
635         bool newline_inserted = false;
636
637         for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
638                 if (linestr[i] == '\n') {
639                         if (newline_inserted) {
640                                 // we know that \r will be ignored by
641                                 // insertStringAsLines. Of course, it is a dirty
642                                 // trick, but it works...
643                                 linestr[i - 1] = '\r';
644                                 linestr[i] = '\n';
645                         } else {
646                                 linestr[i] = ' ';
647                                 newline_inserted = true;
648                         }
649                 } else if (IsPrintable(linestr[i])) {
650                         newline_inserted = false;
651                 }
652         }
653         insertStringAsLines(cur, linestr);
654 }
655
656
657 bool LyXText::setCursor(LCursor & cur, pit_type par, pos_type pos,
658         bool setfont, bool boundary)
659 {
660         LCursor old = cur;
661         setCursorIntern(cur, par, pos, setfont, boundary);
662         return deleteEmptyParagraphMechanism(cur, old);
663 }
664
665
666 void LyXText::setCursor(CursorSlice & cur, pit_type par,
667         pos_type pos, bool boundary)
668 {
669         BOOST_ASSERT(par != int(paragraphs().size()));
670         cur.pit() = par;
671         cur.pos() = pos;
672         cur.boundary() = boundary;
673
674         // now some strict checking
675         Paragraph & para = getPar(par);
676
677         // None of these should happen, but we're scaredy-cats
678         if (pos < 0) {
679                 lyxerr << "dont like -1" << endl;
680                 BOOST_ASSERT(false);
681         }
682
683         if (pos > para.size()) {
684                 lyxerr << "dont like 1, pos: " << pos
685                        << " size: " << para.size()
686                        << " par: " << par << endl;
687                 BOOST_ASSERT(false);
688         }
689 }
690
691
692 void LyXText::setCursorIntern(LCursor & cur,
693         pit_type par, pos_type pos, bool setfont, bool boundary)
694 {
695         setCursor(cur.top(), par, pos, boundary);
696         cur.setTargetX();
697         if (setfont)
698                 setCurrentFont(cur);
699 }
700
701
702 void LyXText::setCurrentFont(LCursor & cur)
703 {
704         BOOST_ASSERT(this == cur.text());
705         pos_type pos = cur.pos();
706         Paragraph & par = cur.paragraph();
707
708         if (cur.boundary() && pos > 0)
709                 --pos;
710
711         if (pos > 0) {
712                 if (pos == cur.lastpos())
713                         --pos;
714                 else // potentional bug... BUG (Lgb)
715                         if (par.isSeparator(pos)) {
716                                 if (pos > cur.textRow().pos() &&
717                                     bidi.level(pos) % 2 ==
718                                     bidi.level(pos - 1) % 2)
719                                         --pos;
720                                 else if (pos + 1 < cur.lastpos())
721                                         ++pos;
722                         }
723         }
724
725         BufferParams const & bufparams = cur.buffer().params();
726         current_font = par.getFontSettings(bufparams, pos);
727         real_current_font = getFont(par, pos);
728
729         if (cur.pos() == cur.lastpos()
730             && bidi.isBoundary(cur.buffer(), par, cur.pos())
731             && !cur.boundary()) {
732                 Language const * lang = par.getParLanguage(bufparams);
733                 current_font.setLanguage(lang);
734                 current_font.setNumber(LyXFont::OFF);
735                 real_current_font.setLanguage(lang);
736                 real_current_font.setNumber(LyXFont::OFF);
737         }
738 }
739
740
741 // x is an absolute screen coord
742 // returns the column near the specified x-coordinate of the row
743 // x is set to the real beginning of this column
744 pos_type LyXText::getColumnNearX(pit_type const pit,
745         Row const & row, int & x, bool & boundary) const
746 {
747         int const xo = theCoords.get(this, pit).x_;
748         x -= xo;
749         RowMetrics const r = computeRowMetrics(pit, row);
750         Paragraph const & par = pars_[pit];
751
752         pos_type vc = row.pos();
753         pos_type end = row.endpos();
754         pos_type c = 0;
755         LyXLayout_ptr const & layout = par.layout();
756
757         bool left_side = false;
758
759         pos_type body_pos = par.beginOfBody();
760
761         double tmpx = r.x;
762         double last_tmpx = tmpx;
763
764         if (body_pos > 0 &&
765             (body_pos > end || !par.isLineSeparator(body_pos - 1)))
766                 body_pos = 0;
767
768         // check for empty row
769         if (vc == end) {
770                 x = int(tmpx) + xo;
771                 return 0;
772         }
773
774         while (vc < end && tmpx <= x) {
775                 c = bidi.vis2log(vc);
776                 last_tmpx = tmpx;
777                 if (body_pos > 0 && c == body_pos - 1) {
778                         tmpx += r.label_hfill +
779                                 font_metrics::width(layout->labelsep, getLabelFont(par));
780                         if (par.isLineSeparator(body_pos - 1))
781                                 tmpx -= singleWidth(par, body_pos - 1);
782                 }
783
784                 if (hfillExpansion(par, row, c)) {
785                         tmpx += singleWidth(par, c);
786                         if (c >= body_pos)
787                                 tmpx += r.hfill;
788                         else
789                                 tmpx += r.label_hfill;
790                 } else if (par.isSeparator(c)) {
791                         tmpx += singleWidth(par, c);
792                         if (c >= body_pos)
793                                 tmpx += r.separator;
794                 } else {
795                         tmpx += singleWidth(par, c);
796                 }
797                 ++vc;
798         }
799
800         if ((tmpx + last_tmpx) / 2 > x) {
801                 tmpx = last_tmpx;
802                 left_side = true;
803         }
804
805         BOOST_ASSERT(vc <= end);  // This shouldn't happen.
806
807         boundary = false;
808         // This (rtl_support test) is not needed, but gives
809         // some speedup if rtl_support == false
810         bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
811
812         // If lastrow is false, we don't need to compute
813         // the value of rtl.
814         bool const rtl = lastrow ? isRTL(par) : false;
815         if (lastrow &&
816                  ((rtl  &&  left_side && vc == row.pos() && x < tmpx - 5) ||
817                   (!rtl && !left_side && vc == end  && x > tmpx + 5)))
818                 c = end;
819         else if (vc == row.pos()) {
820                 c = bidi.vis2log(vc);
821                 if (bidi.level(c) % 2 == 1)
822                         ++c;
823         } else {
824                 c = bidi.vis2log(vc - 1);
825                 bool const rtl = (bidi.level(c) % 2 == 1);
826                 if (left_side == rtl) {
827                         ++c;
828                         boundary = bidi.isBoundary(*bv()->buffer(), par, c);
829                 }
830         }
831
832         // The following code is necessary because the cursor position past
833         // the last char in a row is logically equivalent to that before
834         // the first char in the next row. That's why insets causing row
835         // divisions -- Newline and display-style insets -- must be treated
836         // specially, so cursor up/down doesn't get stuck in an air gap -- MV
837         // Newline inset, air gap below:
838         if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
839                 if (bidi.level(end -1) % 2 == 0)
840                         tmpx -= singleWidth(par, end - 1);
841                 else
842                         tmpx += singleWidth(par, end - 1);
843                 c = end - 1;
844         }
845         // Air gap above display inset:
846         if (row.pos() < end && c >= end && end < par.size()
847             && par.isInset(end) && par.getInset(end)->display()) {
848                 c = end - 1;
849         }
850         // Air gap below display inset:
851         if (row.pos() < end && c >= end && par.isInset(end - 1)
852             && par.getInset(end - 1)->display()) {
853                 c = end - 1;
854         }
855
856         x = int(tmpx) + xo;
857         return c - row.pos();
858 }
859
860
861 // y is screen coordinate
862 pit_type LyXText::getPitNearY(int y) const
863 {
864         BOOST_ASSERT(!paragraphs().empty());
865         BOOST_ASSERT(theCoords.getParPos().find(this) != theCoords.getParPos().end());
866         CoordCache::InnerParPosCache const & cc = theCoords.getParPos().find(this)->second;
867         lyxerr[Debug::DEBUG]
868                 << BOOST_CURRENT_FUNCTION
869                 << ": y: " << y << " cache size: " << cc.size()
870                 << endl;
871
872         // look for highest numbered paragraph with y coordinate less than given y
873         pit_type pit = 0;
874         int yy = -1;
875         CoordCache::InnerParPosCache::const_iterator it = cc.begin();
876         CoordCache::InnerParPosCache::const_iterator et = cc.end();
877         for (; it != et; ++it) {
878                 lyxerr[Debug::DEBUG]
879                         << BOOST_CURRENT_FUNCTION
880                         << "  examining: pit: " << it->first
881                         << " y: " << it->second.y_
882                         << endl;
883                 
884                 if (it->first >= pit && int(it->second.y_) - int(pars_[it->first].ascent()) <= y) {
885                         pit = it->first;
886                         yy = it->second.y_;
887                 }
888         }
889
890         lyxerr[Debug::DEBUG]
891                 << BOOST_CURRENT_FUNCTION
892                 << ": found best y: " << yy << " for pit: " << pit
893                 << endl;
894         
895         return pit;
896 }
897
898
899 Row const & LyXText::getRowNearY(int y, pit_type pit) const
900 {
901         Paragraph const & par = pars_[pit];
902         int yy = theCoords.get(this, pit).y_ - par.ascent();
903         BOOST_ASSERT(!par.rows().empty());
904         RowList::const_iterator rit = par.rows().begin();
905         RowList::const_iterator const rlast = boost::prior(par.rows().end());
906         for (; rit != rlast; yy += rit->height(), ++rit)
907                 if (yy + rit->height() > y)
908                         break;
909         return *rit;
910 }
911
912
913 // x,y are absolute screen coordinates
914 // sets cursor recursively descending into nested editable insets
915 InsetBase * LyXText::editXY(LCursor & cur, int x, int y) const
916 {
917         pit_type pit = getPitNearY(y);
918         BOOST_ASSERT(pit != -1);
919         Row const & row = getRowNearY(y, pit);
920         bool bound = false;
921
922         int xx = x; // is modified by getColumnNearX
923         pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
924         cur.pit() = pit;
925         cur.pos() = pos;
926         cur.boundary() = bound;
927         cur.x_target() = x;
928
929         // try to descend into nested insets
930         InsetBase * inset = checkInsetHit(x, y);
931         lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
932         if (!inset) {
933                 // Either we deconst editXY or better we move current_font
934                 // and real_current_font to LCursor
935                 const_cast<LyXText *>(this)->setCurrentFont(cur);
936                 return 0;
937         }
938
939         // This should be just before or just behind the
940         // cursor position set above.
941         BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
942                      || inset == pars_[pit].getInset(pos));
943         // Make sure the cursor points to the position before
944         // this inset.
945         if (inset == pars_[pit].getInset(pos - 1))
946                 --cur.pos();
947         inset = inset->editXY(cur, x, y);
948         if (cur.top().text() == this)
949                 const_cast<LyXText *>(this)->setCurrentFont(cur);
950         return inset;
951 }
952
953
954 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
955 {
956         if (cur.selection())
957                 return false;
958         if (cur.pos() == cur.lastpos())
959                 return false;
960         InsetBase * inset = cur.nextInset();
961         if (!isHighlyEditableInset(inset))
962                 return false;
963         inset->edit(cur, front);
964         return true;
965 }
966
967
968 bool LyXText::cursorLeft(LCursor & cur)
969 {
970         if (cur.pos() != 0) {
971                 bool boundary = cur.boundary();
972                 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
973                 if (!checkAndActivateInset(cur, false)) {
974                         if (false && !boundary &&
975                                         bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
976                                 updateNeeded |=
977                                         setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
978                 }
979                 return updateNeeded;
980         }
981
982         if (cur.pit() != 0) {
983                 // Steps into the paragraph above
984                 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
985         }
986         return false;
987 }
988
989
990 bool LyXText::cursorRight(LCursor & cur)
991 {
992         if (false && cur.boundary()) {
993                 return setCursor(cur, cur.pit(), cur.pos(), true, false);
994         }
995
996         if (cur.pos() != cur.lastpos()) {
997                 bool updateNeeded = false;
998                 if (!checkAndActivateInset(cur, true)) {
999                         updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
1000                         if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1001                                                          cur.pos()))
1002                                 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
1003                 }
1004                 return updateNeeded;
1005         }
1006
1007         if (cur.pit() != cur.lastpit())
1008                 return setCursor(cur, cur.pit() + 1, 0);
1009         return false;
1010 }
1011
1012
1013 bool LyXText::cursorUp(LCursor & cur)
1014 {
1015         Paragraph const & par = cur.paragraph();
1016         int const row = par.pos2row(cur.pos());
1017         int const x = cur.targetX();
1018
1019         if (!cur.selection()) {
1020                 int const y = bv_funcs::getPos(cur).y_;
1021                 LCursor old = cur;
1022                 editXY(cur, x, y - par.rows()[row].ascent() - 1);
1023
1024                 // This happens when you move out of an inset.
1025                 // And to give the DEPM the possibility of doing
1026                 // something we must provide it with two different
1027                 // cursors. (Lgb)
1028                 LCursor dummy = cur;
1029                 if (dummy == old)
1030                         ++dummy.pos();
1031
1032                 return deleteEmptyParagraphMechanism(dummy, old);
1033         }
1034
1035         bool updateNeeded = false;
1036
1037         if (row > 0) {
1038                 updateNeeded |= setCursor(cur, cur.pit(),
1039                                           x2pos(cur.pit(), row - 1, x));
1040         } else if (cur.pit() > 0) {
1041                 --cur.pit();
1042                 //cannot use 'par' now
1043                 updateNeeded |= setCursor(cur, cur.pit(), x2pos(cur.pit(), cur.paragraph().rows().size() - 1, x));
1044         }
1045
1046         cur.x_target() = x;
1047
1048         return updateNeeded;
1049 }
1050
1051
1052 bool LyXText::cursorDown(LCursor & cur)
1053 {
1054         Paragraph const & par = cur.paragraph();
1055         int const row = par.pos2row(cur.pos());
1056         int const x = cur.targetX();
1057
1058         if (!cur.selection()) {
1059                 int const y = bv_funcs::getPos(cur).y_;
1060                 LCursor old = cur;
1061                 editXY(cur, x, y + par.rows()[row].descent() + 1);
1062
1063                 // This happens when you move out of an inset.
1064                 // And to give the DEPM the possibility of doing
1065                 // something we must provide it with two different
1066                 // cursors. (Lgb)
1067                 LCursor dummy = cur;
1068                 if (dummy == old)
1069                         ++dummy.pos();
1070
1071                 bool const changed = deleteEmptyParagraphMechanism(dummy, old);
1072
1073                 // Make sure that cur gets back whatever happened to dummy(Lgb)
1074                 if (changed)
1075                         cur = dummy;
1076
1077                 return changed;
1078
1079         }
1080
1081         bool updateNeeded = false;
1082
1083         if (row + 1 < int(par.rows().size())) {
1084                 updateNeeded |= setCursor(cur, cur.pit(),
1085                                           x2pos(cur.pit(), row + 1, x));
1086         } else if (cur.pit() + 1 < int(paragraphs().size())) {
1087                 ++cur.pit();
1088                 updateNeeded |= setCursor(cur, cur.pit(),
1089                                           x2pos(cur.pit(), 0, x));
1090         }
1091
1092         cur.x_target() = x;
1093
1094         return updateNeeded;
1095 }
1096
1097
1098 bool LyXText::cursorUpParagraph(LCursor & cur)
1099 {
1100         bool updated = false;
1101         if (cur.pos() > 0)
1102                 updated = setCursor(cur, cur.pit(), 0);
1103         else if (cur.pit() != 0)
1104                 updated = setCursor(cur, cur.pit() - 1, 0);
1105         return updated;
1106 }
1107
1108
1109 bool LyXText::cursorDownParagraph(LCursor & cur)
1110 {
1111         bool updated = false;
1112         if (cur.pit() != cur.lastpit())
1113                 updated = setCursor(cur, cur.pit() + 1, 0);
1114         else
1115                 updated = setCursor(cur, cur.pit(), cur.lastpos());
1116         return updated;
1117 }
1118
1119
1120 // fix the cursor `cur' after a characters has been deleted at `where'
1121 // position. Called by deleteEmptyParagraphMechanism
1122 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1123 {
1124         // Do nothing if cursor is not in the paragraph where the
1125         // deletion occured,
1126         if (cur.pit() != where.pit())
1127                 return;
1128
1129         // If cursor position is after the deletion place update it
1130         if (cur.pos() > where.pos())
1131                 --cur.pos();
1132
1133         // Check also if we don't want to set the cursor on a spot behind the
1134         // pagragraph because we erased the last character.
1135         if (cur.pos() > cur.lastpos())
1136                 cur.pos() = cur.lastpos();
1137 }
1138
1139
1140 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor const & old)
1141 {
1142         // Would be wrong to delete anything if we have a selection.
1143         if (cur.selection())
1144                 return false;
1145
1146         //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1147         Paragraph const & oldpar = pars_[old.pit()];
1148
1149         // We allow all kinds of "mumbo-jumbo" when freespacing.
1150         if (oldpar.isFreeSpacing())
1151                 return false;
1152
1153         /* Ok I'll put some comments here about what is missing.
1154            I have fixed BackSpace (and thus Delete) to not delete
1155            double-spaces automagically. I have also changed Cut,
1156            Copy and Paste to hopefully do some sensible things.
1157            There are still some small problems that can lead to
1158            double spaces stored in the document file or space at
1159            the beginning of paragraphs(). This happens if you have
1160            the cursor between to spaces and then save. Or if you
1161            cut and paste and the selection have a space at the
1162            beginning and then save right after the paste. I am
1163            sure none of these are very hard to fix, but I will
1164            put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1165            that I can get some feedback. (Lgb)
1166         */
1167
1168         // If old.pos() == 0 and old.pos()(1) == LineSeparator
1169         // delete the LineSeparator.
1170         // MISSING
1171
1172         // If old.pos() == 1 and old.pos()(0) == LineSeparator
1173         // delete the LineSeparator.
1174         // MISSING
1175
1176         // If the chars around the old cursor were spaces, delete one of them.
1177         if (old.pit() != cur.pit() || old.pos() != cur.pos()) {
1178
1179                 // Only if the cursor has really moved.
1180                 if (old.pos() > 0
1181                     && old.pos() < oldpar.size()
1182                     && oldpar.isLineSeparator(old.pos())
1183                     && oldpar.isLineSeparator(old.pos() - 1)) {
1184                         // We need to set the text to Change::INSERTED to
1185                         // get it erased properly
1186                         pars_[old.pit()].setChange(old.pos() -1,
1187                                 Change::INSERTED);
1188                         pars_[old.pit()].erase(old.pos() - 1);
1189 #ifdef WITH_WARNINGS
1190 #warning This will not work anymore when we have multiple views of the same buffer
1191 // In this case, we will have to correct also the cursors held by
1192 // other bufferviews. It will probably be easier to do that in a more
1193 // automated way in CursorSlice code. (JMarc 26/09/2001)
1194 #endif
1195                         // correct all cursor parts
1196                         fixCursorAfterDelete(cur.top(), old.top());
1197 #ifdef WITH_WARNINGS
1198 #warning DEPM, look here
1199 #endif
1200                         //fixCursorAfterDelete(cur.anchor(), old.top());
1201                         return true;
1202                 }
1203         }
1204
1205         // only do our magic if we changed paragraph
1206         if (old.pit() == cur.pit())
1207                 return false;
1208
1209         // don't delete anything if this is the ONLY paragraph!
1210         if (pars_.size() == 1)
1211                 return false;
1212
1213         // Do not delete empty paragraphs with keepempty set.
1214         if (oldpar.allowEmpty())
1215                 return false;
1216
1217         // record if we have deleted a paragraph
1218         // we can't possibly have deleted a paragraph before this point
1219         bool deleted = false;
1220
1221         if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1222                 // ok, we will delete something
1223                 deleted = true;
1224
1225                 bool selection_position_was_oldcursor_position =
1226                         cur.anchor().pit() == old.pit() && cur.anchor().pos() == old.pos();
1227
1228                 // This is a bit of a overkill. We change the old and the cur par
1229                 // at max, certainly not everything in between...
1230                 recUndo(old.pit(), cur.pit());
1231
1232                 // Delete old par.
1233                 pars_.erase(pars_.begin() + old.pit());
1234
1235                 // Update cursor par offset if necessary.
1236                 // Some 'iterator registration' would be nice that takes care of
1237                 // such events. Maybe even signal/slot?
1238                 if (cur.pit() > old.pit())
1239                         --cur.pit();
1240 #ifdef WITH_WARNINGS
1241 #warning DEPM, look here
1242 #endif
1243 //              if (cur.anchor().pit() > old.pit())
1244 //                      --cur.anchor().pit();
1245
1246                 if (selection_position_was_oldcursor_position) {
1247                         // correct selection
1248                         cur.resetAnchor();
1249                 }
1250         }
1251
1252         if (deleted)
1253                 return true;
1254
1255         if (pars_[old.pit()].stripLeadingSpaces())
1256                 cur.resetAnchor();
1257
1258         return false;
1259 }
1260
1261
1262 ParagraphList & LyXText::paragraphs() const
1263 {
1264         return const_cast<ParagraphList &>(pars_);
1265 }
1266
1267
1268 void LyXText::recUndo(pit_type first, pit_type last) const
1269 {
1270         recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1271 }
1272
1273
1274 void LyXText::recUndo(pit_type par) const
1275 {
1276         recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1277 }
1278
1279
1280 int defaultRowHeight()
1281 {
1282         return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) *  1.2);
1283 }