comparison libgui/qterminal/libqterminal/unix/TerminalView.cpp @ 15681:018c46ef8a0c

maint: move everything under libgui/qterminal
author Jordi Gutiérrez Hermoso <jordigh@octave.org>
date Mon, 26 Nov 2012 12:07:51 -0500
parents libqterminal/unix/TerminalView.cpp@a1bcffac7fa8
children 8e180eac78d0
comparison
equal deleted inserted replaced
15680:6b1369106141 15681:018c46ef8a0c
1 /*
2 This file is part of Konsole, a terminal emulator for KDE.
3
4 Copyright (C) 2006-7 by Robert Knight <robertknight@gmail.com>
5 Copyright (C) 1997,1998 by Lars Doelle <lars.doelle@on-line.de>
6
7 Rewritten for QT4 by e_k <e_k at users.sourceforge.net>, Copyright (C)2008
8 Copyright (C) 2012 Jacob Dawid <jacob.dawid@googlemail.com>
9
10 This program is free software; you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation; either version 2 of the License, or
13 (at your option) any later version.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
23 02110-1301 USA.
24 */
25
26 // Own
27 #include "unix/TerminalView.h"
28
29 // Qt
30 #include <QtGui/QApplication>
31 #include <QtGui/QBoxLayout>
32 #include <QtGui/QClipboard>
33 #include <QtGui/QKeyEvent>
34 #include <QtCore/QEvent>
35 #include <QtCore/QTime>
36 #include <QtCore/QFile>
37 #include <QtGui/QGridLayout>
38 #include <QtGui/QLabel>
39 #include <QtGui/QLayout>
40 #include <QtGui/QPainter>
41 #include <QtGui/QPixmap>
42 #include <QtGui/QScrollBar>
43 #include <QtGui/QStyle>
44 #include <QtCore>
45 #include <QtGui>
46
47 #include "unix/Filter.h"
48 #include "unix/konsole_wcwidth.h"
49 #include "unix/ScreenWindow.h"
50 #include "unix/TerminalCharacterDecoder.h"
51
52 #ifndef loc
53 #define loc(X,Y) ((Y)*_columns+(X))
54 #endif
55
56 #define yMouseScroll 1
57
58 #define REPCHAR "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
59 "abcdefgjijklmnopqrstuvwxyz" \
60 "0123456789./+@"
61
62 // scroll increment used when dragging selection at top/bottom of window.
63
64 // static
65 bool TerminalView::_antialiasText = true;
66
67 /* ------------------------------------------------------------------------- */
68 /* */
69 /* Colors */
70 /* */
71 /* ------------------------------------------------------------------------- */
72
73 /* Note that we use ANSI color order (bgr), while IBMPC color order is (rgb)
74
75 Code 0 1 2 3 4 5 6 7
76 ----------- ------- ------- ------- ------- ------- ------- ------- -------
77 ANSI (bgr) Black Red Green Yellow Blue Magenta Cyan White
78 IBMPC (rgb) Black Blue Green Cyan Red Magenta Yellow White
79 */
80
81 ScreenWindow* TerminalView::screenWindow() const
82 {
83 return _screenWindow;
84 }
85 void TerminalView::setScreenWindow(ScreenWindow* window)
86 {
87 // disconnect existing screen window if any
88 if ( _screenWindow )
89 {
90 disconnect( _screenWindow , 0 , this , 0 );
91 }
92
93 _screenWindow = window;
94
95 if ( window )
96 {
97 //#warning "The order here is not specified - does it matter whether updateImage or updateLineProperties comes first?"
98 connect( _screenWindow , SIGNAL(outputChanged()) , this , SLOT(updateLineProperties()) );
99 connect( _screenWindow , SIGNAL(outputChanged()) , this , SLOT(updateImage()) );
100 window->setWindowLines(_lines);
101 }
102 }
103
104 const ColorEntry* TerminalView::colorTable() const
105 {
106 return _colorTable;
107 }
108
109 void TerminalView::setColorTable(const ColorEntry table[])
110 {
111 for (int i = 0; i < TABLE_COLORS; i++)
112 _colorTable[i] = table[i];
113
114 QPalette p = palette();
115 p.setColor( backgroundRole(), _colorTable[DEFAULT_BACK_COLOR].color );
116 setPalette( p );
117
118 // Avoid propagating the palette change to the scroll bar
119 _scrollBar->setPalette( QApplication::palette() );
120
121 update();
122 }
123
124 /* ------------------------------------------------------------------------- */
125 /* */
126 /* Font */
127 /* */
128 /* ------------------------------------------------------------------------- */
129
130 /*
131 The VT100 has 32 special graphical characters. The usual vt100 extended
132 xterm fonts have these at 0x00..0x1f.
133
134 QT's iso mapping leaves 0x00..0x7f without any changes. But the graphicals
135 come in here as proper unicode characters.
136
137 We treat non-iso10646 fonts as VT100 extended and do the required mapping
138 from unicode to 0x00..0x1f. The remaining translation is then left to the
139 QCodec.
140 */
141
142 static inline bool isLineChar(quint16 c) { return ((c & 0xFF80) == 0x2500);}
143 static inline bool isLineCharString(const QString& string)
144 {
145 return (string.length() > 0) && (isLineChar(string.at(0).unicode()));
146 }
147
148
149 // assert for i in [0..31] : vt100extended(vt100_graphics[i]) == i.
150
151 unsigned short vt100_graphics[32] =
152 { // 0/8 1/9 2/10 3/11 4/12 5/13 6/14 7/15
153 0x0020, 0x25C6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0,
154 0x00b1, 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c,
155 0xF800, 0xF801, 0x2500, 0xF803, 0xF804, 0x251c, 0x2524, 0x2534,
156 0x252c, 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00b7
157 };
158
159 void TerminalView::fontChange(const QFont&)
160 {
161 QFontMetrics fm(font());
162 _fontHeight = fm.height() + _lineSpacing;
163
164
165 // waba TerminalDisplay 1.123:
166 // "Base character width on widest ASCII character. This prevents too wide
167 // characters in the presence of double wide (e.g. Japanese) characters."
168 // Get the width from representative normal width characters
169 _fontWidth = (double)fm.width(REPCHAR)/(double)strlen(REPCHAR);
170
171 _fixedFont = true;
172
173 int fw = fm.width(REPCHAR[0]);
174 for(unsigned int i=1; i< strlen(REPCHAR); i++)
175 {
176 if (fw != fm.width(REPCHAR[i]))
177 {
178 _fixedFont = false;
179 break;
180 }
181 }
182
183
184 if (_fontWidth < 1)
185 _fontWidth = 1;
186
187 _fontAscent = fm.ascent();
188
189 emit changedFontMetricSignal( _fontHeight, _fontWidth );
190 //parentWidget()->setFixedWidth(_fontWidth * 80 + _leftMargin);
191 propagateSize();
192 update();
193 }
194
195 void TerminalView::setVTFont(const QFont& f)
196 {
197 QFont font = f;
198
199 QFontMetrics metrics(font);
200
201 if ( metrics.height() < height() && metrics.maxWidth() < width() )
202 {
203 // hint that text should be drawn without anti-aliasing.
204 // depending on the user's font configuration, this may not be respected
205 if (!_antialiasText)
206 font.setStyleStrategy( QFont::NoAntialias );
207
208 // experimental optimization. Konsole assumes that the terminal is using a
209 // mono-spaced font, in which case kerning information should have an effect.
210 // Disabling kerning saves some computation when rendering text.
211 // font.setKerning(false);
212
213 QWidget::setFont(font);
214 fontChange(font);
215 }
216 }
217
218 void TerminalView::setFont(const QFont &)
219 {
220 // ignore font change request if not coming from konsole itself
221 }
222
223 /* ------------------------------------------------------------------------- */
224 /* */
225 /* Constructor / Destructor */
226 /* */
227 /* ------------------------------------------------------------------------- */
228
229 TerminalView::TerminalView(QWidget *parent)
230 :QWidget(parent)
231 ,_screenWindow(0)
232 ,_allowBell(true)
233 ,_gridLayout(0)
234 ,_fontHeight(1)
235 ,_fontWidth(1)
236 ,_fontAscent(1)
237 ,_lines(1)
238 ,_columns(1)
239 ,_usedLines(1)
240 ,_usedColumns(1)
241 ,_contentHeight(1)
242 ,_contentWidth(1)
243 ,_image(0)
244 ,_randomSeed(0)
245 ,_resizing(false)
246 ,_terminalSizeHint(false)
247 ,_terminalSizeStartup(true)
248 ,_actSel(0)
249 ,_wordSelectionMode(false)
250 ,_lineSelectionMode(false)
251 ,_preserveLineBreaks(false)
252 ,_columnSelectionMode(false)
253 ,_scrollbarLocation(NoScrollBar)
254 ,_wordCharacters(":@-./_~")
255 ,_bellMode(SystemBeepBell)
256 ,_blinking(false)
257 ,_cursorBlinking(false)
258 ,_hasBlinkingCursor(false)
259 ,_ctrlDrag(false)
260 ,_tripleClickMode(SelectWholeLine)
261 ,_isFixedSize(false)
262 ,_possibleTripleClick(false)
263 ,_resizeWidget(0)
264 ,_resizeTimer(0)
265 ,_outputSuspendedLabel(0)
266 ,_lineSpacing(0)
267 ,_colorsInverted(false)
268 ,_blendColor(qRgba(0,0,0,0xff))
269 ,_filterChain(new TerminalImageFilterChain())
270 ,_cursorShape(BlockCursor)
271 ,_readonly(false)
272 {
273 // terminal applications are not designed with Right-To-Left in mind,
274 // so the layout is forced to Left-To-Right
275 setLayoutDirection(Qt::LeftToRight);
276
277 // The offsets are not yet calculated.
278 // Do not calculate these too often to be more smoothly when resizing
279 // konsole in opaque mode.
280 _topMargin = DEFAULT_TOP_MARGIN;
281 _leftMargin = DEFAULT_LEFT_MARGIN;
282
283 // create scroll bar for scrolling output up and down
284 // set the scroll bar's slider to occupy the whole area of the scroll bar initially
285 _scrollBar = new QScrollBar(this);
286 setScroll(0,0);
287 _scrollBar->setCursor( Qt::ArrowCursor );
288 connect(_scrollBar, SIGNAL(valueChanged(int)), this,
289 SLOT(scrollBarPositionChanged(int)));
290
291 // setup timers for blinking cursor and text
292 _blinkTimer = new QTimer(this);
293 connect(_blinkTimer, SIGNAL(timeout()), this, SLOT(blinkEvent()));
294 _blinkCursorTimer = new QTimer(this);
295 connect(_blinkCursorTimer, SIGNAL(timeout()), this, SLOT(blinkCursorEvent()));
296
297 // QCursor::setAutoHideCursor( this, true );
298
299 setUsesMouse(true);
300 setColorTable(base_color_table);
301 setMouseTracking(true);
302
303 // Enable drag and drop
304 setAcceptDrops(true); // attempt
305 dragInfo.state = diNone;
306
307 setFocusPolicy( Qt::WheelFocus );
308
309 // enable input method support
310 setAttribute(Qt::WA_InputMethodEnabled, true);
311
312 // this is an important optimization, it tells Qt
313 // that TerminalDisplay will handle repainting its entire area.
314 setAttribute(Qt::WA_OpaquePaintEvent);
315
316 _gridLayout = new QGridLayout(this);
317 _gridLayout->setMargin(0);
318
319 setLayout( _gridLayout );
320 }
321
322 TerminalView::~TerminalView()
323 {
324 qApp->removeEventFilter( this );
325
326 delete[] _image;
327
328 delete _gridLayout;
329 delete _outputSuspendedLabel;
330 delete _filterChain;
331 }
332
333 /* ------------------------------------------------------------------------- */
334 /* */
335 /* Display Operations */
336 /* */
337 /* ------------------------------------------------------------------------- */
338
339 /**
340 A table for emulating the simple (single width) unicode drawing chars.
341 It represents the 250x - 257x glyphs. If it's zero, we can't use it.
342 if it's not, it's encoded as follows: imagine a 5x5 grid where the points are numbered
343 0 to 24 left to top, top to bottom. Each point is represented by the corresponding bit.
344
345 Then, the pixels basically have the following interpretation:
346 _|||_
347 -...-
348 -...-
349 -...-
350 _|||_
351
352 where _ = none
353 | = vertical line.
354 - = horizontal line.
355 */
356
357
358 enum LineEncode
359 {
360 TopL = (1<<1),
361 TopC = (1<<2),
362 TopR = (1<<3),
363
364 LeftT = (1<<5),
365 Int11 = (1<<6),
366 Int12 = (1<<7),
367 Int13 = (1<<8),
368 RightT = (1<<9),
369
370 LeftC = (1<<10),
371 Int21 = (1<<11),
372 Int22 = (1<<12),
373 Int23 = (1<<13),
374 RightC = (1<<14),
375
376 LeftB = (1<<15),
377 Int31 = (1<<16),
378 Int32 = (1<<17),
379 Int33 = (1<<18),
380 RightB = (1<<19),
381
382 BotL = (1<<21),
383 BotC = (1<<22),
384 BotR = (1<<23)
385 };
386
387 #include "LineFont.h"
388
389 static void drawLineChar(QPainter& paint, int x, int y, int w, int h, uchar code)
390 {
391 //Calculate cell midpoints, end points.
392 int cx = x + w/2;
393 int cy = y + h/2;
394 int ex = x + w - 1;
395 int ey = y + h - 1;
396
397 quint32 toDraw = LineChars[code];
398
399 //Top _lines:
400 if (toDraw & TopL)
401 paint.drawLine(cx-1, y, cx-1, cy-2);
402 if (toDraw & TopC)
403 paint.drawLine(cx, y, cx, cy-2);
404 if (toDraw & TopR)
405 paint.drawLine(cx+1, y, cx+1, cy-2);
406
407 //Bot _lines:
408 if (toDraw & BotL)
409 paint.drawLine(cx-1, cy+2, cx-1, ey);
410 if (toDraw & BotC)
411 paint.drawLine(cx, cy+2, cx, ey);
412 if (toDraw & BotR)
413 paint.drawLine(cx+1, cy+2, cx+1, ey);
414
415 //Left _lines:
416 if (toDraw & LeftT)
417 paint.drawLine(x, cy-1, cx-2, cy-1);
418 if (toDraw & LeftC)
419 paint.drawLine(x, cy, cx-2, cy);
420 if (toDraw & LeftB)
421 paint.drawLine(x, cy+1, cx-2, cy+1);
422
423 //Right _lines:
424 if (toDraw & RightT)
425 paint.drawLine(cx+2, cy-1, ex, cy-1);
426 if (toDraw & RightC)
427 paint.drawLine(cx+2, cy, ex, cy);
428 if (toDraw & RightB)
429 paint.drawLine(cx+2, cy+1, ex, cy+1);
430
431 //Intersection points.
432 if (toDraw & Int11)
433 paint.drawPoint(cx-1, cy-1);
434 if (toDraw & Int12)
435 paint.drawPoint(cx, cy-1);
436 if (toDraw & Int13)
437 paint.drawPoint(cx+1, cy-1);
438
439 if (toDraw & Int21)
440 paint.drawPoint(cx-1, cy);
441 if (toDraw & Int22)
442 paint.drawPoint(cx, cy);
443 if (toDraw & Int23)
444 paint.drawPoint(cx+1, cy);
445
446 if (toDraw & Int31)
447 paint.drawPoint(cx-1, cy+1);
448 if (toDraw & Int32)
449 paint.drawPoint(cx, cy+1);
450 if (toDraw & Int33)
451 paint.drawPoint(cx+1, cy+1);
452
453 }
454
455 void TerminalView::drawLineCharString( QPainter& painter, int x, int y, const QString& str,
456 const Character* attributes)
457 {
458 const QPen& currentPen = painter.pen();
459
460 if ( attributes->rendition & RE_BOLD )
461 {
462 QPen boldPen(currentPen);
463 boldPen.setWidth(3);
464 painter.setPen( boldPen );
465 }
466
467 for (int i=0 ; i < str.length(); i++)
468 {
469 uchar code = str[i].cell();
470 if (LineChars[code])
471 drawLineChar(painter, x + (_fontWidth*i), y, _fontWidth, _fontHeight, code);
472 }
473
474 painter.setPen( currentPen );
475 }
476
477 void TerminalView::setKeyboardCursorShape(KeyboardCursorShape shape)
478 {
479 _cursorShape = shape;
480 }
481 TerminalView::KeyboardCursorShape TerminalView::keyboardCursorShape() const
482 {
483 return _cursorShape;
484 }
485 void TerminalView::setKeyboardCursorColor(bool useForegroundColor, const QColor& color)
486 {
487 if (useForegroundColor)
488 _cursorColor = QColor(); // an invalid color means that
489 // the foreground color of the
490 // current character should
491 // be used
492
493 else
494 _cursorColor = color;
495 }
496 QColor TerminalView::keyboardCursorColor() const
497 {
498 return _cursorColor;
499 }
500
501 void TerminalView::drawBackground(QPainter& painter, const QRect& rect, const QColor& backgroundColor)
502 {
503 // the area of the widget showing the contents of the terminal display is drawn
504 // using the background color from the color scheme set with setColorTable()
505 //
506 // the area of the widget behind the scroll-bar is drawn using the background
507 // brush from the scroll-bar's palette, to give the effect of the scroll-bar
508 // being outside of the terminal display and visual consistency with other KDE
509 // applications.
510 //
511 QRect scrollBarArea = _scrollBar->isVisible() ?
512 rect.intersected(_scrollBar->geometry()) :
513 QRect();
514
515 QRegion contentsRegion = QRegion(rect).subtracted(scrollBarArea);
516 QRect contentsRect = contentsRegion.boundingRect();
517
518 painter.fillRect(contentsRect, backgroundColor);
519 painter.fillRect(scrollBarArea,_scrollBar->palette().background());
520 }
521
522 void TerminalView::drawCursor(QPainter& painter,
523 const QRect& rect,
524 const QColor& foregroundColor,
525 const QColor& /*backgroundColor*/,
526 bool& invertCharacterColor)
527 {
528 QRect cursorRect = rect;
529 cursorRect.setHeight(_fontHeight - _lineSpacing - 1);
530
531 if (!_cursorBlinking)
532 {
533 if ( _cursorColor.isValid() )
534 painter.setPen(_cursorColor);
535 else {
536 painter.setPen(foregroundColor);
537 }
538
539 if ( _cursorShape == BlockCursor )
540 {
541 // draw the cursor outline, adjusting the area so that
542 // it is draw entirely inside 'rect'
543 int penWidth = qMax(1,painter.pen().width());
544
545 painter.drawRect(cursorRect.adjusted(penWidth/2,
546 penWidth/2,
547 - penWidth/2 - penWidth%2,
548 - penWidth/2 - penWidth%2));
549 if ( hasFocus() )
550 {
551 painter.fillRect(cursorRect, _cursorColor.isValid() ? _cursorColor : foregroundColor);
552
553 if ( !_cursorColor.isValid() )
554 {
555 // invert the colour used to draw the text to ensure that the character at
556 // the cursor position is readable
557 invertCharacterColor = true;
558 }
559 }
560 }
561 else if ( _cursorShape == UnderlineCursor )
562 painter.drawLine(cursorRect.left(),
563 cursorRect.bottom(),
564 cursorRect.right(),
565 cursorRect.bottom());
566 else if ( _cursorShape == IBeamCursor )
567 painter.drawLine(cursorRect.left(),
568 cursorRect.top(),
569 cursorRect.left(),
570 cursorRect.bottom());
571
572 }
573 }
574
575 void TerminalView::drawCharacters(QPainter& painter,
576 const QRect& rect,
577 const QString& text,
578 const Character* style,
579 bool invertCharacterColor)
580 {
581 // don't draw text which is currently blinking
582 if ( _blinking && (style->rendition & RE_BLINK) )
583 return;
584
585 // setup bold and underline
586 bool useBold = style->rendition & RE_BOLD || style->isBold(_colorTable) || font().bold();
587 bool useUnderline = style->rendition & RE_UNDERLINE || font().underline();
588
589 QFont font = painter.font();
590 if ( font.bold() != useBold
591 || font.underline() != useUnderline )
592 {
593 font.setBold(useBold);
594 font.setUnderline(useUnderline);
595 painter.setFont(font);
596 }
597
598 const CharacterColor& textColor = ( invertCharacterColor ? style->backgroundColor : style->foregroundColor );
599 const QColor color = textColor.color(_colorTable);
600
601 QPen pen = painter.pen();
602 if ( pen.color() != color )
603 {
604 pen.setColor(color);
605 painter.setPen(color);
606 }
607 // draw text
608 if ( isLineCharString(text) ) {
609 drawLineCharString(painter,rect.x(),rect.y(),text,style);
610 }
611 else
612 {
613 // the drawText(rect,flags,string) overload is used here with null flags
614 // instead of drawText(rect,string) because the (rect,string) overload causes
615 // the application's default layout direction to be used instead of
616 // the widget-specific layout direction, which should always be
617 // Qt::LeftToRight for this widget
618 painter.drawText(rect,0,text);
619 }
620 }
621
622 void TerminalView::drawTextFragment(QPainter& painter ,
623 const QRect& rect,
624 const QString& text,
625 const Character* style)
626 {
627 painter.save();
628
629 // setup painter
630 const QColor foregroundColor = style->foregroundColor.color(_colorTable);
631 const QColor backgroundColor = style->backgroundColor.color(_colorTable);
632
633 // draw background if different from the display's background color
634 if ( backgroundColor != palette().background().color() )
635 drawBackground(painter,rect,backgroundColor);
636
637 // draw cursor shape if the current character is the cursor
638 // this may alter the foreground and background colors
639 bool invertCharacterColor = false;
640
641 if ( style->rendition & RE_CURSOR )
642 drawCursor(painter,rect,foregroundColor,backgroundColor,invertCharacterColor);
643 // draw text
644 drawCharacters(painter,rect,text,style,invertCharacterColor);
645
646 painter.restore();
647 }
648
649 void TerminalView::setRandomSeed(uint randomSeed) { _randomSeed = randomSeed; }
650 uint TerminalView::randomSeed() const { return _randomSeed; }
651
652 #if 0
653 /*!
654 Set XIM Position
655 */
656 void TerminalDisplay::setCursorPos(const int curx, const int cury)
657 {
658 QPoint tL = contentsRect().topLeft();
659 int tLx = tL.x();
660 int tLy = tL.y();
661
662 int xpos, ypos;
663 ypos = _topMargin + tLy + _fontHeight*(cury-1) + _fontAscent;
664 xpos = _leftMargin + tLx + _fontWidth*curx;
665 //setMicroFocusHint(xpos, ypos, 0, _fontHeight); //### ???
666 // fprintf(stderr, "x/y = %d/%d\txpos/ypos = %d/%d\n", curx, cury, xpos, ypos);
667 _cursorLine = cury;
668 _cursorCol = curx;
669 }
670 #endif
671
672 // scrolls the image by 'lines', down if lines > 0 or up otherwise.
673 //
674 // the terminal emulation keeps track of the scrolling of the character
675 // image as it receives input, and when the view is updated, it calls scrollImage()
676 // with the final scroll amount. this improves performance because scrolling the
677 // display is much cheaper than re-rendering all the text for the
678 // part of the image which has moved up or down.
679 // Instead only new lines have to be drawn
680 //
681 // note: it is important that the area of the display which is
682 // scrolled aligns properly with the character grid -
683 // which has a top left point at (_leftMargin,_topMargin) ,
684 // a cell width of _fontWidth and a cell height of _fontHeight).
685 void TerminalView::scrollImage(int lines , const QRect& screenWindowRegion)
686 {
687 // if the flow control warning is enabled this will interfere with the
688 // scrolling optimisations and cause artifacts. the simple solution here
689 // is to just disable the optimisation whilst it is visible
690 if ( _outputSuspendedLabel && _outputSuspendedLabel->isVisible() ) {
691 return;
692 }
693
694 // constrain the region to the display
695 // the bottom of the region is capped to the number of lines in the display's
696 // internal image - 2, so that the height of 'region' is strictly less
697 // than the height of the internal image.
698 QRect region = screenWindowRegion;
699 region.setBottom( qMin(region.bottom(),this->_lines-2) );
700
701 if ( lines == 0
702 || _image == 0
703 || !region.isValid()
704 || (region.top() + abs(lines)) >= region.bottom()
705 || this->_lines <= region.height() ) return;
706
707 QRect scrollRect;
708
709 void* firstCharPos = &_image[ region.top() * this->_columns ];
710 void* lastCharPos = &_image[ (region.top() + abs(lines)) * this->_columns ];
711
712 int top = _topMargin + (region.top() * _fontHeight);
713 int linesToMove = region.height() - abs(lines);
714 int bytesToMove = linesToMove *
715 this->_columns *
716 sizeof(Character);
717
718 Q_ASSERT( linesToMove > 0 );
719 Q_ASSERT( bytesToMove > 0 );
720
721 //scroll internal image
722 if ( lines > 0 )
723 {
724 // check that the memory areas that we are going to move are valid
725 Q_ASSERT( (char*)lastCharPos + bytesToMove <
726 (char*)(_image + (this->_lines * this->_columns)) );
727
728 Q_ASSERT( (lines*this->_columns) < _imageSize );
729
730 //scroll internal image down
731 memmove( firstCharPos , lastCharPos , bytesToMove );
732
733 //set region of display to scroll, making sure that
734 //the region aligns correctly to the character grid
735 scrollRect = QRect( _leftMargin , top,
736 this->_usedColumns * _fontWidth ,
737 linesToMove * _fontHeight );
738 }
739 else
740 {
741 // check that the memory areas that we are going to move are valid
742 Q_ASSERT( (char*)firstCharPos + bytesToMove <
743 (char*)(_image + (this->_lines * this->_columns)) );
744
745 //scroll internal image up
746 memmove( lastCharPos , firstCharPos , bytesToMove );
747
748 //set region of the display to scroll, making sure that
749 //the region aligns correctly to the character grid
750 QPoint topPoint( _leftMargin , top + abs(lines)*_fontHeight );
751
752 scrollRect = QRect( topPoint ,
753 QSize( this->_usedColumns*_fontWidth ,
754 linesToMove * _fontHeight ));
755 }
756
757 //scroll the display vertically to match internal _image
758 scroll( 0 , _fontHeight * (-lines) , scrollRect );
759 }
760
761 QRegion TerminalView::hotSpotRegion() const
762 {
763 QRegion region;
764 foreach( Filter::HotSpot* hotSpot , _filterChain->hotSpots() )
765 {
766 QRect rect;
767 rect.setLeft(hotSpot->startColumn());
768 rect.setTop(hotSpot->startLine());
769 rect.setRight(hotSpot->endColumn());
770 rect.setBottom(hotSpot->endLine());
771
772 region |= imageToWidget(rect);
773 }
774 return region;
775 }
776
777 void TerminalView::processFilters()
778 {
779 if (!_screenWindow)
780 return;
781
782 QRegion preUpdateHotSpots = hotSpotRegion();
783
784 // use _screenWindow->getImage() here rather than _image because
785 // other classes may call processFilters() when this display's
786 // ScreenWindow emits a scrolled() signal - which will happen before
787 // updateImage() is called on the display and therefore _image is
788 // out of date at this point
789 _filterChain->setImage( _screenWindow->getImage(),
790 _screenWindow->windowLines(),
791 _screenWindow->windowColumns(),
792 _screenWindow->getLineProperties() );
793 _filterChain->process();
794
795 QRegion postUpdateHotSpots = hotSpotRegion();
796
797 update( preUpdateHotSpots | postUpdateHotSpots );
798 }
799
800 void TerminalView::updateImage()
801 {
802 if ( !_screenWindow )
803 return;
804 updateLineProperties();
805
806 // optimization - scroll the existing image where possible and
807 // avoid expensive text drawing for parts of the image that
808 // can simply be moved up or down
809 scrollImage( _screenWindow->scrollCount() ,
810 _screenWindow->scrollRegion() );
811 _screenWindow->resetScrollCount();
812
813 Character* const newimg = _screenWindow->getImage();
814 int lines = _screenWindow->windowLines() + 1;
815 int columns = _screenWindow->windowColumns();
816
817 setScroll( _screenWindow->currentLine() , _screenWindow->lineCount() );
818
819 if (!_image)
820 updateImageSize(); // Create _image
821
822 Q_ASSERT( this->_usedLines <= this->_lines );
823 Q_ASSERT( this->_usedColumns <= this->_columns );
824
825 int y,x,len;
826
827 QPoint tL = contentsRect().topLeft();
828
829 int tLx = tL.x();
830 int tLy = tL.y();
831 _hasBlinker = false;
832
833 CharacterColor cf; // undefined
834 CharacterColor _clipboard; // undefined
835 int cr = -1; // undefined
836
837 const int linesToUpdate = qMin(this->_lines, qMax(0,lines ));
838 const int columnsToUpdate = qMin(this->_columns,qMax(0,columns));
839
840 QChar *disstrU = new QChar[columnsToUpdate];
841 char *dirtyMask = new char[columnsToUpdate+2];
842 QRegion dirtyRegion;
843
844 // debugging variable, this records the number of lines that are found to
845 // be 'dirty' ( ie. have changed from the old _image to the new _image ) and
846 // which therefore need to be repainted
847 int dirtyLineCount = 0;
848
849 for (y = 0; y < linesToUpdate; y++)
850 {
851 const Character* currentLine = &_image[y*this->_columns];
852 const Character* const newLine = &newimg[y*columns];
853
854 bool updateLine = false;
855
856 // The dirty mask indicates which characters need repainting. We also
857 // mark surrounding neighbours dirty, in case the character exceeds
858 // its cell boundaries
859 memset(dirtyMask, 0, columnsToUpdate+2);
860
861 for( x = 0 ; x < columnsToUpdate ; x++)
862 {
863 if ( newLine[x] != currentLine[x] )
864 {
865 dirtyMask[x] = true;
866 }
867 }
868
869 if (!_resizing) // not while _resizing, we're expecting a paintEvent
870 for (x = 0; x < columnsToUpdate; x++)
871 {
872 _hasBlinker |= (newLine[x].rendition & RE_BLINK);
873
874 // Start drawing if this character or the next one differs.
875 // We also take the next one into account to handle the situation
876 // where characters exceed their cell width.
877 if (dirtyMask[x])
878 {
879 quint16 c = newLine[x+0].character;
880 if ( !c )
881 continue;
882 int p = 0;
883 disstrU[p++] = c; //fontMap(c);
884 bool lineDraw = isLineChar(c);
885 bool doubleWidth = (x+1 == columnsToUpdate) ? false : (newLine[x+1].character == 0);
886 cr = newLine[x].rendition;
887 _clipboard = newLine[x].backgroundColor;
888 if (newLine[x].foregroundColor != cf) cf = newLine[x].foregroundColor;
889 int lln = columnsToUpdate - x;
890 for (len = 1; len < lln; len++)
891 {
892 const Character& ch = newLine[x+len];
893
894 if (!ch.character)
895 continue; // Skip trailing part of multi-col chars.
896
897 bool nextIsDoubleWidth = (x+len+1 == columnsToUpdate) ? false : (newLine[x+len+1].character == 0);
898
899 if ( ch.foregroundColor != cf ||
900 ch.backgroundColor != _clipboard ||
901 ch.rendition != cr ||
902 !dirtyMask[x+len] ||
903 isLineChar(c) != lineDraw ||
904 nextIsDoubleWidth != doubleWidth )
905 break;
906
907 disstrU[p++] = c; //fontMap(c);
908 }
909
910 QString unistr(disstrU, p);
911
912 bool saveFixedFont = _fixedFont;
913 if (lineDraw)
914 _fixedFont = false;
915 if (doubleWidth)
916 _fixedFont = false;
917
918 updateLine = true;
919
920 _fixedFont = saveFixedFont;
921 x += len - 1;
922 }
923
924 }
925
926 //both the top and bottom halves of double height _lines must always be redrawn
927 //although both top and bottom halves contain the same characters, only
928 //the top one is actually
929 //drawn.
930 if (_lineProperties.count() > y)
931 updateLine |= (_lineProperties[y] & LINE_DOUBLEHEIGHT);
932
933 // if the characters on the line are different in the old and the new _image
934 // then this line must be repainted.
935 if (updateLine)
936 {
937 dirtyLineCount++;
938
939 // add the area occupied by this line to the region which needs to be
940 // repainted
941 QRect dirtyRect = QRect( _leftMargin+tLx ,
942 _topMargin+tLy+_fontHeight*y ,
943 _fontWidth * columnsToUpdate ,
944 _fontHeight );
945
946 dirtyRegion |= dirtyRect;
947 }
948
949 // replace the line of characters in the old _image with the
950 // current line of the new _image
951 memcpy((void*)currentLine,(const void*)newLine,columnsToUpdate*sizeof(Character));
952 }
953
954 // if the new _image is smaller than the previous _image, then ensure that the area
955 // outside the new _image is cleared
956 if ( linesToUpdate < _usedLines )
957 {
958 dirtyRegion |= QRect( _leftMargin+tLx ,
959 _topMargin+tLy+_fontHeight*linesToUpdate ,
960 _fontWidth * this->_columns ,
961 _fontHeight * (_usedLines-linesToUpdate) );
962 }
963 _usedLines = linesToUpdate;
964
965 if ( columnsToUpdate < _usedColumns )
966 {
967 dirtyRegion |= QRect( _leftMargin+tLx+columnsToUpdate*_fontWidth ,
968 _topMargin+tLy ,
969 _fontWidth * (_usedColumns-columnsToUpdate) ,
970 _fontHeight * this->_lines );
971 }
972 _usedColumns = columnsToUpdate;
973
974 dirtyRegion |= _inputMethodData.previousPreeditRect;
975
976 // update the parts of the display which have changed
977 update(dirtyRegion);
978
979 if ( _hasBlinker && !_blinkTimer->isActive()) _blinkTimer->start( BLINK_DELAY );
980 if (!_hasBlinker && _blinkTimer->isActive()) { _blinkTimer->stop(); _blinking = false; }
981 delete[] dirtyMask;
982 delete[] disstrU;
983
984 }
985
986 void TerminalView::showResizeNotification()
987 {
988 if (_terminalSizeHint && isVisible())
989 {
990 if (_terminalSizeStartup) {
991 _terminalSizeStartup=false;
992 return;
993 }
994 if (!_resizeWidget)
995 {
996 _resizeWidget = new QLabel(("Size: XXX x XXX"), this);
997 _resizeWidget->setMinimumWidth(_resizeWidget->fontMetrics().width(("Size: XXX x XXX")));
998 _resizeWidget->setMinimumHeight(_resizeWidget->sizeHint().height());
999 _resizeWidget->setAlignment(Qt::AlignCenter);
1000
1001 _resizeWidget->setStyleSheet("background-color:palette(window);border-style:solid;border-width:1px;border-color:palette(dark)");
1002
1003 _resizeTimer = new QTimer(this);
1004 _resizeTimer->setSingleShot(true);
1005 connect(_resizeTimer, SIGNAL(timeout()), _resizeWidget, SLOT(hide()));
1006
1007 }
1008 QString sizeStr;
1009 sizeStr.sprintf("Size: %d x %d", _columns, _lines);
1010 _resizeWidget->setText(sizeStr);
1011 _resizeWidget->move((width()-_resizeWidget->width())/2,
1012 (height()-_resizeWidget->height())/2+20);
1013 _resizeWidget->show();
1014 _resizeTimer->start(1000);
1015 }
1016 }
1017
1018 void TerminalView::setBlinkingCursor(bool blink)
1019 {
1020 _hasBlinkingCursor=blink;
1021
1022 if (blink && !_blinkCursorTimer->isActive())
1023 _blinkCursorTimer->start(BLINK_DELAY);
1024
1025 if (!blink && _blinkCursorTimer->isActive())
1026 {
1027 _blinkCursorTimer->stop();
1028 if (_cursorBlinking)
1029 blinkCursorEvent();
1030 else
1031 _cursorBlinking = false;
1032 }
1033 }
1034
1035 void TerminalView::paintEvent( QPaintEvent* pe )
1036 {
1037 updateImage();
1038 //qDebug("%s %d paintEvent", __FILE__, __LINE__);
1039 QPainter paint(this);
1040 //qDebug("%s %d paintEvent %d %d", __FILE__, __LINE__, paint.window().top(), paint.window().right());
1041
1042 foreach (QRect rect, (pe->region() & contentsRect()).rects())
1043 {
1044 drawBackground(paint,rect,palette().background().color());
1045 drawContents(paint, rect);
1046 }
1047 // drawBackground(paint,contentsRect(),palette().background().color(), true /* use opacity setting */);
1048 // drawContents(paint, contentsRect());
1049 drawInputMethodPreeditString(paint,preeditRect());
1050 paintFilters(paint);
1051 paint.end();
1052 }
1053
1054 QPoint TerminalView::cursorPosition() const
1055 {
1056 if (_screenWindow)
1057 return _screenWindow->cursorPosition();
1058 else
1059 return QPoint(0,0);
1060 }
1061
1062 QRect TerminalView::preeditRect() const
1063 {
1064 const int preeditLength = string_width(_inputMethodData.preeditString);
1065
1066 if ( preeditLength == 0 )
1067 return QRect();
1068
1069 return QRect(_leftMargin + _fontWidth*cursorPosition().x(),
1070 _topMargin + _fontHeight*cursorPosition().y(),
1071 _fontWidth*preeditLength,
1072 _fontHeight);
1073 }
1074
1075 void TerminalView::drawInputMethodPreeditString(QPainter& painter , const QRect& rect)
1076 {
1077 if ( _inputMethodData.preeditString.isEmpty() ) {
1078 return;
1079 }
1080 const QPoint cursorPos = cursorPosition();
1081
1082 bool invertColors = false;
1083 const QColor background = _colorTable[DEFAULT_BACK_COLOR].color;
1084 const QColor foreground = _colorTable[DEFAULT_FORE_COLOR].color;
1085 const Character* style = &_image[loc(cursorPos.x(),cursorPos.y())];
1086
1087 drawBackground(painter,rect,background);
1088 drawCursor(painter,rect,foreground,background,invertColors);
1089 drawCharacters(painter,rect,_inputMethodData.preeditString,style,invertColors);
1090
1091 _inputMethodData.previousPreeditRect = rect;
1092 }
1093
1094 FilterChain* TerminalView::filterChain() const
1095 {
1096 return _filterChain;
1097 }
1098
1099 void TerminalView::paintFilters(QPainter& painter)
1100 {
1101 //qDebug("%s %d paintFilters", __FILE__, __LINE__);
1102
1103 // get color of character under mouse and use it to draw
1104 // lines for filters
1105 QPoint cursorPos = mapFromGlobal(QCursor::pos());
1106 int cursorLine;
1107 int cursorColumn;
1108 getCharacterPosition( cursorPos , cursorLine , cursorColumn );
1109 Character cursorCharacter = _image[loc(cursorColumn,cursorLine)];
1110
1111 painter.setPen( QPen(cursorCharacter.foregroundColor.color(colorTable())) );
1112
1113 // iterate over hotspots identified by the display's currently active filters
1114 // and draw appropriate visuals to indicate the presence of the hotspot
1115
1116 QList<Filter::HotSpot*> spots = _filterChain->hotSpots();
1117 QListIterator<Filter::HotSpot*> iter(spots);
1118 while (iter.hasNext())
1119 {
1120 Filter::HotSpot* spot = iter.next();
1121
1122 for ( int line = spot->startLine() ; line <= spot->endLine() ; line++ )
1123 {
1124 int startColumn = 0;
1125 int endColumn = _columns-1; // TODO use number of _columns which are actually
1126 // occupied on this line rather than the width of the
1127 // display in _columns
1128
1129 // ignore whitespace at the end of the lines
1130 while ( QChar(_image[loc(endColumn,line)].character).isSpace() && endColumn > 0 )
1131 endColumn--;
1132
1133 // increment here because the column which we want to set 'endColumn' to
1134 // is the first whitespace character at the end of the line
1135 endColumn++;
1136
1137 if ( line == spot->startLine() )
1138 startColumn = spot->startColumn();
1139 if ( line == spot->endLine() )
1140 endColumn = spot->endColumn();
1141
1142 // subtract one pixel from
1143 // the right and bottom so that
1144 // we do not overdraw adjacent
1145 // hotspots
1146 //
1147 // subtracting one pixel from all sides also prevents an edge case where
1148 // moving the mouse outside a link could still leave it underlined
1149 // because the check below for the position of the cursor
1150 // finds it on the border of the target area
1151 QRect r;
1152 r.setCoords( startColumn*_fontWidth + 1, line*_fontHeight + 1,
1153 endColumn*_fontWidth - 1, (line+1)*_fontHeight - 1 );
1154
1155 // Underline link hotspots
1156 if ( spot->type() == Filter::HotSpot::Link )
1157 {
1158 QFontMetrics metrics(font());
1159
1160 // find the baseline (which is the invisible line that the characters in the font sit on,
1161 // with some having tails dangling below)
1162 int baseline = r.bottom() - metrics.descent();
1163 // find the position of the underline below that
1164 int underlinePos = baseline + metrics.underlinePos();
1165
1166 if ( r.contains( mapFromGlobal(QCursor::pos()) ) )
1167 painter.drawLine( r.left() , underlinePos ,
1168 r.right() , underlinePos );
1169 }
1170 // Marker hotspots simply have a transparent rectanglular shape
1171 // drawn on top of them
1172 else if ( spot->type() == Filter::HotSpot::Marker )
1173 {
1174 //TODO - Do not use a hardcoded colour for this
1175 painter.fillRect(r,QBrush(QColor(255,0,0,120)));
1176 }
1177 }
1178 }
1179 }
1180 void TerminalView::drawContents(QPainter &paint, const QRect &rect)
1181 {
1182 //qDebug("%s %d drawContents and rect x=%d y=%d w=%d h=%d", __FILE__, __LINE__, rect.x(), rect.y(),rect.width(),rect.height());
1183
1184 QPoint topLeft = contentsRect().topLeft();
1185 // Take the topmost vertical position for the view.
1186 int topLeftY = topLeft.y();
1187
1188 // In Konsole, the view has been centered. Don't do that here, since there
1189 // are strange hopping effects during a resize when the view does no match
1190 // exactly the widget width.
1191 // int topLeftX = (_contentWidth - _usedColumns * _fontWidth) / 2;
1192 int topLeftX = 0;
1193
1194 int leftUpperX = qMin(_usedColumns-1, qMax(0, qRound((rect.left() - topLeftX - _leftMargin ) / _fontWidth)));
1195 int leftUpperY = qMin(_usedLines-1, qMax(0, qRound((rect.top() - topLeftY - _topMargin ) / _fontHeight)));
1196 int rightLowerX = qMin(_usedColumns-1, qMax(0, qRound((rect.right() - topLeftX - _leftMargin ) / _fontWidth)));
1197 int rightLowerY = qMin(_usedLines-1, qMax(0, qRound((rect.bottom() - topLeftY - _topMargin ) / _fontHeight)));
1198
1199 const int bufferSize = _usedColumns;
1200 QChar *disstrU = new QChar[bufferSize];
1201 for (int y = leftUpperY; y <= rightLowerY; y++)
1202 {
1203 quint16 c = _image[loc(leftUpperX,y)].character;
1204 int x = leftUpperX;
1205 if(!c && x)
1206 x--; // Search for start of multi-column character
1207 for (; x <= rightLowerX; x++)
1208 {
1209 int len = 1;
1210 int p = 0;
1211
1212 // is this a single character or a sequence of characters ?
1213 if ( _image[loc(x,y)].rendition & RE_EXTENDED_CHAR )
1214 {
1215 // sequence of characters
1216 ushort extendedCharLength = 0;
1217 ushort* chars = ExtendedCharTable::instance
1218 .lookupExtendedChar(_image[loc(x,y)].charSequence,extendedCharLength);
1219 for ( int index = 0 ; index < extendedCharLength ; index++ )
1220 {
1221 Q_ASSERT( p < bufferSize );
1222 disstrU[p++] = chars[index];
1223 }
1224 }
1225 else
1226 {
1227 // single character
1228 c = _image[loc(x,y)].character;
1229 if (c)
1230 {
1231 Q_ASSERT( p < bufferSize );
1232 disstrU[p++] = c; //fontMap(c);
1233 }
1234 }
1235
1236 bool lineDraw = isLineChar(c);
1237 bool doubleWidth = (_image[ qMin(loc(x,y)+1,_imageSize) ].character == 0);
1238 CharacterColor currentForeground = _image[loc(x,y)].foregroundColor;
1239 CharacterColor currentBackground = _image[loc(x,y)].backgroundColor;
1240 quint8 currentRendition = _image[loc(x,y)].rendition;
1241
1242 while (x+len <= rightLowerX &&
1243 _image[loc(x+len,y)].foregroundColor == currentForeground &&
1244 _image[loc(x+len,y)].backgroundColor == currentBackground &&
1245 _image[loc(x+len,y)].rendition == currentRendition &&
1246 (_image[ qMin(loc(x+len,y)+1,_imageSize) ].character == 0) == doubleWidth &&
1247 isLineChar( c = _image[loc(x+len,y)].character) == lineDraw) // Assignment!
1248 {
1249 if (c)
1250 disstrU[p++] = c; //fontMap(c);
1251 if (doubleWidth) // assert((_image[loc(x+len,y)+1].character == 0)), see above if condition
1252 len++; // Skip trailing part of multi-column character
1253 len++;
1254 }
1255 if ((x+len < _usedColumns) && (!_image[loc(x+len,y)].character))
1256 len++; // Adjust for trailing part of multi-column character
1257
1258 bool save__fixedFont = _fixedFont;
1259 if (lineDraw)
1260 _fixedFont = false;
1261 if (doubleWidth)
1262 _fixedFont = false;
1263 QString unistr(disstrU,p);
1264
1265 if (y < _lineProperties.size())
1266 {
1267 if (_lineProperties[y] & LINE_DOUBLEWIDTH) {
1268 paint.scale(2,1);
1269 }
1270
1271 if (_lineProperties[y] & LINE_DOUBLEHEIGHT) {
1272 paint.scale(1,2);
1273 }
1274 }
1275
1276 // calculate the area in which the text will be drawn
1277 QRect textArea = QRect( _leftMargin+topLeftX+_fontWidth*x ,
1278 _topMargin+topLeftY+_fontHeight*y ,
1279 _fontWidth*len,
1280 _fontHeight);
1281
1282 // move the calculated area to take account of scaling applied to the painter.
1283 // the position of the area from the origin (0,0) is scaled
1284 // by the opposite of whatever
1285 // transformation has been applied to the painter. this ensures that
1286 // painting does actually start from textArea.topLeft()
1287 // (instead of textArea.topLeft() * painter-scale)
1288 QMatrix inverted = paint.matrix().inverted();
1289 textArea.moveCenter( inverted.map(textArea.center()) );
1290
1291
1292 //paint text fragment
1293 drawTextFragment( paint,
1294 textArea,
1295 unistr,
1296 &_image[loc(x,y)] );
1297
1298
1299 _fixedFont = save__fixedFont;
1300
1301 //reset back to single-width, single-height _lines
1302 paint.resetMatrix();
1303
1304 if (y < _lineProperties.size()-1)
1305 {
1306 //double-height _lines are represented by two adjacent _lines
1307 //containing the same characters
1308 //both _lines will have the LINE_DOUBLEHEIGHT attribute.
1309 //If the current line has the LINE_DOUBLEHEIGHT attribute,
1310 //we can therefore skip the next line
1311 if (_lineProperties[y] & LINE_DOUBLEHEIGHT)
1312 y++;
1313 }
1314 x += len - 1;
1315 } // for x
1316 } // for y
1317 delete [] disstrU;
1318 }
1319
1320 void TerminalView::blinkEvent()
1321 {
1322 _blinking = !_blinking;
1323
1324 //TODO: Optimise to only repaint the areas of the widget
1325 // where there is blinking text
1326 // rather than repainting the whole widget.
1327 update();
1328 }
1329
1330 QRect TerminalView::imageToWidget(const QRect& imageArea) const
1331 {
1332 //qDebug("%s %d imageToWidget", __FILE__, __LINE__);
1333 QRect result;
1334 result.setLeft( _leftMargin + _fontWidth * imageArea.left() );
1335 result.setTop( _topMargin + _fontHeight * imageArea.top() );
1336 result.setWidth( _fontWidth * imageArea.width() );
1337 result.setHeight( _fontHeight * imageArea.height() );
1338
1339 return result;
1340 }
1341
1342 void TerminalView::blinkCursorEvent()
1343 {
1344 _cursorBlinking = !_cursorBlinking;
1345
1346 QRect cursorRect = imageToWidget( QRect(cursorPosition(),QSize(1,1)) );
1347
1348 update(cursorRect);
1349 }
1350
1351 /* ------------------------------------------------------------------------- */
1352 /* */
1353 /* Resizing */
1354 /* */
1355 /* ------------------------------------------------------------------------- */
1356
1357 void TerminalView::resizeEvent(QResizeEvent*)
1358 {
1359 updateImageSize();
1360 }
1361
1362 void TerminalView::propagateSize()
1363 {
1364 if (_isFixedSize)
1365 {
1366 setSize(_columns, _lines);
1367 QWidget::setFixedSize(sizeHint());
1368 parentWidget()->adjustSize();
1369 parentWidget()->setFixedSize(parentWidget()->sizeHint());
1370 return;
1371 }
1372 if (_image)
1373 updateImageSize();
1374 }
1375
1376 void TerminalView::updateImageSize()
1377 {
1378 //qDebug("%s %d updateImageSize", __FILE__, __LINE__);
1379 Character* oldimg = _image;
1380 int oldlin = _lines;
1381 int oldcol = _columns;
1382
1383 makeImage();
1384
1385
1386 // copy the old image to reduce flicker
1387 int lines = qMin(oldlin,_lines);
1388 int columns = qMin(oldcol,_columns);
1389
1390 //qDebug("%s %d updateImageSize", __FILE__, __LINE__);
1391 if (oldimg)
1392 {
1393 for (int line = 0; line < lines; line++)
1394 {
1395 memcpy((void*)&_image[_columns*line],
1396 (void*)&oldimg[oldcol*line],columns*sizeof(Character));
1397 }
1398 delete[] oldimg;
1399 }
1400
1401 //qDebug("%s %d updateImageSize", __FILE__, __LINE__);
1402 if (_screenWindow)
1403 _screenWindow->setWindowLines(_lines);
1404
1405 _resizing = (oldlin!=_lines) || (oldcol!=_columns);
1406
1407 if ( _resizing )
1408 {
1409 //qDebug("%s %d updateImageSize", __FILE__, __LINE__);
1410 showResizeNotification();
1411 emit changedContentSizeSignal(_contentHeight, _contentWidth); // expose resizeEvent
1412 }
1413 //qDebug("%s %d updateImageSize", __FILE__, __LINE__);
1414
1415 _resizing = false;
1416 }
1417
1418 //showEvent and hideEvent are reimplemented here so that it appears to other classes that the
1419 //display has been resized when the display is hidden or shown.
1420 //
1421 //this allows
1422 //TODO: Perhaps it would be better to have separate signals for show and hide instead of using
1423 //the same signal as the one for a content size change
1424 void TerminalView::showEvent(QShowEvent*)
1425 {
1426 emit changedContentSizeSignal(_contentHeight,_contentWidth);
1427 }
1428 void TerminalView::hideEvent(QHideEvent*)
1429 {
1430 emit changedContentSizeSignal(_contentHeight,_contentWidth);
1431 }
1432
1433 /* ------------------------------------------------------------------------- */
1434 /* */
1435 /* Scrollbar */
1436 /* */
1437 /* ------------------------------------------------------------------------- */
1438
1439 void TerminalView::scrollBarPositionChanged(int)
1440 {
1441 if ( !_screenWindow )
1442 return;
1443
1444 _screenWindow->scrollTo( _scrollBar->value() );
1445
1446 // if the thumb has been moved to the bottom of the _scrollBar then set
1447 // the display to automatically track new output,
1448 // that is, scroll down automatically
1449 // to how new _lines as they are added
1450 const bool atEndOfOutput = (_scrollBar->value() == _scrollBar->maximum());
1451 _screenWindow->setTrackOutput( atEndOfOutput );
1452
1453 updateImage();
1454 }
1455
1456 void TerminalView::setScroll(int cursor, int slines)
1457 {
1458 //qDebug("%s %d setScroll", __FILE__, __LINE__);
1459 // update _scrollBar if the range or value has changed,
1460 // otherwise return
1461 //
1462 // setting the range or value of a _scrollBar will always trigger
1463 // a repaint, so it should be avoided if it is not necessary
1464 if ( _scrollBar->minimum() == 0 &&
1465 _scrollBar->maximum() == (slines - _lines) &&
1466 _scrollBar->value() == cursor )
1467 {
1468 return;
1469 }
1470
1471 disconnect(_scrollBar, SIGNAL(valueChanged(int)), this, SLOT(scrollBarPositionChanged(int)));
1472 _scrollBar->setRange(0,slines - _lines);
1473 _scrollBar->setSingleStep(1);
1474 _scrollBar->setPageStep(_lines);
1475 _scrollBar->setValue(cursor);
1476 connect(_scrollBar, SIGNAL(valueChanged(int)), this, SLOT(scrollBarPositionChanged(int)));
1477 }
1478
1479 void TerminalView::setScrollBarPosition(ScrollBarPosition position)
1480 {
1481 if (_scrollbarLocation == position) {
1482 // return;
1483 }
1484
1485 if ( position == NoScrollBar )
1486 _scrollBar->hide();
1487 else
1488 _scrollBar->show();
1489
1490 _topMargin = _leftMargin = 1;
1491 _scrollbarLocation = position;
1492
1493 propagateSize();
1494 update();
1495 }
1496
1497 void TerminalView::mousePressEvent(QMouseEvent* ev)
1498 {
1499 if ( _possibleTripleClick && (ev->button()==Qt::LeftButton) ) {
1500 mouseTripleClickEvent(ev);
1501 return;
1502 }
1503
1504 if ( !contentsRect().contains(ev->pos()) ) return;
1505
1506 if ( !_screenWindow ) return;
1507
1508 int charLine;
1509 int charColumn;
1510 getCharacterPosition(ev->pos(),charLine,charColumn);
1511 QPoint pos = QPoint(charColumn,charLine);
1512
1513 if ( ev->button() == Qt::LeftButton)
1514 {
1515 _lineSelectionMode = false;
1516 _wordSelectionMode = false;
1517
1518 emit isBusySelecting(true); // Keep it steady...
1519 // Drag only when the Control key is hold
1520 bool selected = false;
1521
1522 // The receiver of the testIsSelected() signal will adjust
1523 // 'selected' accordingly.
1524 //emit testIsSelected(pos.x(), pos.y(), selected);
1525
1526 selected = _screenWindow->isSelected(pos.x(),pos.y());
1527
1528 if ((!_ctrlDrag || ev->modifiers() & Qt::ControlModifier) && selected ) {
1529 // The user clicked inside selected text
1530 dragInfo.state = diPending;
1531 dragInfo.start = ev->pos();
1532 }
1533 else {
1534 // No reason to ever start a drag event
1535 dragInfo.state = diNone;
1536
1537 _preserveLineBreaks = !( ( ev->modifiers() & Qt::ControlModifier ) && !(ev->modifiers() & Qt::AltModifier) );
1538 _columnSelectionMode = (ev->modifiers() & Qt::AltModifier) && (ev->modifiers() & Qt::ControlModifier);
1539
1540 if (_mouseMarks || (ev->modifiers() & Qt::ShiftModifier))
1541 {
1542 _screenWindow->clearSelection();
1543
1544 //emit clearSelectionSignal();
1545 pos.ry() += _scrollBar->value();
1546 _iPntSel = _pntSel = pos;
1547 _actSel = 1; // left mouse button pressed but nothing selected yet.
1548
1549 }
1550 else
1551 {
1552 emit mouseSignal( 0, charColumn + 1, charLine + 1 +_scrollBar->value() -_scrollBar->maximum() , 0);
1553 }
1554 }
1555 }
1556 else if ( ev->button() == Qt::MidButton )
1557 {
1558 if ( _mouseMarks || (!_mouseMarks && (ev->modifiers() & Qt::ShiftModifier)) )
1559 emitSelection(true,ev->modifiers() & Qt::ControlModifier);
1560 else
1561 emit mouseSignal( 1, charColumn +1, charLine +1 +_scrollBar->value() -_scrollBar->maximum() , 0);
1562 }
1563 else if ( ev->button() == Qt::RightButton )
1564 {
1565 if (_mouseMarks || (ev->modifiers() & Qt::ShiftModifier))
1566 {
1567 emit configureRequest( this,
1568 ev->modifiers() & (Qt::ShiftModifier|Qt::ControlModifier),
1569 ev->pos()
1570 );
1571 }
1572 else
1573 emit mouseSignal( 2, charColumn +1, charLine +1 +_scrollBar->value() -_scrollBar->maximum() , 0);
1574 }
1575
1576 QWidget::mousePressEvent (ev);
1577 }
1578
1579 QList<QAction*> TerminalView::filterActions(const QPoint& position)
1580 {
1581 int charLine, charColumn;
1582 getCharacterPosition(position,charLine,charColumn);
1583
1584 Filter::HotSpot* spot = _filterChain->hotSpotAt(charLine,charColumn);
1585
1586 return spot ? spot->actions() : QList<QAction*>();
1587 }
1588
1589 void TerminalView::mouseMoveEvent(QMouseEvent* ev)
1590 {
1591 int charLine = 0;
1592 int charColumn = 0;
1593
1594 getCharacterPosition(ev->pos(),charLine,charColumn);
1595
1596 // handle filters
1597 // change link hot-spot appearance on mouse-over
1598 Filter::HotSpot* spot = _filterChain->hotSpotAt(charLine,charColumn);
1599 if ( spot && spot->type() == Filter::HotSpot::Link)
1600 {
1601 QRect previousHotspotArea = _mouseOverHotspotArea;
1602 _mouseOverHotspotArea.setCoords( qMin(spot->startColumn() , spot->endColumn()) * _fontWidth,
1603 spot->startLine() * _fontHeight,
1604 qMax(spot->startColumn() , spot->endColumn()) * _fontHeight,
1605 (spot->endLine()+1) * _fontHeight );
1606
1607 // display tooltips when mousing over links
1608 // TODO: Extend this to work with filter types other than links
1609 const QString& tooltip = spot->tooltip();
1610 if ( !tooltip.isEmpty() )
1611 {
1612 QToolTip::showText( mapToGlobal(ev->pos()) , tooltip , this , _mouseOverHotspotArea );
1613 }
1614
1615 update( _mouseOverHotspotArea | previousHotspotArea );
1616 }
1617 else if ( _mouseOverHotspotArea.isValid() )
1618 {
1619 update( _mouseOverHotspotArea );
1620 // set hotspot area to an invalid rectangle
1621 _mouseOverHotspotArea = QRect();
1622 }
1623
1624 // for auto-hiding the cursor, we need mouseTracking
1625 if (ev->buttons() == Qt::NoButton ) return;
1626
1627 // if the terminal is interested in mouse movements
1628 // then emit a mouse movement signal, unless the shift
1629 // key is being held down, which overrides this.
1630 if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier))
1631 {
1632 int button = 3;
1633 if (ev->buttons() & Qt::LeftButton)
1634 button = 0;
1635 if (ev->buttons() & Qt::MidButton)
1636 button = 1;
1637 if (ev->buttons() & Qt::RightButton)
1638 button = 2;
1639
1640
1641 emit mouseSignal( button,
1642 charColumn + 1,
1643 charLine + 1 +_scrollBar->value() -_scrollBar->maximum(),
1644 1 );
1645
1646 return;
1647 }
1648
1649 if (dragInfo.state == diPending)
1650 {
1651 // we had a mouse down, but haven't confirmed a drag yet
1652 // if the mouse has moved sufficiently, we will confirm
1653
1654 int distance = 10; //KGlobalSettings::dndEventDelay();
1655 if ( ev->x() > dragInfo.start.x() + distance || ev->x() < dragInfo.start.x() - distance ||
1656 ev->y() > dragInfo.start.y() + distance || ev->y() < dragInfo.start.y() - distance)
1657 {
1658 // we've left the drag square, we can start a real drag operation now
1659 emit isBusySelecting(false); // Ok.. we can breath again.
1660
1661 _screenWindow->clearSelection();
1662 doDrag();
1663 }
1664 return;
1665 }
1666 else if (dragInfo.state == diDragging)
1667 {
1668 // this isn't technically needed because mouseMoveEvent is suppressed during
1669 // Qt drag operations, replaced by dragMoveEvent
1670 return;
1671 }
1672
1673 if (_actSel == 0) return;
1674
1675 // don't extend selection while pasting
1676 if (ev->buttons() & Qt::MidButton) return;
1677
1678 extendSelection( ev->pos() );
1679 }
1680
1681 #if 0
1682 void TerminalDisplay::setSelectionEnd()
1683 {
1684 extendSelection( _configureRequestPoint );
1685 }
1686 #endif
1687
1688 void TerminalView::extendSelection(const QPoint& position) {
1689 QPoint pos = position;
1690
1691 if (!_screenWindow) {
1692 return;
1693 }
1694
1695 QPoint tL = contentsRect().topLeft();
1696 int tLx = tL.x();
1697 int tLy = tL.y();
1698 int scroll = _scrollBar->value();
1699
1700 // we're in the process of moving the mouse with the left button pressed
1701 // the mouse cursor will kept caught within the bounds of the text in
1702 // this widget.
1703
1704 // Adjust position within text area bounds. See FIXME above.
1705 if (pos.x() < tLx + _leftMargin) {
1706 pos.setX(tLx + _leftMargin);
1707 }
1708 if (pos.x() > tLx + _leftMargin + _usedColumns * _fontWidth - 1) {
1709 pos.setX(tLx + _leftMargin + _usedColumns * _fontWidth);
1710 }
1711 if (pos.y() < tLy + _topMargin) {
1712 pos.setY(tLy + _topMargin);
1713 }
1714 if (pos.y() > tLy + _topMargin + _usedLines * _fontHeight - 1) {
1715 pos.setY(tLy + _topMargin + _usedLines * _fontHeight - 1);
1716 }
1717
1718 if (pos.y() == tLy + _topMargin + _usedLines * _fontHeight - 1) {
1719 _scrollBar->setValue(_scrollBar->value() + yMouseScroll); // scrollforward
1720 }
1721 if (pos.y() == tLy + _topMargin) {
1722 _scrollBar->setValue(_scrollBar->value() - yMouseScroll); // scrollback
1723 }
1724
1725 int charColumn = 0;
1726 int charLine = 0;
1727 getCharacterPosition(pos, charLine, charColumn);
1728
1729 QPoint here = QPoint(charColumn, charLine);
1730 QPoint ohere(here);
1731 QPoint _iPntSelCorr = _iPntSel;
1732 _iPntSelCorr.ry() -= _scrollBar->value();
1733 QPoint _pntSelCorr = _pntSel;
1734 _pntSelCorr.ry() -= _scrollBar->value();
1735 bool swapping = false;
1736
1737 if (_wordSelectionMode) {
1738 // Extend to word boundaries
1739 int i = 0;
1740 int selClass = 0;
1741
1742 bool left_not_right = (here.y() < _iPntSelCorr.y() ||
1743 (here.y() == _iPntSelCorr.y() && here.x() < _iPntSelCorr.x()));
1744 bool old_left_not_right = (_pntSelCorr.y() < _iPntSelCorr.y() ||
1745 (_pntSelCorr.y() == _iPntSelCorr.y() && _pntSelCorr.x() < _iPntSelCorr.x()));
1746 swapping = left_not_right != old_left_not_right;
1747
1748 // Find left (left_not_right ? from here : from start)
1749 QPoint left = left_not_right ? here : _iPntSelCorr;
1750 i = loc(left.x(), left.y());
1751 if (i >= 0 && i <= _imageSize) {
1752 selClass = charClass(_image[i].character);
1753 while (((left.x() > 0) || (left.y() > 0 && (_lineProperties[left.y() - 1] & LINE_WRAPPED)))
1754 && charClass(_image[i - 1].character) == selClass) {
1755 i--;
1756 if (left.x() > 0) {
1757 left.rx()--;
1758 } else {
1759 left.rx() = _usedColumns - 1;
1760 left.ry()--;
1761 }
1762 }
1763 }
1764
1765 // Find left (left_not_right ? from start : from here)
1766 QPoint right = left_not_right ? _iPntSelCorr : here;
1767 i = loc(right.x(), right.y());
1768 if (i >= 0 && i <= _imageSize) {
1769 selClass = charClass(_image[i].character);
1770 while (((right.x() < _usedColumns - 1) || (right.y() < _usedLines - 1 && (_lineProperties[right.y()] & LINE_WRAPPED)))
1771 && charClass(_image[i + 1].character) == selClass) {
1772 i++;
1773 if (right.x() < _usedColumns - 1) {
1774 right.rx()++;
1775 } else {
1776 right.rx() = 0;
1777 right.ry()++;
1778 }
1779 }
1780 }
1781
1782 // Pick which is start (ohere) and which is extension (here)
1783 if (left_not_right) {
1784 here = left;
1785 ohere = right;
1786 } else {
1787 here = right;
1788 ohere = left;
1789 }
1790 ohere.rx()++;
1791 }
1792
1793 if (_lineSelectionMode) {
1794 // Extend to complete line
1795 bool above_not_below = (here.y() < _iPntSelCorr.y());
1796
1797 QPoint above = above_not_below ? here : _iPntSelCorr;
1798 QPoint below = above_not_below ? _iPntSelCorr : here;
1799
1800 while (above.y() > 0 && (_lineProperties[above.y() - 1] & LINE_WRAPPED)) {
1801 above.ry()--;
1802 }
1803 while (below.y() < _usedLines - 1 && (_lineProperties[below.y()] & LINE_WRAPPED)) {
1804 below.ry()++;
1805 }
1806
1807 above.setX(0);
1808 below.setX(_usedColumns - 1);
1809
1810 // Pick which is start (ohere) and which is extension (here)
1811 if (above_not_below) {
1812 here = above;
1813 ohere = below;
1814 } else {
1815 here = below;
1816 ohere = above;
1817 }
1818
1819 QPoint newSelBegin = QPoint(ohere.x(), ohere.y());
1820 swapping = !(_tripleSelBegin == newSelBegin);
1821 _tripleSelBegin = newSelBegin;
1822
1823 ohere.rx()++;
1824 }
1825
1826 int offset = 0;
1827 if (!_wordSelectionMode && !_lineSelectionMode) {
1828 int i = 0;
1829 int selClass = 0;
1830
1831 bool left_not_right = (here.y() < _iPntSelCorr.y() ||
1832 (here.y() == _iPntSelCorr.y() && here.x() < _iPntSelCorr.x()));
1833 bool old_left_not_right = (_pntSelCorr.y() < _iPntSelCorr.y() ||
1834 (_pntSelCorr.y() == _iPntSelCorr.y() && _pntSelCorr.x() < _iPntSelCorr.x()));
1835 swapping = left_not_right != old_left_not_right;
1836
1837 // Find left (left_not_right ? from here : from start)
1838 QPoint left = left_not_right ? here : _iPntSelCorr;
1839
1840 // Find left (left_not_right ? from start : from here)
1841 QPoint right = left_not_right ? _iPntSelCorr : here;
1842 if (right.x() > 0 && !_columnSelectionMode) {
1843 i = loc(right.x(), right.y());
1844 if (i >= 0 && i <= _imageSize) {
1845 selClass = charClass(_image[i - 1].character);
1846 if (selClass == ' ') {
1847 while (right.x() < _usedColumns - 1 && charClass(_image[i + 1].character) == selClass && (right.y() < _usedLines - 1) &&
1848 !(_lineProperties[right.y()] & LINE_WRAPPED)) {
1849 i++;
1850 right.rx()++;
1851 }
1852 if (right.x() < _usedColumns - 1) {
1853 right = left_not_right ? _iPntSelCorr : here;
1854 } else {
1855 right.rx()++; // will be balanced later because of offset=-1;
1856 }
1857 }
1858 }
1859 }
1860
1861 // Pick which is start (ohere) and which is extension (here)
1862 if (left_not_right) {
1863 here = left;
1864 ohere = right;
1865 offset = 0;
1866 } else {
1867 here = right;
1868 ohere = left;
1869 offset = -1;
1870 }
1871 }
1872
1873 if ((here == _pntSelCorr) && (scroll == _scrollBar->value())) {
1874 return; // not moved
1875 }
1876
1877 if (here == ohere) {
1878 return; // It's not left, it's not right.
1879 }
1880
1881 if (_actSel < 2 || swapping) {
1882 if (_columnSelectionMode && !_lineSelectionMode && !_wordSelectionMode) {
1883 _screenWindow->setSelectionStart(ohere.x(), ohere.y(), true);
1884 } else {
1885 _screenWindow->setSelectionStart(ohere.x() - 1 - offset , ohere.y(), false);
1886 }
1887
1888 }
1889
1890 _actSel = 2; // within selection
1891 _pntSel = here;
1892 _pntSel.ry() += _scrollBar->value();
1893
1894 if (_columnSelectionMode && !_lineSelectionMode && !_wordSelectionMode) {
1895 _screenWindow->setSelectionEnd(here.x(), here.y());
1896 } else {
1897 _screenWindow->setSelectionEnd(here.x() + offset, here.y());
1898 }
1899 }
1900
1901 void TerminalView::mouseReleaseEvent(QMouseEvent* ev)
1902 {
1903 if ( !_screenWindow )
1904 return;
1905
1906 int charLine;
1907 int charColumn;
1908 getCharacterPosition(ev->pos(),charLine,charColumn);
1909
1910 if ( ev->button() == Qt::LeftButton)
1911 {
1912 emit isBusySelecting(false);
1913 if(dragInfo.state == diPending)
1914 {
1915 // We had a drag event pending but never confirmed. Kill selection
1916 _screenWindow->clearSelection();
1917 //emit clearSelectionSignal();
1918 }
1919 else
1920 {
1921 if ( _actSel > 1 )
1922 {
1923 setSelection( _screenWindow->selectedText(_preserveLineBreaks) );
1924 }
1925
1926 _actSel = 0;
1927
1928 //FIXME: emits a release event even if the mouse is
1929 // outside the range. The procedure used in `mouseMoveEvent'
1930 // applies here, too.
1931
1932 if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier))
1933 emit mouseSignal( 3, // release
1934 charColumn + 1,
1935 charLine + 1 +_scrollBar->value() -_scrollBar->maximum() , 0);
1936 }
1937 dragInfo.state = diNone;
1938 }
1939
1940
1941 if ( !_mouseMarks &&
1942 ((ev->button() == Qt::RightButton && !(ev->modifiers() & Qt::ShiftModifier))
1943 || ev->button() == Qt::MidButton) )
1944 {
1945 emit mouseSignal( 3,
1946 charColumn + 1,
1947 charLine + 1 +_scrollBar->value() -_scrollBar->maximum() ,
1948 0);
1949 }
1950
1951 QWidget::mouseReleaseEvent(ev);
1952 }
1953
1954 void TerminalView::getCharacterPosition(const QPoint& widgetPoint,int& line,int& column) const
1955 {
1956
1957 column = (widgetPoint.x() + _fontWidth/2 -contentsRect().left()-_leftMargin) / _fontWidth;
1958 line = (widgetPoint.y()-contentsRect().top()-_topMargin) / _fontHeight;
1959
1960 if ( line < 0 )
1961 line = 0;
1962 if ( column < 0 )
1963 column = 0;
1964
1965 if ( line >= _usedLines )
1966 line = _usedLines-1;
1967
1968 // the column value returned can be equal to _usedColumns, which
1969 // is the position just after the last character displayed in a line.
1970 //
1971 // this is required so that the user can select characters in the right-most
1972 // column (or left-most for right-to-left input)
1973 if ( column > _usedColumns )
1974 column = _usedColumns;
1975 }
1976
1977 void TerminalView::updateLineProperties()
1978 {
1979 if ( !_screenWindow )
1980 return;
1981
1982 _lineProperties = _screenWindow->getLineProperties();
1983 }
1984
1985 void TerminalView::mouseDoubleClickEvent(QMouseEvent* ev)
1986 {
1987 if ( ev->button() != Qt::LeftButton) return;
1988 if ( !_screenWindow ) return;
1989
1990 int charLine = 0;
1991 int charColumn = 0;
1992
1993 getCharacterPosition(ev->pos(),charLine,charColumn);
1994
1995 QPoint pos(charColumn,charLine);
1996
1997 // pass on double click as two clicks.
1998 if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier))
1999 {
2000 // Send just _ONE_ click event, since the first click of the double click
2001 // was already sent by the click handler
2002 emit mouseSignal( 0,
2003 pos.x()+1,
2004 pos.y()+1 +_scrollBar->value() -_scrollBar->maximum(),
2005 0 ); // left button
2006 return;
2007 }
2008
2009 _screenWindow->clearSelection();
2010 QPoint bgnSel = pos;
2011 QPoint endSel = pos;
2012 int i = loc(bgnSel.x(),bgnSel.y());
2013 _iPntSel = bgnSel;
2014 _iPntSel.ry() += _scrollBar->value();
2015
2016 _wordSelectionMode = true;
2017
2018 // find word boundaries...
2019 int selClass = charClass(_image[i].character);
2020 {
2021 // find the start of the word
2022 int x = bgnSel.x();
2023 while ( ((x>0) || (bgnSel.y()>0 && (_lineProperties[bgnSel.y()-1] & LINE_WRAPPED) ))
2024 && charClass(_image[i-1].character) == selClass )
2025 {
2026 i--;
2027 if (x>0)
2028 x--;
2029 else
2030 {
2031 x=_usedColumns-1;
2032 bgnSel.ry()--;
2033 }
2034 }
2035
2036 bgnSel.setX(x);
2037 _screenWindow->setSelectionStart( bgnSel.x() , bgnSel.y() , false );
2038
2039 // find the end of the word
2040 i = loc( endSel.x(), endSel.y() );
2041 x = endSel.x();
2042 while( ((x<_usedColumns-1) || (endSel.y()<_usedLines-1 && (_lineProperties[endSel.y()] & LINE_WRAPPED) ))
2043 && charClass(_image[i+1].character) == selClass )
2044 {
2045 i++;
2046 if (x<_usedColumns-1)
2047 x++;
2048 else
2049 {
2050 x=0;
2051 endSel.ry()++;
2052 }
2053 }
2054
2055 endSel.setX(x);
2056
2057 // In word selection mode don't select @ (64) if at end of word.
2058 if ( ( QChar( _image[i].character ) == '@' ) && ( ( endSel.x() - bgnSel.x() ) > 0 ) )
2059 endSel.setX( x - 1 );
2060
2061
2062 _actSel = 2; // within selection
2063
2064 _screenWindow->setSelectionEnd( endSel.x() , endSel.y() );
2065
2066 setSelection( _screenWindow->selectedText(_preserveLineBreaks) );
2067 }
2068
2069 _possibleTripleClick=true;
2070
2071 QTimer::singleShot(QApplication::doubleClickInterval(),this,
2072 SLOT(tripleClickTimeout()));
2073 }
2074
2075 void TerminalView::wheelEvent( QWheelEvent* ev )
2076 {
2077 if (ev->orientation() != Qt::Vertical)
2078 return;
2079
2080 if ( _mouseMarks )
2081 _scrollBar->event(ev);
2082 else
2083 {
2084 int charLine;
2085 int charColumn;
2086 getCharacterPosition( ev->pos() , charLine , charColumn );
2087
2088 emit mouseSignal( ev->delta() > 0 ? 4 : 5,
2089 charColumn + 1,
2090 charLine + 1 +_scrollBar->value() -_scrollBar->maximum() ,
2091 0);
2092 }
2093 }
2094
2095 void TerminalView::tripleClickTimeout()
2096 {
2097 _possibleTripleClick=false;
2098 }
2099
2100 void TerminalView::mouseTripleClickEvent(QMouseEvent* ev)
2101 {
2102 if ( !_screenWindow ) return;
2103
2104 int charLine;
2105 int charColumn;
2106 getCharacterPosition(ev->pos(),charLine,charColumn);
2107 _iPntSel = QPoint(charColumn,charLine);
2108
2109 _screenWindow->clearSelection();
2110
2111 _lineSelectionMode = true;
2112 _wordSelectionMode = false;
2113
2114 _actSel = 2; // within selection
2115 emit isBusySelecting(true); // Keep it steady...
2116
2117 while (_iPntSel.y()>0 && (_lineProperties[_iPntSel.y()-1] & LINE_WRAPPED) )
2118 _iPntSel.ry()--;
2119
2120 if (_tripleClickMode == SelectForwardsFromCursor) {
2121 // find word boundary start
2122 int i = loc(_iPntSel.x(),_iPntSel.y());
2123 int selClass = charClass(_image[i].character);
2124 int x = _iPntSel.x();
2125
2126 while ( ((x>0) ||
2127 (_iPntSel.y()>0 && (_lineProperties[_iPntSel.y()-1] & LINE_WRAPPED) )
2128 )
2129 && charClass(_image[i-1].character) == selClass )
2130 {
2131 i--;
2132 if (x>0)
2133 x--;
2134 else
2135 {
2136 x=_columns-1;
2137 _iPntSel.ry()--;
2138 }
2139 }
2140
2141 _screenWindow->setSelectionStart( x , _iPntSel.y() , false );
2142 _tripleSelBegin = QPoint( x, _iPntSel.y() );
2143 }
2144 else if (_tripleClickMode == SelectWholeLine) {
2145 _screenWindow->setSelectionStart( 0 , _iPntSel.y() , false );
2146 _tripleSelBegin = QPoint( 0, _iPntSel.y() );
2147 }
2148
2149 while (_iPntSel.y()<_lines-1 && (_lineProperties[_iPntSel.y()] & LINE_WRAPPED) )
2150 _iPntSel.ry()++;
2151
2152 _screenWindow->setSelectionEnd( _columns - 1 , _iPntSel.y() );
2153
2154 setSelection(_screenWindow->selectedText(_preserveLineBreaks));
2155
2156 _iPntSel.ry() += _scrollBar->value();
2157
2158 emit tripleClicked( _screenWindow->selectedText( _preserveLineBreaks ) );
2159 }
2160
2161
2162 bool TerminalView::focusNextPrevChild( bool next )
2163 {
2164 if (next)
2165 return false; // This disables changing the active part in konqueror
2166 // when pressing Tab
2167 return QWidget::focusNextPrevChild( next );
2168 }
2169
2170
2171 int TerminalView::charClass(quint16 ch) const
2172 {
2173 QChar qch=QChar(ch);
2174 if ( qch.isSpace() ) return ' ';
2175
2176 if ( qch.isLetterOrNumber() || _wordCharacters.contains(qch, Qt::CaseInsensitive ) )
2177 return 'a';
2178
2179 // Everything else is weird
2180 return 1;
2181 }
2182
2183 void TerminalView::setWordCharacters(const QString& wc)
2184 {
2185 _wordCharacters = wc;
2186 }
2187
2188 void TerminalView::setUsesMouse(bool on)
2189 {
2190 _mouseMarks = on;
2191 setCursor( _mouseMarks ? Qt::IBeamCursor : Qt::ArrowCursor );
2192 }
2193 bool TerminalView::usesMouse() const
2194 {
2195 return _mouseMarks;
2196 }
2197
2198 /* ------------------------------------------------------------------------- */
2199 /* */
2200 /* Clipboard */
2201 /* */
2202 /* ------------------------------------------------------------------------- */
2203
2204 #undef KeyPress
2205
2206 void TerminalView::emitSelection(bool useXselection,bool appendReturn)
2207 {
2208 if ( !_screenWindow )
2209 return;
2210
2211 // Paste Clipboard by simulating keypress events
2212 QString text = QApplication::clipboard()->text(useXselection ? QClipboard::Selection :
2213 QClipboard::Clipboard);
2214 if(appendReturn)
2215 text.append("\r");
2216 if ( ! text.isEmpty() )
2217 {
2218 text.replace("\n", "\r");
2219 QKeyEvent e(QEvent::KeyPress, 0, Qt::NoModifier, text);
2220 emit keyPressedSignal(&e); // expose as a big fat keypress event
2221
2222 _screenWindow->clearSelection();
2223 }
2224 }
2225
2226 void TerminalView::setSelection(const QString& t)
2227 {
2228 QApplication::clipboard()->setText(t, QClipboard::Selection);
2229 }
2230
2231 void TerminalView::copyClipboard()
2232 {
2233 if ( !_screenWindow )
2234 return;
2235
2236 QString text = _screenWindow->selectedText(_preserveLineBreaks);
2237 QApplication::clipboard()->setText(text);
2238 }
2239
2240 void TerminalView::pasteClipboard()
2241 {
2242 emitSelection(false,false);
2243 }
2244
2245 void TerminalView::pasteSelection()
2246 {
2247 emitSelection(true,false);
2248 }
2249
2250 /* ------------------------------------------------------------------------- */
2251 /* */
2252 /* Keyboard */
2253 /* */
2254 /* ------------------------------------------------------------------------- */
2255
2256 void TerminalView::keyPressEvent( QKeyEvent* event )
2257 {
2258 //qDebug("%s %d keyPressEvent and key is %d", __FILE__, __LINE__, event->key());
2259
2260 bool emitKeyPressSignal = true;
2261
2262 // Keyboard-based navigation
2263 if ( event->modifiers() == Qt::ShiftModifier )
2264 {
2265 bool update = true;
2266
2267 if ( event->key() == Qt::Key_PageUp )
2268 {
2269 //qDebug("%s %d pageup", __FILE__, __LINE__);
2270 _screenWindow->scrollBy( ScreenWindow::ScrollPages , -1 );
2271 }
2272 else if ( event->key() == Qt::Key_PageDown )
2273 {
2274 //qDebug("%s %d pagedown", __FILE__, __LINE__);
2275 _screenWindow->scrollBy( ScreenWindow::ScrollPages , 1 );
2276 }
2277 else if ( event->key() == Qt::Key_Up )
2278 {
2279 //qDebug("%s %d keyup", __FILE__, __LINE__);
2280 _screenWindow->scrollBy( ScreenWindow::ScrollLines , -1 );
2281 }
2282 else if ( event->key() == Qt::Key_Down )
2283 {
2284 //qDebug("%s %d keydown", __FILE__, __LINE__);
2285 _screenWindow->scrollBy( ScreenWindow::ScrollLines , 1 );
2286 }
2287 else {
2288 update = false;
2289 }
2290
2291 if ( update )
2292 {
2293 //qDebug("%s %d updating", __FILE__, __LINE__);
2294 _screenWindow->setTrackOutput( _screenWindow->atEndOfOutput() );
2295
2296 updateLineProperties();
2297 updateImage();
2298
2299 // do not send key press to terminal
2300 emitKeyPressSignal = false;
2301 }
2302 }
2303
2304 _screenWindow->setTrackOutput( true );
2305
2306 _actSel=0; // Key stroke implies a screen update, so TerminalDisplay won't
2307 // know where the current selection is.
2308
2309 if (_hasBlinkingCursor)
2310 {
2311 _blinkCursorTimer->start(BLINK_DELAY);
2312 if (_cursorBlinking)
2313 blinkCursorEvent();
2314 else
2315 _cursorBlinking = false;
2316 }
2317
2318 if ( emitKeyPressSignal && !_readonly )
2319 emit keyPressedSignal(event);
2320
2321 if (_readonly) {
2322 event->ignore();
2323 }
2324 else {
2325 event->accept();
2326 }
2327 }
2328
2329 void TerminalView::inputMethodEvent( QInputMethodEvent* event )
2330 {
2331 QKeyEvent keyEvent(QEvent::KeyPress,0,Qt::NoModifier,event->commitString());
2332 emit keyPressedSignal(&keyEvent);
2333
2334 _inputMethodData.preeditString = event->preeditString();
2335 update(preeditRect() | _inputMethodData.previousPreeditRect);
2336
2337 event->accept();
2338 }
2339 QVariant TerminalView::inputMethodQuery( Qt::InputMethodQuery query ) const
2340 {
2341 const QPoint cursorPos = _screenWindow ? _screenWindow->cursorPosition() : QPoint(0,0);
2342 switch ( query )
2343 {
2344 case Qt::ImMicroFocus:
2345 return imageToWidget(QRect(cursorPos.x(),cursorPos.y(),1,1));
2346 break;
2347 case Qt::ImFont:
2348 return font();
2349 break;
2350 case Qt::ImCursorPosition:
2351 // return the cursor position within the current line
2352 return cursorPos.x();
2353 break;
2354 case Qt::ImSurroundingText:
2355 {
2356 // return the text from the current line
2357 QString lineText;
2358 QTextStream stream(&lineText);
2359 PlainTextDecoder decoder;
2360 decoder.begin(&stream);
2361 decoder.decodeLine(&_image[loc(0,cursorPos.y())],_usedColumns,_lineProperties[cursorPos.y()]);
2362 decoder.end();
2363 return lineText;
2364 }
2365 break;
2366 case Qt::ImCurrentSelection:
2367 return QString();
2368 break;
2369 default:
2370 break;
2371 }
2372
2373 return QVariant();
2374 }
2375
2376 bool TerminalView::event( QEvent *e )
2377 {
2378 if ( e->type() == QEvent::ShortcutOverride )
2379 {
2380 QKeyEvent* keyEvent = static_cast<QKeyEvent *>( e );
2381
2382 // a check to see if keyEvent->text() is empty is used
2383 // to avoid intercepting the press of the modifier key on its own.
2384 //
2385 // this is important as it allows a press and release of the Alt key
2386 // on its own to focus the menu bar, making it possible to
2387 // work with the menu without using the mouse
2388 if ( (keyEvent->modifiers() == Qt::AltModifier) &&
2389 !keyEvent->text().isEmpty() )
2390 {
2391 keyEvent->accept();
2392 return true;
2393 }
2394
2395 // Override any of the following shortcuts because
2396 // they are needed by the terminal
2397 int keyCode = keyEvent->key() | keyEvent->modifiers();
2398 switch ( keyCode )
2399 {
2400 // list is taken from the QLineEdit::event() code
2401 case Qt::Key_Tab:
2402 case Qt::Key_Delete:
2403 case Qt::Key_Home:
2404 case Qt::Key_End:
2405 case Qt::Key_Backspace:
2406 case Qt::Key_Left:
2407 case Qt::Key_Right:
2408 keyEvent->accept();
2409 return true;
2410 }
2411 }
2412 return QWidget::event( e );
2413 }
2414
2415 void TerminalView::setBellMode(int mode)
2416 {
2417 _bellMode=mode;
2418 }
2419
2420 void TerminalView::enableBell()
2421 {
2422 _allowBell = true;
2423 }
2424
2425 void TerminalView::swapColorTable()
2426 {
2427 ColorEntry color = _colorTable[1];
2428 _colorTable[1]=_colorTable[0];
2429 _colorTable[0]= color;
2430 _colorsInverted = !_colorsInverted;
2431 update();
2432 }
2433
2434 void TerminalView::clearImage()
2435 {
2436 // We initialize _image[_imageSize] too. See makeImage()
2437 for (int i = 0; i <= _imageSize; i++)
2438 {
2439 _image[i].character = ' ';
2440 _image[i].foregroundColor = CharacterColor(COLOR_SPACE_DEFAULT,
2441 DEFAULT_FORE_COLOR);
2442 _image[i].backgroundColor = CharacterColor(COLOR_SPACE_DEFAULT,
2443 DEFAULT_BACK_COLOR);
2444 _image[i].rendition = DEFAULT_RENDITION;
2445 }
2446 }
2447
2448 void TerminalView::calcGeometry()
2449 {
2450 _scrollBar->resize(QApplication::style()->pixelMetric(QStyle::PM_ScrollBarExtent),
2451 contentsRect().height());
2452 switch(_scrollbarLocation)
2453 {
2454 case NoScrollBar :
2455 _leftMargin = DEFAULT_LEFT_MARGIN;
2456 _contentWidth = contentsRect().width() - 2 * DEFAULT_LEFT_MARGIN;
2457 break;
2458 case ScrollBarLeft :
2459 _leftMargin = DEFAULT_LEFT_MARGIN + _scrollBar->width();
2460 _contentWidth = contentsRect().width() - 2 * DEFAULT_LEFT_MARGIN - _scrollBar->width();
2461 _scrollBar->move(contentsRect().topLeft());
2462 break;
2463 case ScrollBarRight:
2464 _leftMargin = DEFAULT_LEFT_MARGIN;
2465 _contentWidth = contentsRect().width() - 2 * DEFAULT_LEFT_MARGIN - _scrollBar->width();
2466 _scrollBar->move(contentsRect().topRight() - QPoint(_scrollBar->width()-1,0));
2467 break;
2468 }
2469
2470 _topMargin = DEFAULT_TOP_MARGIN;
2471 _contentHeight = contentsRect().height() - 2 * DEFAULT_TOP_MARGIN + /* mysterious */ 1;
2472
2473 if (!_isFixedSize)
2474 {
2475 // ensure that display is always at least one column wide
2476 _columns = qMax(1,qRound(_contentWidth / _fontWidth));
2477 _usedColumns = qMin(_usedColumns,_columns);
2478
2479 // ensure that display is always at least one line high
2480 _lines = qMax(1, qRound(_contentHeight / _fontHeight));
2481 _usedLines = qMin(_usedLines,_lines);
2482 }
2483 }
2484
2485 void TerminalView::makeImage()
2486 {
2487 //qDebug("%s %d makeImage", __FILE__, __LINE__);
2488 calcGeometry();
2489
2490 // confirm that array will be of non-zero size, since the painting code
2491 // assumes a non-zero array length
2492 Q_ASSERT( _lines > 0 && _columns > 0 );
2493 Q_ASSERT( _usedLines <= _lines && _usedColumns <= _columns );
2494
2495 _imageSize=_lines*_columns;
2496
2497 // We over-commit one character so that we can be more relaxed in dealing with
2498 // certain boundary conditions: _image[_imageSize] is a valid but unused position
2499 _image = new Character[_imageSize+1];
2500
2501 clearImage();
2502 }
2503
2504 // calculate the needed size
2505 void TerminalView::setSize(int columns, int lines)
2506 {
2507 //FIXME - Not quite correct, a small amount of additional space
2508 // will be used for margins, the scrollbar etc.
2509 // we need to allow for this so that '_size' does allow
2510 // enough room for the specified number of columns and lines to fit
2511
2512 QSize newSize = QSize( columns * _fontWidth ,
2513 lines * _fontHeight );
2514
2515 if ( newSize != size() )
2516 {
2517 _size = newSize;
2518 updateGeometry();
2519 }
2520 }
2521
2522 void TerminalView::setFixedSize(int cols, int lins)
2523 {
2524 _isFixedSize = true;
2525
2526 //ensure that display is at least one line by one column in size
2527 _columns = qMax(1,cols);
2528 _lines = qMax(1,lins);
2529 _usedColumns = qMin(_usedColumns,_columns);
2530 _usedLines = qMin(_usedLines,_lines);
2531
2532 if (_image)
2533 {
2534 delete[] _image;
2535 makeImage();
2536 }
2537 setSize(cols, lins);
2538 QWidget::setFixedSize(_size);
2539 }
2540
2541 QSize TerminalView::sizeHint() const
2542 {
2543 return _size;
2544 }
2545
2546
2547 /* --------------------------------------------------------------------- */
2548 /* */
2549 /* Drag & Drop */
2550 /* */
2551 /* --------------------------------------------------------------------- */
2552
2553 void TerminalView::dragEnterEvent(QDragEnterEvent* event)
2554 {
2555 if (event->mimeData()->hasFormat("text/plain"))
2556 event->acceptProposedAction();
2557 }
2558
2559 void TerminalView::dropEvent(QDropEvent* event)
2560 {
2561 // KUrl::List urls = KUrl::List::fromMimeData(event->mimeData());
2562
2563 QString dropText;
2564 /* if (!urls.isEmpty())
2565 {
2566 for ( int i = 0 ; i < urls.count() ; i++ )
2567 {
2568 KUrl url = KIO::NetAccess::mostLocalUrl( urls[i] , 0 );
2569 QString urlText;
2570
2571 if (url.isLocalFile())
2572 urlText = url.path();
2573 else
2574 urlText = url.url();
2575
2576 // in future it may be useful to be able to insert file names with drag-and-drop
2577 // without quoting them (this only affects paths with spaces in)
2578 urlText = KShell::quoteArg(urlText);
2579
2580 dropText += urlText;
2581
2582 if ( i != urls.count()-1 )
2583 dropText += ' ';
2584 }
2585 }
2586 else
2587 {
2588 dropText = event->mimeData()->text();
2589 }
2590 */
2591 if(event->mimeData()->hasFormat("text/plain"))
2592 {
2593 emit sendStringToEmu(dropText.toLocal8Bit());
2594 }
2595 }
2596
2597 void TerminalView::doDrag()
2598 {
2599 dragInfo.state = diDragging;
2600 dragInfo.dragObject = new QDrag(this);
2601 QMimeData *mimeData = new QMimeData;
2602 mimeData->setText(QApplication::clipboard()->text(QClipboard::Selection));
2603 dragInfo.dragObject->setMimeData(mimeData);
2604 dragInfo.dragObject->start(Qt::CopyAction);
2605 // Don't delete the QTextDrag object. Qt will delete it when it's done with it.
2606 }
2607
2608 void TerminalView::outputSuspended(bool suspended)
2609 {
2610 //create the label when this function is first called
2611 if (!_outputSuspendedLabel)
2612 {
2613 //This label includes a link to an English language website
2614 //describing the 'flow control' (Xon/Xoff) feature found in almost
2615 //all terminal emulators.
2616 //If there isn't a suitable article available in the target language the link
2617 //can simply be removed.
2618 _outputSuspendedLabel = new QLabel( ("<qt>Output has been "
2619 "<a href=\"http://en.wikipedia.org/wiki/XON\">suspended</a>"
2620 " by pressing Ctrl+S."
2621 " Press <b>Ctrl+Q</b> to resume.</qt>"),
2622 this );
2623
2624 QPalette palette(_outputSuspendedLabel->palette());
2625
2626 palette.setColor(QPalette::Normal, QPalette::WindowText, QColor(Qt::white));
2627 palette.setColor(QPalette::Normal, QPalette::Window, QColor(Qt::black));
2628 // KColorScheme::adjustForeground(palette,KColorScheme::NeutralText);
2629 // KColorScheme::adjustBackground(palette,KColorScheme::NeutralBackground);
2630 _outputSuspendedLabel->setPalette(palette);
2631 _outputSuspendedLabel->setAutoFillBackground(true);
2632 _outputSuspendedLabel->setBackgroundRole(QPalette::Base);
2633 _outputSuspendedLabel->setFont(QApplication::font());
2634 _outputSuspendedLabel->setMargin(5);
2635
2636 //enable activation of "Xon/Xoff" link in label
2637 _outputSuspendedLabel->setTextInteractionFlags(Qt::LinksAccessibleByMouse |
2638 Qt::LinksAccessibleByKeyboard);
2639 _outputSuspendedLabel->setOpenExternalLinks(true);
2640 _outputSuspendedLabel->setVisible(false);
2641
2642 _gridLayout->addWidget(_outputSuspendedLabel);
2643 _gridLayout->addItem( new QSpacerItem(0,0,QSizePolicy::Expanding,
2644 QSizePolicy::Expanding),
2645 1,0);
2646
2647 }
2648
2649 _outputSuspendedLabel->setVisible(suspended);
2650 }
2651
2652 uint TerminalView::lineSpacing() const
2653 {
2654 return _lineSpacing;
2655 }
2656
2657 void TerminalView::setLineSpacing(uint i)
2658 {
2659 _lineSpacing = i;
2660 setVTFont(font()); // Trigger an update.
2661 }