2 * Jexer - Java Text User Interface
4 * The MIT License (MIT)
6 * Copyright (C) 2017 Kevin Lamonte
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the "Software"),
10 * to deal in the Software without restriction, including without limitation
11 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 * and/or sell copies of the Software, and to permit persons to whom the
13 * Software is furnished to do so, subject to the following conditions:
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 * DEALINGS IN THE SOFTWARE.
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
29 package jexer
.teditor
;
31 import java
.util
.ArrayList
;
32 import java
.util
.List
;
34 import jexer
.bits
.Cell
;
35 import jexer
.bits
.CellAttributes
;
38 * A Line represents a single line of text on the screen. Each character is
39 * a Cell, so it can have color attributes in addition to the basic char.
41 public class Line
implements Fragment
{
44 * The cells of the line.
46 private List
<Cell
> cells
;
51 private int lineNumber
;
54 * The previous Fragment in the list.
56 private Fragment prevFrag
;
59 * The next Fragment in the list.
61 private Fragment nextFrag
;
64 * Construct a new Line from an existing text string.
71 * Construct a new Line from an existing text string.
73 * @param text the code points of the line
75 public Line(final String text
) {
76 cells
= new ArrayList
<Cell
>(text
.length());
77 for (int i
= 0; i
< text
.length(); i
++) {
78 cells
.add(new Cell(text
.charAt(i
)));
83 * Reset all colors of this Line to white-on-black.
85 public void resetColors() {
86 setColors(new CellAttributes());
90 * Set all colors of this Line to one color.
92 * @param color the new color to use
94 public void setColors(final CellAttributes color
) {
95 for (Cell cell
: cells
) {
101 * Set the color of one cell.
103 * @param index a cell number, between 0 and getCellCount()
104 * @param color the new color to use
105 * @throws IndexOutOfBoundsException if index is negative or not less
106 * than getCellCount()
108 public void setColor(final int index
, final CellAttributes color
) {
109 cells
.get(index
).setTo(color
);
113 * Get the raw text that will be rendered.
117 public String
getText() {
118 char [] text
= new char[cells
.size()];
119 for (int i
= 0; i
< cells
.size(); i
++) {
120 text
[i
] = cells
.get(i
).getChar();
122 return new String(text
);
126 * Get the attributes for a cell.
128 * @param index a cell number, between 0 and getCellCount()
129 * @return the attributes
130 * @throws IndexOutOfBoundsException if index is negative or not less
131 * than getCellCount()
133 public CellAttributes
getColor(final int index
) {
134 return cells
.get(index
);
138 * Get the number of graphical cells represented by this text. Note that
139 * a Unicode grapheme cluster can take any number of pixels, but this
140 * editor is intended to be used with a fixed-width font. So this count
141 * returns the number of fixed-width cells, NOT the number of grapheme
144 * @return the number of fixed-width cells this fragment's text will
147 public int getCellCount() {
152 * Get the text to render for a specific fixed-width cell.
154 * @param index a cell number, between 0 and getCellCount()
155 * @return the codepoints to render for this fixed-width cell
156 * @throws IndexOutOfBoundsException if index is negative or not less
157 * than getCellCount()
159 public Cell
getCell(final int index
) {
160 return cells
.get(index
);
164 * Get the text to render for several fixed-width cells.
166 * @param start a cell number, between 0 and getCellCount()
167 * @param length the number of cells to return
168 * @return the codepoints to render for this fixed-width cell
169 * @throws IndexOutOfBoundsException if start or (start + length) is
170 * negative or not less than getCellCount()
172 public String
getCells(final int start
, final int length
) {
173 char [] text
= new char[length
];
174 for (int i
= 0; i
< length
; i
++) {
175 text
[i
] = cells
.get(i
+ start
).getChar();
177 return new String(text
);
181 * Sets (replaces) the text to render for a specific fixed-width cell.
183 * @param index a cell number, between 0 and getCellCount()
184 * @param ch the character for this fixed-width cell
185 * @throws IndexOutOfBoundsException if index is negative or not less
186 * than getCellCount()
188 public void setCell(final int index
, final char ch
) {
189 cells
.set(index
, new Cell(ch
));
193 * Sets (replaces) the text to render for a specific fixed-width cell.
195 * @param index a cell number, between 0 and getCellCount()
196 * @param cell the new value for this fixed-width cell
197 * @throws IndexOutOfBoundsException if index is negative or not less
198 * than getCellCount()
200 public void setCell(final int index
, final Cell cell
) {
201 cells
.set(index
, cell
);
205 * Inserts a char to render for a specific fixed-width cell.
207 * @param index a cell number, between 0 and getCellCount() - 1
208 * @param ch the character for this fixed-width cell
209 * @throws IndexOutOfBoundsException if index is negative or not less
210 * than getCellCount()
212 public void insertCell(final int index
, final char ch
) {
213 cells
.add(index
, new Cell(ch
));
217 * Inserts a Cell to render for a specific fixed-width cell.
219 * @param index a cell number, between 0 and getCellCount() - 1
220 * @param cell the new value for this fixed-width cell
221 * @throws IndexOutOfBoundsException if index is negative or not less
222 * than getCellCount()
224 public void insertCell(final int index
, final Cell cell
) {
225 cells
.add(index
, cell
);
229 * Delete a specific fixed-width cell.
231 * @param index a cell number, between 0 and getCellCount() - 1
232 * @throws IndexOutOfBoundsException if index is negative or not less
233 * than getCellCount()
235 public void deleteCell(final int index
) {
240 * Delete several fixed-width cells.
242 * @param start a cell number, between 0 and getCellCount() - 1
243 * @param length the number of cells to delete
244 * @throws IndexOutOfBoundsException if index is negative or not less
245 * than getCellCount()
247 public void deleteCells(final int start
, final int length
) {
248 for (int i
= 0; i
< length
; i
++) {
254 * Appends a char to render for a specific fixed-width cell.
256 * @param ch the character for this fixed-width cell
258 public void appendCell(final char ch
) {
259 cells
.add(new Cell(ch
));
263 * Inserts a Cell to render for a specific fixed-width cell.
265 * @param cell the new value for this fixed-width cell
267 public void appendCell(final Cell cell
) {
272 * Get the next Fragment in the list, or null if this Fragment is the
275 * @return the next Fragment, or null
277 public Fragment
next() {
282 * Get the previous Fragment in the list, or null if this Fragment is the
285 * @return the previous Fragment, or null
287 public Fragment
prev() {
292 * See if this Fragment can be joined with the next Fragment in list.
294 * @return true if the join was possible, false otherwise
296 public boolean isNextJoinable() {
297 if ((nextFrag
!= null) && (nextFrag
instanceof Line
)) {
304 * Join this Fragment with the next Fragment in list.
306 * @return true if the join was successful, false otherwise
308 public boolean joinNext() {
309 if ((nextFrag
== null) || !(nextFrag
instanceof Line
)) {
312 Line q
= (Line
) nextFrag
;
313 ArrayList
<Cell
> newCells
= new ArrayList
<Cell
>(this.cells
.size() +
315 newCells
.addAll(this.cells
);
316 newCells
.addAll(q
.cells
);
317 this.cells
= newCells
;
318 ((Line
) q
.nextFrag
).prevFrag
= this;
319 nextFrag
= q
.nextFrag
;
324 * See if this Fragment can be joined with the previous Fragment in list.
326 * @return true if the join was possible, false otherwise
328 public boolean isPrevJoinable() {
329 if ((prevFrag
!= null) && (prevFrag
instanceof Line
)) {
336 * Join this Fragment with the previous Fragment in list.
338 * @return true if the join was successful, false otherwise
340 public boolean joinPrev() {
341 if ((prevFrag
== null) || !(prevFrag
instanceof Line
)) {
344 Line p
= (Line
) prevFrag
;
345 ArrayList
<Cell
> newCells
= new ArrayList
<Cell
>(this.cells
.size() +
347 newCells
.addAll(p
.cells
);
348 newCells
.addAll(this.cells
);
349 this.cells
= newCells
;
350 ((Line
) p
.prevFrag
).nextFrag
= this;
351 prevFrag
= p
.prevFrag
;
356 * Set the next Fragment in the list. Note that this performs no sanity
357 * checking or modifications on fragment; this function can break
358 * connectivity in the list.
360 * @param fragment the next Fragment, or null
362 public void setNext(Fragment fragment
) {
367 * Set the previous Fragment in the list. Note that this performs no
368 * sanity checking or modifications on fragment; this function can break
369 * connectivity in the list.
371 * @param fragment the previous Fragment, or null
373 public void setPrev(Fragment fragment
) {
378 * Split this Fragment into two. 'this' Fragment will contain length
379 * cells, 'this.next()' will contain (getCellCount() - length) cells.
381 * @param length the number of cells to leave in this Fragment
382 * @throws IndexOutOfBoundsException if length is negative, or 0, greater
383 * than (getCellCount() - 1)
385 public void split(final int length
) {
386 // Create the next node
388 q
.nextFrag
= nextFrag
;
390 ((Line
) nextFrag
).prevFrag
= q
;
394 q
.cells
= new ArrayList
<Cell
>(cells
.size() - length
);
395 q
.cells
.addAll(cells
.subList(length
, cells
.size()));
396 cells
= cells
.subList(0, length
);
400 * Insert a new Fragment at a position, splitting the contents of this
401 * Fragment into two around it. 'this' Fragment will contain the cells
402 * between 0 and index, 'this.next()' will be the inserted fragment, and
403 * 'this.next().next()' will contain the cells between 'index' and
404 * getCellCount() - 1.
406 * @param index the number of cells to leave in this Fragment
407 * @param fragment the Fragment to insert
408 * @throws IndexOutOfBoundsException if length is negative, or 0, greater
409 * than (getCellCount() - 1)
411 public void split(final int index
, Fragment fragment
) {
412 // Create the next node and insert into the list.
414 q
.nextFrag
= nextFrag
;
415 q
.nextFrag
.setPrev(q
);
416 q
.prevFrag
= fragment
;
418 fragment
.setPrev(this);
422 q
.cells
= new ArrayList
<Cell
>(cells
.size() - index
);
423 q
.cells
.addAll(cells
.subList(index
, cells
.size()));
424 cells
= cells
.subList(0, index
);
428 * Insert a new Fragment before this one.
430 * @param fragment the Fragment to insert
432 public void insert(Fragment fragment
) {
433 fragment
.setNext(this);
434 fragment
.setPrev(prevFrag
);
435 prevFrag
.setNext(fragment
);
440 * Append a new Fragment at the end of this one.
442 * @param fragment the Fragment to append
444 public void append(Fragment fragment
) {
445 fragment
.setNext(nextFrag
);
446 fragment
.setPrev(this);
447 nextFrag
.setPrev(fragment
);
452 * Delete this Fragment from the list, and return its next().
454 * @return this Fragment's next(), or null if it was at the end of the
457 public Fragment
deleteGetNext() {
458 Fragment result
= nextFrag
;
459 nextFrag
.setPrev(prevFrag
);
460 prevFrag
.setNext(nextFrag
);
467 * Delete this Fragment from the list, and return its prev().
469 * @return this Fragment's next(), or null if it was at the beginning of
472 public Fragment
deleteGetPrev() {
473 Fragment result
= prevFrag
;
474 nextFrag
.setPrev(prevFrag
);
475 prevFrag
.setNext(nextFrag
);
482 * Get the anchor position.
484 * @return the anchor number
486 public int getAnchor() {
491 * Set the anchor position.
493 * @param x the new anchor number
495 public void setAnchor(final int x
) {