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