9fans.net/go@v0.0.5/draw/frame/frinsert.go (about)

     1  package frame
     2  
     3  import (
     4  	"9fans.net/go/draw"
     5  )
     6  
     7  // bxscan splits text into boxes inside tmpf starting at *ppt,
     8  // and reports the position at its end.
     9  //
    10  // It updates ppt to the position of the start of the text.
    11  func (f *Frame) bxscan(tmpf *Frame, text []rune, ppt *draw.Point) draw.Point {
    12  	tmpf.R = f.R
    13  	tmpf.B = f.B
    14  	tmpf.Font = f.Font
    15  	tmpf.MaxTab = f.MaxTab
    16  	tmpf.box = nil
    17  	tmpf.NumChars = 0
    18  	tmpf.Cols = f.Cols
    19  	tmpf.MaxLines = f.MaxLines
    20  	nl := 0
    21  	for len(text) > 0 && nl <= tmpf.MaxLines {
    22  		tmpf.box = append(tmpf.box, box{})
    23  		b := &tmpf.box[len(tmpf.box)-1]
    24  		c := text[0]
    25  		if c == '\t' || c == '\n' {
    26  			b.bc = c
    27  			b.wid = 5000
    28  			if c == '\t' {
    29  				b.minwid = tmpf.Font.StringWidth(" ")
    30  			}
    31  			b.nrune = -1
    32  			if c == '\n' {
    33  				nl++
    34  			}
    35  			tmpf.NumChars++
    36  			text = text[1:]
    37  		} else {
    38  			nr := 0
    39  			w := 0
    40  			for nr < len(text) {
    41  				c := text[nr]
    42  				if c == '\t' || c == '\n' || nr > 256 { // 256 is arbitrary cutoff to keep boxes small
    43  					break
    44  				}
    45  				w += tmpf.Font.RunesWidth(text[nr : nr+1])
    46  				nr++
    47  			}
    48  			b.bytes, text = []byte(string(text[:nr])), text[nr:]
    49  			b.wid = w
    50  			b.nrune = nr
    51  			tmpf.NumChars += nr
    52  		}
    53  	}
    54  	// Adjust for line wrap before calling draw so that
    55  	// the starting position agrees with that of draw.
    56  	// TODO we could call tmpf.cklinewrap0 instead,
    57  	// which would arguably be clearer.
    58  	f.cklinewrap0(ppt, &tmpf.box[0])
    59  	return tmpf.draw(*ppt)
    60  }
    61  
    62  // chop removes any boxes in f that extend beyond
    63  // its bounds, starting the calculation at box index bn
    64  // that's at rune index p. It updates f.NumChars and f.NumLines.
    65  //
    66  // This is only called when we know that the frame needs to be
    67  // truncated.
    68  func (f *Frame) chop(pt draw.Point, p, bn int) {
    69  	for ; ; bn++ {
    70  		if bn >= len(f.box) {
    71  			drawerror(f.Display, "endofframe")
    72  		}
    73  		b := &f.box[bn]
    74  		f.cklinewrap(&pt, b)
    75  		if pt.Y >= f.R.Max.Y {
    76  			break
    77  		}
    78  		p += b.NRUNE()
    79  		f.advance(&pt, b)
    80  	}
    81  	f.NumChars = p
    82  	f.NumLines = f.MaxLines
    83  	if bn < len(f.box) { // BUG
    84  		f.delbox(bn, len(f.box)-1)
    85  	}
    86  }
    87  
    88  // Insert inserts text into f starting at rune index p0.
    89  // Tabs and newlines are handled by the library,
    90  // but all other characters, including control characters, are just displayed.
    91  // For example, backspaces are printed; to erase a character, use frdelete.
    92  func (f *Frame) Insert(text []rune, p int) {
    93  	p0 := p
    94  	if p0 > f.NumChars || len(text) == 0 || f.B == nil {
    95  		return
    96  	}
    97  	// Find the index of the box that starts at the insertion point,
    98  	// splitting the box that's there if needed.
    99  	n0 := f.findbox(0, 0, p0)
   100  	// cn0 tracks the rune index of box that's being processed.
   101  	cn0 := p0
   102  	// nn0 remembers the index of the starting box (we update
   103  	// n0 as we scan through during the calculations).
   104  	nn0 := n0
   105  
   106  	// Find the point at the end of the box just before the insertion point.
   107  	pt0 := f.ptofcharnb(p0, n0)
   108  	// ppt0 remembers the starting point.
   109  	ppt0 := pt0
   110  	opt0 := pt0
   111  
   112  	// Scan the text into a temporary frame starting at the point
   113  	// we know that it will be inserted. This tells us where the
   114  	// text will end up.
   115  	var tmpf Frame
   116  	pt1 := f.bxscan(&tmpf, text, &ppt0)
   117  	ppt1 := pt1
   118  	if n0 < len(f.box) {
   119  		b := &f.box[n0]
   120  		f.cklinewrap(&pt0, b) // for frdrawsel
   121  		f.cklinewrap0(&ppt1, b)
   122  	}
   123  	f.modified = true
   124  
   125  	// ppt0 and ppt1 are start and end of insertion
   126  	// as they will appear when insertion is complete.
   127  	// pt0 is current location of insertion position (p0).
   128  	// pt1 is terminal point (without line wrap) of insertion.
   129  	if f.P0 == f.P1 {
   130  		f.Tick(f.PointOf(f.P0), false)
   131  	}
   132  	// Find point where old and new x's line up.
   133  	// Invariants:
   134  	//   - pt0 is where the next box (b, n0) is now
   135  	//   - pt1 is where it will be after the insertion
   136  	// If pt1 goes off the rectangle, we can toss everything from there on.
   137  	var pts []draw.Point
   138  	for pt1.X != pt0.X && pt1.Y != f.R.Max.Y && n0 < len(f.box) {
   139  		b := &f.box[n0]
   140  		f.cklinewrap(&pt0, b)
   141  		f.cklinewrap0(&pt1, b)
   142  		if b.nrune > 0 {
   143  			n := f.canfit(pt1, b)
   144  			if n == 0 {
   145  				drawerror(f.Display, "canfit==0")
   146  			}
   147  			if n != b.nrune {
   148  				f.splitbox(n0, n)
   149  				b = &f.box[n0]
   150  			}
   151  		}
   152  		// has a text box overflowed off the frame?
   153  		if pt1.Y == f.R.Max.Y {
   154  			break
   155  		}
   156  		pts = append(pts, pt0, pt1)
   157  		f.advance(&pt0, b)
   158  		pt1.X += f.newwid(pt1, b)
   159  		cn0 += b.NRUNE()
   160  		n0++
   161  	}
   162  	if pt1.Y > f.R.Max.Y {
   163  		drawerror(f.Display, "frinsert pt1 too far")
   164  	}
   165  	if pt1.Y == f.R.Max.Y && n0 < len(f.box) {
   166  		f.NumChars -= f.strlen(n0)
   167  		f.delbox(n0, len(f.box)-1)
   168  	}
   169  	if n0 == len(f.box) {
   170  		f.NumLines = (pt1.Y - f.R.Min.Y) / f.Font.Height
   171  		if pt1.X > f.R.Min.X {
   172  			f.NumLines++
   173  		}
   174  	} else if pt1.Y != pt0.Y {
   175  		y := f.R.Max.Y
   176  		q0 := pt0.Y + f.Font.Height
   177  		q1 := pt1.Y + f.Font.Height
   178  		f.NumLines += (q1 - q0) / f.Font.Height
   179  		if f.NumLines > f.MaxLines {
   180  			f.chop(ppt1, p0, nn0)
   181  		}
   182  		if pt1.Y < y {
   183  			r := f.R
   184  			r.Min.Y = q1
   185  			r.Max.Y = y
   186  			if q1 < y {
   187  				f.B.Draw(r, f.B, nil, draw.Pt(f.R.Min.X, q0))
   188  			}
   189  			r.Min = pt1
   190  			r.Max.X = pt1.X + (f.R.Max.X - pt0.X)
   191  			r.Max.Y = q1
   192  			f.B.Draw(r, f.B, nil, pt0)
   193  		}
   194  	}
   195  
   196  	// Move the old stuff down to make room.
   197  	// The draws above moved everything down after the point where x's lined up.
   198  	// The loop below will move the stuff between the insertion and the draws.
   199  	y := pt1.Y
   200  	if y != f.R.Max.Y {
   201  		y = 0
   202  	}
   203  	for bn := n0 - 1; len(pts) > 0; bn, pts = bn-1, pts[:len(pts)-2] {
   204  		b := &f.box[bn]
   205  		pt := pts[len(pts)-1]
   206  		if b.nrune > 0 {
   207  			r := draw.Rectangle{Min: pt, Max: pt}
   208  			r.Max.X += b.wid
   209  			r.Max.Y += f.Font.Height
   210  			f.B.Draw(r, f.B, nil, pts[len(pts)-2])
   211  			// clear bit hanging off right
   212  			if len(pts) == 2 && pt.Y > pt0.Y {
   213  				// first new char is bigger than first char we're displacing,
   214  				// causing line wrap. ugly special case.
   215  				r := draw.Rectangle{Min: opt0, Max: opt0}
   216  				r.Max.X = f.R.Max.X
   217  				r.Max.Y += f.Font.Height
   218  				var col *draw.Image
   219  				if f.P0 <= cn0 && cn0 < f.P1 { // b+1 is inside selection
   220  					col = f.Cols[HIGH]
   221  				} else {
   222  					col = f.Cols[BACK]
   223  				}
   224  				f.B.Draw(r, col, nil, r.Min)
   225  			} else if pt.Y < y {
   226  				r := draw.Rectangle{Min: pt, Max: pt}
   227  				r.Min.X += b.wid
   228  				r.Max.X = f.R.Max.X
   229  				r.Max.Y += f.Font.Height
   230  				var col *draw.Image
   231  				if f.P0 <= cn0 && cn0 < f.P1 { // b+1 is inside selection
   232  					col = f.Cols[HIGH]
   233  				} else {
   234  					col = f.Cols[BACK]
   235  				}
   236  				f.B.Draw(r, col, nil, r.Min)
   237  			}
   238  			y = pt.Y
   239  			cn0 -= b.nrune
   240  		} else {
   241  			r := draw.Rectangle{Min: pt, Max: pt}
   242  			r.Max.X += b.wid
   243  			r.Max.Y += f.Font.Height
   244  			if r.Max.X >= f.R.Max.X {
   245  				r.Max.X = f.R.Max.X
   246  			}
   247  			cn0--
   248  			var col *draw.Image
   249  			if f.P0 <= cn0 && cn0 < f.P1 { // b is inside selection
   250  				col = f.Cols[HIGH]
   251  			} else {
   252  				col = f.Cols[BACK]
   253  			}
   254  			f.B.Draw(r, col, nil, r.Min)
   255  			y = 0
   256  			if pt.X == f.R.Min.X {
   257  				y = pt.Y
   258  			}
   259  		}
   260  	}
   261  	// insertion can extend the selection, so the condition here is different.
   262  	var col, tcol *draw.Image
   263  	if f.P0 < p0 && p0 <= f.P1 {
   264  		col = f.Cols[HIGH]
   265  		tcol = f.Cols[HTEXT]
   266  	} else {
   267  		col = f.Cols[BACK]
   268  		tcol = f.Cols[TEXT]
   269  	}
   270  	f.SelectPaint(ppt0, ppt1, col)
   271  	tmpf.drawtext(ppt0, tcol, col)
   272  	f.addbox(nn0, len(tmpf.box))
   273  	copy(f.box[nn0:], tmpf.box)
   274  	if nn0 > 0 && f.box[nn0-1].nrune >= 0 && ppt0.X-f.box[nn0-1].wid >= f.R.Min.X {
   275  		// There's some text just before the insertion point. Make sure we clean up from there.
   276  		// TODO when can the start of that box ever be outside the frame rectangle bounds?
   277  		nn0--
   278  		ppt0.X -= f.box[nn0].wid
   279  	}
   280  	n0 += len(tmpf.box)
   281  	if n0 < len(f.box)-1 {
   282  		n0++
   283  	}
   284  	f.clean(ppt0, nn0, n0)
   285  	f.NumChars += tmpf.NumChars
   286  	if f.P0 >= p0 {
   287  		f.P0 += tmpf.NumChars
   288  	}
   289  	if f.P0 > f.NumChars {
   290  		f.P0 = f.NumChars
   291  	}
   292  	if f.P1 >= p0 {
   293  		f.P1 += tmpf.NumChars
   294  	}
   295  	if f.P1 > f.NumChars {
   296  		f.P1 = f.NumChars
   297  	}
   298  	if f.P0 == f.P1 {
   299  		f.Tick(f.PointOf(f.P0), true)
   300  	}
   301  }