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