1 | /* |
2 | This file is part of Konsole, an X terminal. |
3 | |
4 | Copyright 2007-2008 by Robert Knight <robert.knight@gmail.com> |
5 | Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de> |
6 | |
7 | This program is free software; you can redistribute it and/or modify |
8 | it under the terms of the GNU General Public License as published by |
9 | the Free Software Foundation; either version 2 of the License, or |
10 | (at your option) any later version. |
11 | |
12 | This program is distributed in the hope that it will be useful, |
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 | GNU General Public License for more details. |
16 | |
17 | You should have received a copy of the GNU General Public License |
18 | along with this program; if not, write to the Free Software |
19 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
20 | 02110-1301 USA. |
21 | */ |
22 | |
23 | // Own |
24 | #include "Screen.h" |
25 | |
26 | // Standard |
27 | #include <stdio.h> |
28 | #include <stdlib.h> |
29 | #include <unistd.h> |
30 | #include <string.h> |
31 | #include <ctype.h> |
32 | |
33 | // Qt |
34 | #include <QTextStream> |
35 | #include <QDate> |
36 | |
37 | // KDE |
38 | //#include <kdebug.h> |
39 | |
40 | // Konsole |
41 | #include "konsole_wcwidth.h" |
42 | #include "TerminalCharacterDecoder.h" |
43 | |
44 | using namespace Konsole; |
45 | |
46 | //FIXME: this is emulation specific. Use false for xterm, true for ANSI. |
47 | //FIXME: see if we can get this from terminfo. |
48 | #define BS_CLEARS false |
49 | |
50 | //Macro to convert x,y position on screen to position within an image. |
51 | // |
52 | //Originally the image was stored as one large contiguous block of |
53 | //memory, so a position within the image could be represented as an |
54 | //offset from the beginning of the block. For efficiency reasons this |
55 | //is no longer the case. |
56 | //Many internal parts of this class still use this representation for parameters and so on, |
57 | //notably moveImage() and clearImage(). |
58 | //This macro converts from an X,Y position into an image offset. |
59 | #ifndef loc |
60 | #define loc(X,Y) ((Y)*columns+(X)) |
61 | #endif |
62 | |
63 | |
64 | Character Screen::defaultChar = Character(' ', |
65 | CharacterColor(COLOR_SPACE_DEFAULT,DEFAULT_FORE_COLOR), |
66 | CharacterColor(COLOR_SPACE_DEFAULT,DEFAULT_BACK_COLOR), |
67 | DEFAULT_RENDITION); |
68 | |
69 | //#define REVERSE_WRAPPED_LINES // for wrapped line debug |
70 | |
71 | Screen::Screen(int l, int c) |
72 | : lines(l), |
73 | columns(c), |
74 | screenLines(new ImageLine[lines+1] ), |
75 | _scrolledLines(0), |
76 | _droppedLines(0), |
77 | history(new HistoryScrollNone()), |
78 | cuX(0), cuY(0), |
79 | currentRendition(0), |
80 | _topMargin(0), _bottomMargin(0), |
81 | selBegin(0), selTopLeft(0), selBottomRight(0), |
82 | blockSelectionMode(false), |
83 | effectiveForeground(CharacterColor()), effectiveBackground(CharacterColor()), effectiveRendition(0), |
84 | lastPos(-1) |
85 | { |
86 | lineProperties.resize(lines+1); |
87 | for (int i=0;i<lines+1;i++) |
88 | lineProperties[i]=LINE_DEFAULT; |
89 | |
90 | initTabStops(); |
91 | clearSelection(); |
92 | reset(); |
93 | } |
94 | |
95 | /*! Destructor |
96 | */ |
97 | |
98 | Screen::~Screen() |
99 | { |
100 | delete[] screenLines; |
101 | delete history; |
102 | } |
103 | |
104 | void Screen::cursorUp(int n) |
105 | //=CUU |
106 | { |
107 | if (n == 0) n = 1; // Default |
108 | int stop = cuY < _topMargin ? 0 : _topMargin; |
109 | cuX = qMin(columns-1,cuX); // nowrap! |
110 | cuY = qMax(stop,cuY-n); |
111 | } |
112 | |
113 | void Screen::cursorDown(int n) |
114 | //=CUD |
115 | { |
116 | if (n == 0) n = 1; // Default |
117 | int stop = cuY > _bottomMargin ? lines-1 : _bottomMargin; |
118 | cuX = qMin(columns-1,cuX); // nowrap! |
119 | cuY = qMin(stop,cuY+n); |
120 | } |
121 | |
122 | void Screen::cursorLeft(int n) |
123 | //=CUB |
124 | { |
125 | if (n == 0) n = 1; // Default |
126 | cuX = qMin(columns-1,cuX); // nowrap! |
127 | cuX = qMax(0,cuX-n); |
128 | } |
129 | |
130 | void Screen::cursorRight(int n) |
131 | //=CUF |
132 | { |
133 | if (n == 0) n = 1; // Default |
134 | cuX = qMin(columns-1,cuX+n); |
135 | } |
136 | |
137 | void Screen::setMargins(int top, int bot) |
138 | //=STBM |
139 | { |
140 | if (top == 0) top = 1; // Default |
141 | if (bot == 0) bot = lines; // Default |
142 | top = top - 1; // Adjust to internal lineno |
143 | bot = bot - 1; // Adjust to internal lineno |
144 | if ( !( 0 <= top && top < bot && bot < lines ) ) |
145 | { //Debug()<<" setRegion("<<top<<","<<bot<<") : bad range."; |
146 | return; // Default error action: ignore |
147 | } |
148 | _topMargin = top; |
149 | _bottomMargin = bot; |
150 | cuX = 0; |
151 | cuY = getMode(MODE_Origin) ? top : 0; |
152 | |
153 | } |
154 | |
155 | int Screen::topMargin() const |
156 | { |
157 | return _topMargin; |
158 | } |
159 | int Screen::bottomMargin() const |
160 | { |
161 | return _bottomMargin; |
162 | } |
163 | |
164 | void Screen::index() |
165 | //=IND |
166 | { |
167 | if (cuY == _bottomMargin) |
168 | scrollUp(1); |
169 | else if (cuY < lines-1) |
170 | cuY += 1; |
171 | } |
172 | |
173 | void Screen::reverseIndex() |
174 | //=RI |
175 | { |
176 | if (cuY == _topMargin) |
177 | scrollDown(_topMargin,1); |
178 | else if (cuY > 0) |
179 | cuY -= 1; |
180 | } |
181 | |
182 | void Screen::nextLine() |
183 | //=NEL |
184 | { |
185 | toStartOfLine(); index(); |
186 | } |
187 | |
188 | void Screen::eraseChars(int n) |
189 | { |
190 | if (n == 0) n = 1; // Default |
191 | int p = qMax(0,qMin(cuX+n-1,columns-1)); |
192 | clearImage(loc(cuX,cuY),loc(p,cuY),' '); |
193 | } |
194 | |
195 | void Screen::deleteChars(int n) |
196 | { |
197 | Q_ASSERT( n >= 0 ); |
198 | |
199 | // always delete at least one char |
200 | if (n == 0) |
201 | n = 1; |
202 | |
203 | // if cursor is beyond the end of the line there is nothing to do |
204 | if ( cuX >= screenLines[cuY].count() ) |
205 | return; |
206 | |
207 | if ( cuX+n > screenLines[cuY].count() ) |
208 | n = screenLines[cuY].count() - cuX; |
209 | |
210 | Q_ASSERT( n >= 0 ); |
211 | Q_ASSERT( cuX+n <= screenLines[cuY].count() ); |
212 | |
213 | screenLines[cuY].remove(cuX,n); |
214 | } |
215 | |
216 | void Screen::insertChars(int n) |
217 | { |
218 | if (n == 0) n = 1; // Default |
219 | |
220 | if ( screenLines[cuY].size() < cuX ) |
221 | screenLines[cuY].resize(cuX); |
222 | |
223 | screenLines[cuY].insert(cuX,n,' '); |
224 | |
225 | if ( screenLines[cuY].count() > columns ) |
226 | screenLines[cuY].resize(columns); |
227 | } |
228 | |
229 | void Screen::repeatChars(int count) |
230 | //=REP |
231 | { |
232 | if (count == 0) |
233 | { |
234 | count = 1; |
235 | } |
236 | /** |
237 | * From ECMA-48 version 5, section 8.3.103 |
238 | * If the character preceding REP is a control function or part of a |
239 | * control function, the effect of REP is not defined by this Standard. |
240 | * |
241 | * So, a "normal" program should always use REP immediately after a visible |
242 | * character (those other than escape sequences). So, lastDrawnChar can be |
243 | * safely used. |
244 | */ |
245 | for (int i = 0; i < count; i++) |
246 | { |
247 | displayCharacter(lastDrawnChar); |
248 | } |
249 | } |
250 | |
251 | void Screen::deleteLines(int n) |
252 | { |
253 | if (n == 0) n = 1; // Default |
254 | scrollUp(cuY,n); |
255 | } |
256 | |
257 | void Screen::insertLines(int n) |
258 | { |
259 | if (n == 0) n = 1; // Default |
260 | scrollDown(cuY,n); |
261 | } |
262 | |
263 | void Screen::setMode(int m) |
264 | { |
265 | currentModes[m] = true; |
266 | switch(m) |
267 | { |
268 | case MODE_Origin : cuX = 0; cuY = _topMargin; break; //FIXME: home |
269 | } |
270 | } |
271 | |
272 | void Screen::resetMode(int m) |
273 | { |
274 | currentModes[m] = false; |
275 | switch(m) |
276 | { |
277 | case MODE_Origin : cuX = 0; cuY = 0; break; //FIXME: home |
278 | } |
279 | } |
280 | |
281 | void Screen::saveMode(int m) |
282 | { |
283 | savedModes[m] = currentModes[m]; |
284 | } |
285 | |
286 | void Screen::restoreMode(int m) |
287 | { |
288 | currentModes[m] = savedModes[m]; |
289 | } |
290 | |
291 | bool Screen::getMode(int m) const |
292 | { |
293 | return currentModes[m]; |
294 | } |
295 | |
296 | void Screen::saveCursor() |
297 | { |
298 | savedState.cursorColumn = cuX; |
299 | savedState.cursorLine = cuY; |
300 | savedState.rendition = currentRendition; |
301 | savedState.foreground = currentForeground; |
302 | savedState.background = currentBackground; |
303 | } |
304 | |
305 | void Screen::restoreCursor() |
306 | { |
307 | cuX = qMin(savedState.cursorColumn,columns-1); |
308 | cuY = qMin(savedState.cursorLine,lines-1); |
309 | currentRendition = savedState.rendition; |
310 | currentForeground = savedState.foreground; |
311 | currentBackground = savedState.background; |
312 | updateEffectiveRendition(); |
313 | } |
314 | |
315 | void Screen::resizeImage(int new_lines, int new_columns) |
316 | { |
317 | if ((new_lines==lines) && (new_columns==columns)) return; |
318 | |
319 | if (cuY > new_lines-1) |
320 | { // attempt to preserve focus and lines |
321 | _bottomMargin = lines-1; //FIXME: margin lost |
322 | for (int i = 0; i < cuY-(new_lines-1); i++) |
323 | { |
324 | addHistLine(); scrollUp(0,1); |
325 | } |
326 | } |
327 | |
328 | // create new screen lines and copy from old to new |
329 | |
330 | ImageLine* newScreenLines = new ImageLine[new_lines+1]; |
331 | for (int i=0; i < qMin(lines,new_lines+1) ;i++) |
332 | newScreenLines[i]=screenLines[i]; |
333 | for (int i=lines;(i > 0) && (i<new_lines+1);i++) |
334 | newScreenLines[i].resize( new_columns ); |
335 | |
336 | lineProperties.resize(new_lines+1); |
337 | for (int i=lines;(i > 0) && (i<new_lines+1);i++) |
338 | lineProperties[i] = LINE_DEFAULT; |
339 | |
340 | clearSelection(); |
341 | |
342 | delete[] screenLines; |
343 | screenLines = newScreenLines; |
344 | |
345 | lines = new_lines; |
346 | columns = new_columns; |
347 | cuX = qMin(cuX,columns-1); |
348 | cuY = qMin(cuY,lines-1); |
349 | |
350 | // FIXME: try to keep values, evtl. |
351 | _topMargin=0; |
352 | _bottomMargin=lines-1; |
353 | initTabStops(); |
354 | clearSelection(); |
355 | } |
356 | |
357 | void Screen::setDefaultMargins() |
358 | { |
359 | _topMargin = 0; |
360 | _bottomMargin = lines-1; |
361 | } |
362 | |
363 | |
364 | /* |
365 | Clarifying rendition here and in the display. |
366 | |
367 | currently, the display's color table is |
368 | 0 1 2 .. 9 10 .. 17 |
369 | dft_fg, dft_bg, dim 0..7, intensive 0..7 |
370 | |
371 | currentForeground, currentBackground contain values 0..8; |
372 | - 0 = default color |
373 | - 1..8 = ansi specified color |
374 | |
375 | re_fg, re_bg contain values 0..17 |
376 | due to the TerminalDisplay's color table |
377 | |
378 | rendition attributes are |
379 | |
380 | attr widget screen |
381 | -------------- ------ ------ |
382 | RE_UNDERLINE XX XX affects foreground only |
383 | RE_BLINK XX XX affects foreground only |
384 | RE_BOLD XX XX affects foreground only |
385 | RE_REVERSE -- XX |
386 | RE_TRANSPARENT XX -- affects background only |
387 | RE_INTENSIVE XX -- affects foreground only |
388 | |
389 | Note that RE_BOLD is used in both widget |
390 | and screen rendition. Since xterm/vt102 |
391 | is to poor to distinguish between bold |
392 | (which is a font attribute) and intensive |
393 | (which is a color attribute), we translate |
394 | this and RE_BOLD in falls eventually appart |
395 | into RE_BOLD and RE_INTENSIVE. |
396 | */ |
397 | |
398 | void Screen::reverseRendition(Character& p) const |
399 | { |
400 | CharacterColor f = p.foregroundColor; |
401 | CharacterColor b = p.backgroundColor; |
402 | |
403 | p.foregroundColor = b; |
404 | p.backgroundColor = f; //p->r &= ~RE_TRANSPARENT; |
405 | } |
406 | |
407 | void Screen::updateEffectiveRendition() |
408 | { |
409 | effectiveRendition = currentRendition; |
410 | if (currentRendition & RE_REVERSE) |
411 | { |
412 | effectiveForeground = currentBackground; |
413 | effectiveBackground = currentForeground; |
414 | } |
415 | else |
416 | { |
417 | effectiveForeground = currentForeground; |
418 | effectiveBackground = currentBackground; |
419 | } |
420 | |
421 | if (currentRendition & RE_BOLD) |
422 | effectiveForeground.setIntensive(); |
423 | } |
424 | |
425 | void Screen::copyFromHistory(Character* dest, int startLine, int count) const |
426 | { |
427 | Q_ASSERT( startLine >= 0 && count > 0 && startLine + count <= history->getLines() ); |
428 | |
429 | for (int line = startLine; line < startLine + count; line++) |
430 | { |
431 | const int length = qMin(columns,history->getLineLen(line)); |
432 | const int destLineOffset = (line-startLine)*columns; |
433 | |
434 | history->getCells(line,0,length,dest + destLineOffset); |
435 | |
436 | for (int column = length; column < columns; column++) |
437 | dest[destLineOffset+column] = defaultChar; |
438 | |
439 | // invert selected text |
440 | if (selBegin !=-1) |
441 | { |
442 | for (int column = 0; column < columns; column++) |
443 | { |
444 | if (isSelected(column,line)) |
445 | { |
446 | reverseRendition(dest[destLineOffset + column]); |
447 | } |
448 | } |
449 | } |
450 | } |
451 | } |
452 | |
453 | void Screen::copyFromScreen(Character* dest , int startLine , int count) const |
454 | { |
455 | Q_ASSERT( startLine >= 0 && count > 0 && startLine + count <= lines ); |
456 | |
457 | for (int line = startLine; line < (startLine+count) ; line++) |
458 | { |
459 | int srcLineStartIndex = line*columns; |
460 | int destLineStartIndex = (line-startLine)*columns; |
461 | |
462 | for (int column = 0; column < columns; column++) |
463 | { |
464 | int srcIndex = srcLineStartIndex + column; |
465 | int destIndex = destLineStartIndex + column; |
466 | |
467 | dest[destIndex] = screenLines[srcIndex/columns].value(srcIndex%columns,defaultChar); |
468 | |
469 | // invert selected text |
470 | if (selBegin != -1 && isSelected(column,line + history->getLines())) |
471 | reverseRendition(dest[destIndex]); |
472 | } |
473 | |
474 | } |
475 | } |
476 | |
477 | void Screen::getImage( Character* dest, int size, int startLine, int endLine ) const |
478 | { |
479 | Q_ASSERT( startLine >= 0 ); |
480 | Q_ASSERT( endLine >= startLine && endLine < history->getLines() + lines ); |
481 | |
482 | const int mergedLines = endLine - startLine + 1; |
483 | |
484 | Q_ASSERT( size >= mergedLines * columns ); |
485 | Q_UNUSED( size ); |
486 | |
487 | const int linesInHistoryBuffer = qBound(0,history->getLines()-startLine,mergedLines); |
488 | const int linesInScreenBuffer = mergedLines - linesInHistoryBuffer; |
489 | |
490 | // copy lines from history buffer |
491 | if (linesInHistoryBuffer > 0) |
492 | copyFromHistory(dest,startLine,linesInHistoryBuffer); |
493 | |
494 | // copy lines from screen buffer |
495 | if (linesInScreenBuffer > 0) |
496 | copyFromScreen(dest + linesInHistoryBuffer*columns, |
497 | startLine + linesInHistoryBuffer - history->getLines(), |
498 | linesInScreenBuffer); |
499 | |
500 | // invert display when in screen mode |
501 | if (getMode(MODE_Screen)) |
502 | { |
503 | for (int i = 0; i < mergedLines*columns; i++) |
504 | reverseRendition(dest[i]); // for reverse display |
505 | } |
506 | |
507 | // mark the character at the current cursor position |
508 | int cursorIndex = loc(cuX, cuY + linesInHistoryBuffer); |
509 | if(getMode(MODE_Cursor) && cursorIndex < columns*mergedLines) |
510 | dest[cursorIndex].rendition |= RE_CURSOR; |
511 | } |
512 | |
513 | QVector<LineProperty> Screen::getLineProperties( int startLine , int endLine ) const |
514 | { |
515 | Q_ASSERT( startLine >= 0 ); |
516 | Q_ASSERT( endLine >= startLine && endLine < history->getLines() + lines ); |
517 | |
518 | const int mergedLines = endLine-startLine+1; |
519 | const int linesInHistory = qBound(0,history->getLines()-startLine,mergedLines); |
520 | const int linesInScreen = mergedLines - linesInHistory; |
521 | |
522 | QVector<LineProperty> result(mergedLines); |
523 | int index = 0; |
524 | |
525 | // copy properties for lines in history |
526 | for (int line = startLine; line < startLine + linesInHistory; line++) |
527 | { |
528 | //TODO Support for line properties other than wrapped lines |
529 | if (history->isWrappedLine(line)) |
530 | { |
531 | result[index] = (LineProperty)(result[index] | LINE_WRAPPED); |
532 | } |
533 | index++; |
534 | } |
535 | |
536 | // copy properties for lines in screen buffer |
537 | const int firstScreenLine = startLine + linesInHistory - history->getLines(); |
538 | for (int line = firstScreenLine; line < firstScreenLine+linesInScreen; line++) |
539 | { |
540 | result[index]=lineProperties[line]; |
541 | index++; |
542 | } |
543 | |
544 | return result; |
545 | } |
546 | |
547 | void Screen::reset(bool clearScreen) |
548 | { |
549 | setMode(MODE_Wrap ); saveMode(MODE_Wrap ); // wrap at end of margin |
550 | resetMode(MODE_Origin); saveMode(MODE_Origin); // position refere to [1,1] |
551 | resetMode(MODE_Insert); saveMode(MODE_Insert); // overstroke |
552 | setMode(MODE_Cursor); // cursor visible |
553 | resetMode(MODE_Screen); // screen not inverse |
554 | resetMode(MODE_NewLine); |
555 | |
556 | _topMargin=0; |
557 | _bottomMargin=lines-1; |
558 | |
559 | setDefaultRendition(); |
560 | saveCursor(); |
561 | |
562 | if ( clearScreen ) |
563 | clear(); |
564 | } |
565 | |
566 | void Screen::clear() |
567 | { |
568 | clearEntireScreen(); |
569 | home(); |
570 | } |
571 | |
572 | void Screen::backspace() |
573 | { |
574 | cuX = qMin(columns-1,cuX); // nowrap! |
575 | cuX = qMax(0,cuX-1); |
576 | |
577 | if (screenLines[cuY].size() < cuX+1) |
578 | screenLines[cuY].resize(cuX+1); |
579 | |
580 | if (BS_CLEARS) |
581 | screenLines[cuY][cuX].character = ' '; |
582 | } |
583 | |
584 | void Screen::tab(int n) |
585 | { |
586 | // note that TAB is a format effector (does not write ' '); |
587 | if (n == 0) n = 1; |
588 | while((n > 0) && (cuX < columns-1)) |
589 | { |
590 | cursorRight(1); |
591 | while((cuX < columns-1) && !tabStops[cuX]) |
592 | cursorRight(1); |
593 | n--; |
594 | } |
595 | } |
596 | |
597 | void Screen::backtab(int n) |
598 | { |
599 | // note that TAB is a format effector (does not write ' '); |
600 | if (n == 0) n = 1; |
601 | while((n > 0) && (cuX > 0)) |
602 | { |
603 | cursorLeft(1); while((cuX > 0) && !tabStops[cuX]) cursorLeft(1); |
604 | n--; |
605 | } |
606 | } |
607 | |
608 | void Screen::clearTabStops() |
609 | { |
610 | for (int i = 0; i < columns; i++) tabStops[i] = false; |
611 | } |
612 | |
613 | void Screen::changeTabStop(bool set) |
614 | { |
615 | if (cuX >= columns) return; |
616 | tabStops[cuX] = set; |
617 | } |
618 | |
619 | void Screen::initTabStops() |
620 | { |
621 | tabStops.resize(columns); |
622 | |
623 | // Arrg! The 1st tabstop has to be one longer than the other. |
624 | // i.e. the kids start counting from 0 instead of 1. |
625 | // Other programs might behave correctly. Be aware. |
626 | for (int i = 0; i < columns; i++) |
627 | tabStops[i] = (i%8 == 0 && i != 0); |
628 | } |
629 | |
630 | void Screen::newLine() |
631 | { |
632 | if (getMode(MODE_NewLine)) |
633 | toStartOfLine(); |
634 | index(); |
635 | } |
636 | |
637 | void Screen::checkSelection(int from, int to) |
638 | { |
639 | if (selBegin == -1) |
640 | return; |
641 | int scr_TL = loc(0, history->getLines()); |
642 | //Clear entire selection if it overlaps region [from, to] |
643 | if ( (selBottomRight >= (from+scr_TL)) && (selTopLeft <= (to+scr_TL)) ) |
644 | clearSelection(); |
645 | } |
646 | |
647 | void Screen::displayCharacter(wchar_t c) |
648 | { |
649 | // Note that VT100 does wrapping BEFORE putting the character. |
650 | // This has impact on the assumption of valid cursor positions. |
651 | // We indicate the fact that a newline has to be triggered by |
652 | // putting the cursor one right to the last column of the screen. |
653 | |
654 | int w = konsole_wcwidth(c); |
655 | if (w <= 0) |
656 | return; |
657 | |
658 | if (cuX+w > columns) { |
659 | if (getMode(MODE_Wrap)) { |
660 | lineProperties[cuY] = (LineProperty)(lineProperties[cuY] | LINE_WRAPPED); |
661 | nextLine(); |
662 | } |
663 | else |
664 | cuX = columns-w; |
665 | } |
666 | |
667 | // ensure current line vector has enough elements |
668 | int size = screenLines[cuY].size(); |
669 | if (size < cuX+w) |
670 | { |
671 | screenLines[cuY].resize(cuX+w); |
672 | } |
673 | |
674 | if (getMode(MODE_Insert)) insertChars(w); |
675 | |
676 | lastPos = loc(cuX,cuY); |
677 | |
678 | // check if selection is still valid. |
679 | checkSelection(lastPos, lastPos); |
680 | |
681 | Character& currentChar = screenLines[cuY][cuX]; |
682 | |
683 | currentChar.character = c; |
684 | currentChar.foregroundColor = effectiveForeground; |
685 | currentChar.backgroundColor = effectiveBackground; |
686 | currentChar.rendition = effectiveRendition; |
687 | |
688 | lastDrawnChar = c; |
689 | |
690 | int i = 0; |
691 | int newCursorX = cuX + w--; |
692 | while(w) |
693 | { |
694 | i++; |
695 | |
696 | if ( screenLines[cuY].size() < cuX + i + 1 ) |
697 | screenLines[cuY].resize(cuX+i+1); |
698 | |
699 | Character& ch = screenLines[cuY][cuX + i]; |
700 | ch.character = 0; |
701 | ch.foregroundColor = effectiveForeground; |
702 | ch.backgroundColor = effectiveBackground; |
703 | ch.rendition = effectiveRendition; |
704 | |
705 | w--; |
706 | } |
707 | cuX = newCursorX; |
708 | } |
709 | |
710 | void Screen::compose(const QString& /*compose*/) |
711 | { |
712 | Q_ASSERT( 0 /*Not implemented yet*/ ); |
713 | |
714 | /* if (lastPos == -1) |
715 | return; |
716 | |
717 | QChar c(image[lastPos].character); |
718 | compose.prepend(c); |
719 | //compose.compose(); ### FIXME! |
720 | image[lastPos].character = compose[0].unicode();*/ |
721 | } |
722 | |
723 | int Screen::scrolledLines() const |
724 | { |
725 | return _scrolledLines; |
726 | } |
727 | int Screen::droppedLines() const |
728 | { |
729 | return _droppedLines; |
730 | } |
731 | void Screen::resetDroppedLines() |
732 | { |
733 | _droppedLines = 0; |
734 | } |
735 | void Screen::resetScrolledLines() |
736 | { |
737 | _scrolledLines = 0; |
738 | } |
739 | |
740 | void Screen::scrollUp(int n) |
741 | { |
742 | if (n == 0) n = 1; // Default |
743 | if (_topMargin == 0) addHistLine(); // history.history |
744 | scrollUp(_topMargin, n); |
745 | } |
746 | |
747 | QRect Screen::lastScrolledRegion() const |
748 | { |
749 | return _lastScrolledRegion; |
750 | } |
751 | |
752 | void Screen::scrollUp(int from, int n) |
753 | { |
754 | if (n <= 0) |
755 | return; |
756 | if (from > _bottomMargin) |
757 | return; |
758 | if (from + n > _bottomMargin) |
759 | n = _bottomMargin + 1 - from; |
760 | |
761 | _scrolledLines -= n; |
762 | _lastScrolledRegion = QRect(0,_topMargin,columns-1,(_bottomMargin-_topMargin)); |
763 | |
764 | //FIXME: make sure `topMargin', `bottomMargin', `from', `n' is in bounds. |
765 | moveImage(loc(0,from),loc(0,from+n),loc(columns,_bottomMargin)); |
766 | clearImage(loc(0,_bottomMargin-n+1),loc(columns-1,_bottomMargin),' '); |
767 | } |
768 | |
769 | void Screen::scrollDown(int n) |
770 | { |
771 | if (n == 0) n = 1; // Default |
772 | scrollDown(_topMargin, n); |
773 | } |
774 | |
775 | void Screen::scrollDown(int from, int n) |
776 | { |
777 | _scrolledLines += n; |
778 | |
779 | //FIXME: make sure `topMargin', `bottomMargin', `from', `n' is in bounds. |
780 | if (n <= 0) |
781 | return; |
782 | if (from > _bottomMargin) |
783 | return; |
784 | if (from + n > _bottomMargin) |
785 | n = _bottomMargin - from; |
786 | moveImage(loc(0,from+n),loc(0,from),loc(columns-1,_bottomMargin-n)); |
787 | clearImage(loc(0,from),loc(columns-1,from+n-1),' '); |
788 | } |
789 | |
790 | void Screen::setCursorYX(int y, int x) |
791 | { |
792 | setCursorY(y); setCursorX(x); |
793 | } |
794 | |
795 | void Screen::setCursorX(int x) |
796 | { |
797 | if (x == 0) x = 1; // Default |
798 | x -= 1; // Adjust |
799 | cuX = qMax(0,qMin(columns-1, x)); |
800 | } |
801 | |
802 | void Screen::setCursorY(int y) |
803 | { |
804 | if (y == 0) y = 1; // Default |
805 | y -= 1; // Adjust |
806 | cuY = qMax(0,qMin(lines -1, y + (getMode(MODE_Origin) ? _topMargin : 0) )); |
807 | } |
808 | |
809 | void Screen::home() |
810 | { |
811 | cuX = 0; |
812 | cuY = 0; |
813 | } |
814 | |
815 | void Screen::toStartOfLine() |
816 | { |
817 | cuX = 0; |
818 | } |
819 | |
820 | int Screen::getCursorX() const |
821 | { |
822 | return cuX; |
823 | } |
824 | |
825 | int Screen::getCursorY() const |
826 | { |
827 | return cuY; |
828 | } |
829 | |
830 | void Screen::clearImage(int loca, int loce, char c) |
831 | { |
832 | int scr_TL=loc(0,history->getLines()); |
833 | //FIXME: check positions |
834 | |
835 | //Clear entire selection if it overlaps region to be moved... |
836 | if ( (selBottomRight > (loca+scr_TL) )&&(selTopLeft < (loce+scr_TL)) ) |
837 | { |
838 | clearSelection(); |
839 | } |
840 | |
841 | int topLine = loca/columns; |
842 | int bottomLine = loce/columns; |
843 | |
844 | Character clearCh(c,currentForeground,currentBackground,DEFAULT_RENDITION); |
845 | |
846 | //if the character being used to clear the area is the same as the |
847 | //default character, the affected lines can simply be shrunk. |
848 | bool isDefaultCh = (clearCh == Character()); |
849 | |
850 | for (int y=topLine;y<=bottomLine;y++) |
851 | { |
852 | lineProperties[y] = 0; |
853 | |
854 | int endCol = ( y == bottomLine) ? loce%columns : columns-1; |
855 | int startCol = ( y == topLine ) ? loca%columns : 0; |
856 | |
857 | QVector<Character>& line = screenLines[y]; |
858 | |
859 | if ( isDefaultCh && endCol == columns-1 ) |
860 | { |
861 | line.resize(startCol); |
862 | } |
863 | else |
864 | { |
865 | if (line.size() < endCol + 1) |
866 | line.resize(endCol+1); |
867 | |
868 | Character* data = line.data(); |
869 | for (int i=startCol;i<=endCol;i++) |
870 | data[i]=clearCh; |
871 | } |
872 | } |
873 | } |
874 | |
875 | void Screen::moveImage(int dest, int sourceBegin, int sourceEnd) |
876 | { |
877 | Q_ASSERT( sourceBegin <= sourceEnd ); |
878 | |
879 | int lines=(sourceEnd-sourceBegin)/columns; |
880 | |
881 | //move screen image and line properties: |
882 | //the source and destination areas of the image may overlap, |
883 | //so it matters that we do the copy in the right order - |
884 | //forwards if dest < sourceBegin or backwards otherwise. |
885 | //(search the web for 'memmove implementation' for details) |
886 | if (dest < sourceBegin) |
887 | { |
888 | for (int i=0;i<=lines;i++) |
889 | { |
890 | screenLines[ (dest/columns)+i ] = screenLines[ (sourceBegin/columns)+i ]; |
891 | lineProperties[(dest/columns)+i]=lineProperties[(sourceBegin/columns)+i]; |
892 | } |
893 | } |
894 | else |
895 | { |
896 | for (int i=lines;i>=0;i--) |
897 | { |
898 | screenLines[ (dest/columns)+i ] = screenLines[ (sourceBegin/columns)+i ]; |
899 | lineProperties[(dest/columns)+i]=lineProperties[(sourceBegin/columns)+i]; |
900 | } |
901 | } |
902 | |
903 | if (lastPos != -1) |
904 | { |
905 | int diff = dest - sourceBegin; // Scroll by this amount |
906 | lastPos += diff; |
907 | if ((lastPos < 0) || (lastPos >= (lines*columns))) |
908 | lastPos = -1; |
909 | } |
910 | |
911 | // Adjust selection to follow scroll. |
912 | if (selBegin != -1) |
913 | { |
914 | bool beginIsTL = (selBegin == selTopLeft); |
915 | int diff = dest - sourceBegin; // Scroll by this amount |
916 | int scr_TL=loc(0,history->getLines()); |
917 | int srca = sourceBegin+scr_TL; // Translate index from screen to global |
918 | int srce = sourceEnd+scr_TL; // Translate index from screen to global |
919 | int desta = srca+diff; |
920 | int deste = srce+diff; |
921 | |
922 | if ((selTopLeft >= srca) && (selTopLeft <= srce)) |
923 | selTopLeft += diff; |
924 | else if ((selTopLeft >= desta) && (selTopLeft <= deste)) |
925 | selBottomRight = -1; // Clear selection (see below) |
926 | |
927 | if ((selBottomRight >= srca) && (selBottomRight <= srce)) |
928 | selBottomRight += diff; |
929 | else if ((selBottomRight >= desta) && (selBottomRight <= deste)) |
930 | selBottomRight = -1; // Clear selection (see below) |
931 | |
932 | if (selBottomRight < 0) |
933 | { |
934 | clearSelection(); |
935 | } |
936 | else |
937 | { |
938 | if (selTopLeft < 0) |
939 | selTopLeft = 0; |
940 | } |
941 | |
942 | if (beginIsTL) |
943 | selBegin = selTopLeft; |
944 | else |
945 | selBegin = selBottomRight; |
946 | } |
947 | } |
948 | |
949 | void Screen::clearToEndOfScreen() |
950 | { |
951 | clearImage(loc(cuX,cuY),loc(columns-1,lines-1),' '); |
952 | } |
953 | |
954 | void Screen::clearToBeginOfScreen() |
955 | { |
956 | clearImage(loc(0,0),loc(cuX,cuY),' '); |
957 | } |
958 | |
959 | void Screen::clearEntireScreen() |
960 | { |
961 | // Add entire screen to history |
962 | for (int i = 0; i < (lines-1); i++) |
963 | { |
964 | addHistLine(); scrollUp(0,1); |
965 | } |
966 | |
967 | clearImage(loc(0,0),loc(columns-1,lines-1),' '); |
968 | } |
969 | |
970 | /*! fill screen with 'E' |
971 | This is to aid screen alignment |
972 | */ |
973 | |
974 | void Screen::helpAlign() |
975 | { |
976 | clearImage(loc(0,0),loc(columns-1,lines-1),'E'); |
977 | } |
978 | |
979 | void Screen::clearToEndOfLine() |
980 | { |
981 | clearImage(loc(cuX,cuY),loc(columns-1,cuY),' '); |
982 | } |
983 | |
984 | void Screen::clearToBeginOfLine() |
985 | { |
986 | clearImage(loc(0,cuY),loc(cuX,cuY),' '); |
987 | } |
988 | |
989 | void Screen::clearEntireLine() |
990 | { |
991 | clearImage(loc(0,cuY),loc(columns-1,cuY),' '); |
992 | } |
993 | |
994 | void Screen::setRendition(int re) |
995 | { |
996 | currentRendition |= re; |
997 | updateEffectiveRendition(); |
998 | } |
999 | |
1000 | void Screen::resetRendition(int re) |
1001 | { |
1002 | currentRendition &= ~re; |
1003 | updateEffectiveRendition(); |
1004 | } |
1005 | |
1006 | void Screen::setDefaultRendition() |
1007 | { |
1008 | setForeColor(COLOR_SPACE_DEFAULT,DEFAULT_FORE_COLOR); |
1009 | setBackColor(COLOR_SPACE_DEFAULT,DEFAULT_BACK_COLOR); |
1010 | currentRendition = DEFAULT_RENDITION; |
1011 | updateEffectiveRendition(); |
1012 | } |
1013 | |
1014 | void Screen::setForeColor(int space, int color) |
1015 | { |
1016 | currentForeground = CharacterColor(space, color); |
1017 | |
1018 | if ( currentForeground.isValid() ) |
1019 | updateEffectiveRendition(); |
1020 | else |
1021 | setForeColor(COLOR_SPACE_DEFAULT,DEFAULT_FORE_COLOR); |
1022 | } |
1023 | |
1024 | void Screen::setBackColor(int space, int color) |
1025 | { |
1026 | currentBackground = CharacterColor(space, color); |
1027 | |
1028 | if ( currentBackground.isValid() ) |
1029 | updateEffectiveRendition(); |
1030 | else |
1031 | setBackColor(COLOR_SPACE_DEFAULT,DEFAULT_BACK_COLOR); |
1032 | } |
1033 | |
1034 | void Screen::clearSelection() |
1035 | { |
1036 | selBottomRight = -1; |
1037 | selTopLeft = -1; |
1038 | selBegin = -1; |
1039 | } |
1040 | |
1041 | void Screen::getSelectionStart(int& column , int& line) const |
1042 | { |
1043 | if ( selTopLeft != -1 ) |
1044 | { |
1045 | column = selTopLeft % columns; |
1046 | line = selTopLeft / columns; |
1047 | } |
1048 | else |
1049 | { |
1050 | column = cuX + getHistLines(); |
1051 | line = cuY + getHistLines(); |
1052 | } |
1053 | } |
1054 | void Screen::getSelectionEnd(int& column , int& line) const |
1055 | { |
1056 | if ( selBottomRight != -1 ) |
1057 | { |
1058 | column = selBottomRight % columns; |
1059 | line = selBottomRight / columns; |
1060 | } |
1061 | else |
1062 | { |
1063 | column = cuX + getHistLines(); |
1064 | line = cuY + getHistLines(); |
1065 | } |
1066 | } |
1067 | void Screen::setSelectionStart(const int x, const int y, const bool mode) |
1068 | { |
1069 | selBegin = loc(x,y); |
1070 | /* FIXME, HACK to correct for x too far to the right... */ |
1071 | if (x == columns) selBegin--; |
1072 | |
1073 | selBottomRight = selBegin; |
1074 | selTopLeft = selBegin; |
1075 | blockSelectionMode = mode; |
1076 | } |
1077 | |
1078 | void Screen::setSelectionEnd( const int x, const int y) |
1079 | { |
1080 | if (selBegin == -1) |
1081 | return; |
1082 | |
1083 | int endPos = loc(x,y); |
1084 | |
1085 | if (endPos < selBegin) |
1086 | { |
1087 | selTopLeft = endPos; |
1088 | selBottomRight = selBegin; |
1089 | } |
1090 | else |
1091 | { |
1092 | /* FIXME, HACK to correct for x too far to the right... */ |
1093 | if (x == columns) |
1094 | endPos--; |
1095 | |
1096 | selTopLeft = selBegin; |
1097 | selBottomRight = endPos; |
1098 | } |
1099 | |
1100 | // Normalize the selection in column mode |
1101 | if (blockSelectionMode) |
1102 | { |
1103 | int topRow = selTopLeft / columns; |
1104 | int topColumn = selTopLeft % columns; |
1105 | int bottomRow = selBottomRight / columns; |
1106 | int bottomColumn = selBottomRight % columns; |
1107 | |
1108 | selTopLeft = loc(qMin(topColumn,bottomColumn),topRow); |
1109 | selBottomRight = loc(qMax(topColumn,bottomColumn),bottomRow); |
1110 | } |
1111 | } |
1112 | |
1113 | bool Screen::isSelected( const int x,const int y) const |
1114 | { |
1115 | bool columnInSelection = true; |
1116 | if (blockSelectionMode) |
1117 | { |
1118 | columnInSelection = x >= (selTopLeft % columns) && |
1119 | x <= (selBottomRight % columns); |
1120 | } |
1121 | |
1122 | int pos = loc(x,y); |
1123 | return pos >= selTopLeft && pos <= selBottomRight && columnInSelection; |
1124 | } |
1125 | |
1126 | QString Screen::selectedText(bool preserveLineBreaks) const |
1127 | { |
1128 | QString result; |
1129 | QTextStream stream(&result, QIODevice::ReadWrite); |
1130 | |
1131 | PlainTextDecoder decoder; |
1132 | decoder.begin(&stream); |
1133 | writeSelectionToStream(&decoder , preserveLineBreaks); |
1134 | decoder.end(); |
1135 | |
1136 | return result; |
1137 | } |
1138 | |
1139 | bool Screen::isSelectionValid() const |
1140 | { |
1141 | return selTopLeft >= 0 && selBottomRight >= 0; |
1142 | } |
1143 | |
1144 | void Screen::writeSelectionToStream(TerminalCharacterDecoder* decoder , |
1145 | bool preserveLineBreaks) const |
1146 | { |
1147 | if (!isSelectionValid()) |
1148 | return; |
1149 | writeToStream(decoder,selTopLeft,selBottomRight,preserveLineBreaks); |
1150 | } |
1151 | |
1152 | void Screen::writeToStream(TerminalCharacterDecoder* decoder, |
1153 | int startIndex, int endIndex, |
1154 | bool preserveLineBreaks) const |
1155 | { |
1156 | int top = startIndex / columns; |
1157 | int left = startIndex % columns; |
1158 | |
1159 | int bottom = endIndex / columns; |
1160 | int right = endIndex % columns; |
1161 | |
1162 | Q_ASSERT( top >= 0 && left >= 0 && bottom >= 0 && right >= 0 ); |
1163 | |
1164 | for (int y=top;y<=bottom;y++) |
1165 | { |
1166 | int start = 0; |
1167 | if ( y == top || blockSelectionMode ) start = left; |
1168 | |
1169 | int count = -1; |
1170 | if ( y == bottom || blockSelectionMode ) count = right - start + 1; |
1171 | |
1172 | const bool appendNewLine = ( y != bottom ); |
1173 | int copied = copyLineToStream( y, |
1174 | start, |
1175 | count, |
1176 | decoder, |
1177 | appendNewLine, |
1178 | preserveLineBreaks ); |
1179 | |
1180 | // if the selection goes beyond the end of the last line then |
1181 | // append a new line character. |
1182 | // |
1183 | // this makes it possible to 'select' a trailing new line character after |
1184 | // the text on a line. |
1185 | if ( y == bottom && |
1186 | copied < count ) |
1187 | { |
1188 | Character newLineChar('\n'); |
1189 | decoder->decodeLine(&newLineChar,1,0); |
1190 | } |
1191 | } |
1192 | } |
1193 | |
1194 | int Screen::copyLineToStream(int line , |
1195 | int start, |
1196 | int count, |
1197 | TerminalCharacterDecoder* decoder, |
1198 | bool appendNewLine, |
1199 | bool preserveLineBreaks) const |
1200 | { |
1201 | //buffer to hold characters for decoding |
1202 | //the buffer is static to avoid initialising every |
1203 | //element on each call to copyLineToStream |
1204 | //(which is unnecessary since all elements will be overwritten anyway) |
1205 | static const int MAX_CHARS = 1024; |
1206 | static Character characterBuffer[MAX_CHARS]; |
1207 | |
1208 | Q_ASSERT( count < MAX_CHARS ); |
1209 | |
1210 | LineProperty currentLineProperties = 0; |
1211 | |
1212 | //determine if the line is in the history buffer or the screen image |
1213 | if (line < history->getLines()) |
1214 | { |
1215 | const int lineLength = history->getLineLen(line); |
1216 | |
1217 | // ensure that start position is before end of line |
1218 | start = qMin(start,qMax(0,lineLength-1)); |
1219 | |
1220 | // retrieve line from history buffer. It is assumed |
1221 | // that the history buffer does not store trailing white space |
1222 | // at the end of the line, so it does not need to be trimmed here |
1223 | if (count == -1) |
1224 | { |
1225 | count = lineLength-start; |
1226 | } |
1227 | else |
1228 | { |
1229 | count = qMin(start+count,lineLength)-start; |
1230 | } |
1231 | |
1232 | // safety checks |
1233 | Q_ASSERT( start >= 0 ); |
1234 | Q_ASSERT( count >= 0 ); |
1235 | Q_ASSERT( (start+count) <= history->getLineLen(line) ); |
1236 | |
1237 | history->getCells(line,start,count,characterBuffer); |
1238 | |
1239 | if ( history->isWrappedLine(line) ) |
1240 | currentLineProperties |= LINE_WRAPPED; |
1241 | } |
1242 | else |
1243 | { |
1244 | if ( count == -1 ) |
1245 | count = columns - start; |
1246 | |
1247 | Q_ASSERT( count >= 0 ); |
1248 | |
1249 | const int screenLine = line-history->getLines(); |
1250 | |
1251 | Character* data = screenLines[screenLine].data(); |
1252 | int length = screenLines[screenLine].count(); |
1253 | |
1254 | //retrieve line from screen image |
1255 | for (int i=start;i < qMin(start+count,length);i++) |
1256 | { |
1257 | characterBuffer[i-start] = data[i]; |
1258 | } |
1259 | |
1260 | // count cannot be any greater than length |
1261 | count = qBound(0,count,length-start); |
1262 | |
1263 | Q_ASSERT( screenLine < lineProperties.count() ); |
1264 | currentLineProperties |= lineProperties[screenLine]; |
1265 | } |
1266 | |
1267 | // add new line character at end |
1268 | const bool omitLineBreak = (currentLineProperties & LINE_WRAPPED) || |
1269 | !preserveLineBreaks; |
1270 | |
1271 | if ( !omitLineBreak && appendNewLine && (count+1 < MAX_CHARS) ) |
1272 | { |
1273 | characterBuffer[count] = '\n'; |
1274 | count++; |
1275 | } |
1276 | |
1277 | //decode line and write to text stream |
1278 | decoder->decodeLine( (Character*) characterBuffer , |
1279 | count, currentLineProperties ); |
1280 | |
1281 | return count; |
1282 | } |
1283 | |
1284 | void Screen::writeLinesToStream(TerminalCharacterDecoder* decoder, int fromLine, int toLine) const |
1285 | { |
1286 | writeToStream(decoder,loc(0,fromLine),loc(columns-1,toLine)); |
1287 | } |
1288 | |
1289 | void Screen::addHistLine() |
1290 | { |
1291 | // add line to history buffer |
1292 | // we have to take care about scrolling, too... |
1293 | |
1294 | if (hasScroll()) |
1295 | { |
1296 | int oldHistLines = history->getLines(); |
1297 | |
1298 | history->addCellsVector(screenLines[0]); |
1299 | history->addLine( lineProperties[0] & LINE_WRAPPED ); |
1300 | |
1301 | int newHistLines = history->getLines(); |
1302 | |
1303 | bool beginIsTL = (selBegin == selTopLeft); |
1304 | |
1305 | // If the history is full, increment the count |
1306 | // of dropped lines |
1307 | if ( newHistLines == oldHistLines ) |
1308 | _droppedLines++; |
1309 | |
1310 | // Adjust selection for the new point of reference |
1311 | if (newHistLines > oldHistLines) |
1312 | { |
1313 | if (selBegin != -1) |
1314 | { |
1315 | selTopLeft += columns; |
1316 | selBottomRight += columns; |
1317 | } |
1318 | } |
1319 | |
1320 | if (selBegin != -1) |
1321 | { |
1322 | // Scroll selection in history up |
1323 | int top_BR = loc(0, 1+newHistLines); |
1324 | |
1325 | if (selTopLeft < top_BR) |
1326 | selTopLeft -= columns; |
1327 | |
1328 | if (selBottomRight < top_BR) |
1329 | selBottomRight -= columns; |
1330 | |
1331 | if (selBottomRight < 0) |
1332 | clearSelection(); |
1333 | else |
1334 | { |
1335 | if (selTopLeft < 0) |
1336 | selTopLeft = 0; |
1337 | } |
1338 | |
1339 | if (beginIsTL) |
1340 | selBegin = selTopLeft; |
1341 | else |
1342 | selBegin = selBottomRight; |
1343 | } |
1344 | } |
1345 | |
1346 | } |
1347 | |
1348 | int Screen::getHistLines() const |
1349 | { |
1350 | return history->getLines(); |
1351 | } |
1352 | |
1353 | void Screen::setScroll(const HistoryType& t , bool copyPreviousScroll) |
1354 | { |
1355 | clearSelection(); |
1356 | |
1357 | if ( copyPreviousScroll ) |
1358 | history = t.scroll(history); |
1359 | else |
1360 | { |
1361 | HistoryScroll* oldScroll = history; |
1362 | history = t.scroll(0); |
1363 | delete oldScroll; |
1364 | } |
1365 | } |
1366 | |
1367 | bool Screen::hasScroll() const |
1368 | { |
1369 | return history->hasScroll(); |
1370 | } |
1371 | |
1372 | const HistoryType& Screen::getScroll() const |
1373 | { |
1374 | return history->getType(); |
1375 | } |
1376 | |
1377 | void Screen::setLineProperty(LineProperty property , bool enable) |
1378 | { |
1379 | if ( enable ) |
1380 | lineProperties[cuY] = (LineProperty)(lineProperties[cuY] | property); |
1381 | else |
1382 | lineProperties[cuY] = (LineProperty)(lineProperties[cuY] & ~property); |
1383 | } |
1384 | void Screen::fillWithDefaultChar(Character* dest, int count) |
1385 | { |
1386 | for (int i=0;i<count;i++) |
1387 | dest[i] = defaultChar; |
1388 | } |
1389 | |