initial stubs for editor package
[nikiroo-utils.git] / src / jexer / teditor / Line.java
1 /*
2 * Jexer - Java Text User Interface
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (C) 2017 Kevin Lamonte
7 *
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:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
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.
25 *
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
27 * @version 1
28 */
29 package jexer.teditor;
30
31 import java.util.ArrayList;
32 import java.util.List;
33
34 import jexer.bits.Cell;
35 import jexer.bits.CellAttributes;
36
37 /**
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.
40 */
41 public class Line implements Fragment {
42
43 /**
44 * The cells of the line.
45 */
46 private List<Cell> cells;
47
48 /**
49 * The line number.
50 */
51 private int lineNumber;
52
53 /**
54 * The previous Fragment in the list.
55 */
56 private Fragment prevFrag;
57
58 /**
59 * The next Fragment in the list.
60 */
61 private Fragment nextFrag;
62
63 /**
64 * Construct a new Line from an existing text string.
65 */
66 public Line() {
67 this("");
68 }
69
70 /**
71 * Construct a new Line from an existing text string.
72 *
73 * @param text the code points of the line
74 */
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)));
79 }
80 }
81
82 /**
83 * Reset all colors of this Line to white-on-black.
84 */
85 public void resetColors() {
86 setColors(new CellAttributes());
87 }
88
89 /**
90 * Set all colors of this Line to one color.
91 *
92 * @param color the new color to use
93 */
94 public void setColors(final CellAttributes color) {
95 for (Cell cell: cells) {
96 cell.setTo(color);
97 }
98 }
99
100 /**
101 * Set the color of one cell.
102 *
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()
107 */
108 public void setColor(final int index, final CellAttributes color) {
109 cells.get(index).setTo(color);
110 }
111
112 /**
113 * Get the raw text that will be rendered.
114 *
115 * @return the text
116 */
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();
121 }
122 return new String(text);
123 }
124
125 /**
126 * Get the attributes for a cell.
127 *
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()
132 */
133 public CellAttributes getColor(final int index) {
134 return cells.get(index);
135 }
136
137 /**
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
142 * clusters.
143 *
144 * @return the number of fixed-width cells this fragment's text will
145 * render to
146 */
147 public int getCellCount() {
148 return cells.size();
149 }
150
151 /**
152 * Get the text to render for a specific fixed-width cell.
153 *
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()
158 */
159 public Cell getCell(final int index) {
160 return cells.get(index);
161 }
162
163 /**
164 * Get the text to render for several fixed-width cells.
165 *
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()
171 */
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();
176 }
177 return new String(text);
178 }
179
180 /**
181 * Sets (replaces) the text to render for a specific fixed-width cell.
182 *
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()
187 */
188 public void setCell(final int index, final char ch) {
189 cells.set(index, new Cell(ch));
190 }
191
192 /**
193 * Sets (replaces) the text to render for a specific fixed-width cell.
194 *
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()
199 */
200 public void setCell(final int index, final Cell cell) {
201 cells.set(index, cell);
202 }
203
204 /**
205 * Inserts a char to render for a specific fixed-width cell.
206 *
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()
211 */
212 public void insertCell(final int index, final char ch) {
213 cells.add(index, new Cell(ch));
214 }
215
216 /**
217 * Inserts a Cell to render for a specific fixed-width cell.
218 *
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()
223 */
224 public void insertCell(final int index, final Cell cell) {
225 cells.add(index, cell);
226 }
227
228 /**
229 * Delete a specific fixed-width cell.
230 *
231 * @param index a cell number, between 0 and getCellCount() - 1
232 * @throws IndexOutOfBoundsException if index is negative or not less
233 * than getCellCount()
234 */
235 public void deleteCell(final int index) {
236 cells.remove(index);
237 }
238
239 /**
240 * Delete several fixed-width cells.
241 *
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()
246 */
247 public void deleteCells(final int start, final int length) {
248 for (int i = 0; i < length; i++) {
249 cells.remove(start);
250 }
251 }
252
253 /**
254 * Appends a char to render for a specific fixed-width cell.
255 *
256 * @param ch the character for this fixed-width cell
257 */
258 public void appendCell(final char ch) {
259 cells.add(new Cell(ch));
260 }
261
262 /**
263 * Inserts a Cell to render for a specific fixed-width cell.
264 *
265 * @param cell the new value for this fixed-width cell
266 */
267 public void appendCell(final Cell cell) {
268 cells.add(cell);
269 }
270
271 /**
272 * Get the next Fragment in the list, or null if this Fragment is the
273 * last node.
274 *
275 * @return the next Fragment, or null
276 */
277 public Fragment next() {
278 return nextFrag;
279 }
280
281 /**
282 * Get the previous Fragment in the list, or null if this Fragment is the
283 * first node.
284 *
285 * @return the previous Fragment, or null
286 */
287 public Fragment prev() {
288 return prevFrag;
289 }
290
291 /**
292 * See if this Fragment can be joined with the next Fragment in list.
293 *
294 * @return true if the join was possible, false otherwise
295 */
296 public boolean isNextJoinable() {
297 if ((nextFrag != null) && (nextFrag instanceof Line)) {
298 return true;
299 }
300 return false;
301 }
302
303 /**
304 * Join this Fragment with the next Fragment in list.
305 *
306 * @return true if the join was successful, false otherwise
307 */
308 public boolean joinNext() {
309 if ((nextFrag == null) || !(nextFrag instanceof Line)) {
310 return false;
311 }
312 Line q = (Line) nextFrag;
313 ArrayList<Cell> newCells = new ArrayList<Cell>(this.cells.size() +
314 q.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;
320 return true;
321 }
322
323 /**
324 * See if this Fragment can be joined with the previous Fragment in list.
325 *
326 * @return true if the join was possible, false otherwise
327 */
328 public boolean isPrevJoinable() {
329 if ((prevFrag != null) && (prevFrag instanceof Line)) {
330 return true;
331 }
332 return false;
333 }
334
335 /**
336 * Join this Fragment with the previous Fragment in list.
337 *
338 * @return true if the join was successful, false otherwise
339 */
340 public boolean joinPrev() {
341 if ((prevFrag == null) || !(prevFrag instanceof Line)) {
342 return false;
343 }
344 Line p = (Line) prevFrag;
345 ArrayList<Cell> newCells = new ArrayList<Cell>(this.cells.size() +
346 p.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;
352 return true;
353 }
354
355 /**
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.
359 *
360 * @param fragment the next Fragment, or null
361 */
362 public void setNext(Fragment fragment) {
363 nextFrag = fragment;
364 }
365
366 /**
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.
370 *
371 * @param fragment the previous Fragment, or null
372 */
373 public void setPrev(Fragment fragment) {
374 prevFrag = fragment;
375 }
376
377 /**
378 * Split this Fragment into two. 'this' Fragment will contain length
379 * cells, 'this.next()' will contain (getCellCount() - length) cells.
380 *
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)
384 */
385 public void split(final int length) {
386 // Create the next node
387 Line q = new Line();
388 q.nextFrag = nextFrag;
389 q.prevFrag = this;
390 ((Line) nextFrag).prevFrag = q;
391 nextFrag = q;
392
393 // Split cells
394 q.cells = new ArrayList<Cell>(cells.size() - length);
395 q.cells.addAll(cells.subList(length, cells.size()));
396 cells = cells.subList(0, length);
397 }
398
399 /**
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.
405 *
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)
410 */
411 public void split(final int index, Fragment fragment) {
412 // Create the next node and insert into the list.
413 Line q = new Line();
414 q.nextFrag = nextFrag;
415 q.nextFrag.setPrev(q);
416 q.prevFrag = fragment;
417 fragment.setNext(q);
418 fragment.setPrev(this);
419 nextFrag = fragment;
420
421 // Split cells
422 q.cells = new ArrayList<Cell>(cells.size() - index);
423 q.cells.addAll(cells.subList(index, cells.size()));
424 cells = cells.subList(0, index);
425 }
426
427 /**
428 * Insert a new Fragment before this one.
429 *
430 * @param fragment the Fragment to insert
431 */
432 public void insert(Fragment fragment) {
433 fragment.setNext(this);
434 fragment.setPrev(prevFrag);
435 prevFrag.setNext(fragment);
436 prevFrag = fragment;
437 }
438
439 /**
440 * Append a new Fragment at the end of this one.
441 *
442 * @param fragment the Fragment to append
443 */
444 public void append(Fragment fragment) {
445 fragment.setNext(nextFrag);
446 fragment.setPrev(this);
447 nextFrag.setPrev(fragment);
448 nextFrag = fragment;
449 }
450
451 /**
452 * Delete this Fragment from the list, and return its next().
453 *
454 * @return this Fragment's next(), or null if it was at the end of the
455 * list
456 */
457 public Fragment deleteGetNext() {
458 Fragment result = nextFrag;
459 nextFrag.setPrev(prevFrag);
460 prevFrag.setNext(nextFrag);
461 prevFrag = null;
462 nextFrag = null;
463 return result;
464 }
465
466 /**
467 * Delete this Fragment from the list, and return its prev().
468 *
469 * @return this Fragment's next(), or null if it was at the beginning of
470 * the list
471 */
472 public Fragment deleteGetPrev() {
473 Fragment result = prevFrag;
474 nextFrag.setPrev(prevFrag);
475 prevFrag.setNext(nextFrag);
476 prevFrag = null;
477 nextFrag = null;
478 return result;
479 }
480
481 /**
482 * Get the anchor position.
483 *
484 * @return the anchor number
485 */
486 public int getAnchor() {
487 return lineNumber;
488 }
489
490 /**
491 * Set the anchor position.
492 *
493 * @param x the new anchor number
494 */
495 public void setAnchor(final int x) {
496 lineNumber = x;
497 }
498
499 }