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