Mercurial > hg > octave-nkf
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 } |