• Skip to content
  • Skip to link menu
KDE 4.1 API Reference
  • KDE API Reference
  • API Reference
  • Sitemap
  • Contact Us
 

Konsole

TerminalDisplay.cpp

Go to the documentation of this file.
00001 /*
00002     This file is part of Konsole, a terminal emulator for KDE.
00003     
00004     Copyright 2006-2008 by Robert Knight <robertknight@gmail.com>
00005     Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de>
00006     
00007     This program is free software; you can redistribute it and/or modify
00008     it under the terms of the GNU General Public License as published by
00009     the Free Software Foundation; either version 2 of the License, or
00010     (at your option) any later version.
00011 
00012     This program is distributed in the hope that it will be useful,
00013     but WITHOUT ANY WARRANTY; without even the implied warranty of
00014     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015     GNU General Public License for more details.
00016 
00017     You should have received a copy of the GNU General Public License
00018     along with this program; if not, write to the Free Software
00019     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
00020     02110-1301  USA.
00021 */
00022 
00023 // Own
00024 #include "TerminalDisplay.h"
00025 
00026 // Qt
00027 #include <QtGui/QApplication>
00028 #include <QtGui/QBoxLayout>
00029 #include <QtGui/QClipboard>
00030 #include <QtGui/QKeyEvent>
00031 #include <QtCore/QEvent>
00032 #include <QtCore/QTime>
00033 #include <QtCore/QFile>
00034 #include <QtGui/QGridLayout>
00035 #include <QtGui/QLabel>
00036 #include <QtGui/QLayout>
00037 #include <QtGui/QPainter>
00038 #include <QtGui/QPixmap>
00039 #include <QtGui/QScrollBar>
00040 #include <QtGui/QStyle>
00041 #include <QtCore/QTimer>
00042 #include <QtGui/QToolTip>
00043 
00044 // KDE
00045 #include <kshell.h>
00046 #include <KColorScheme>
00047 #include <KCursor>
00048 #include <kdebug.h>
00049 #include <KLocale>
00050 #include <KMenu>
00051 #include <KNotification>
00052 #include <KGlobalSettings>
00053 #include <KShortcut>
00054 #include <KIO/NetAccess>
00055 
00056 // Konsole
00057 #include <config-apps.h>
00058 #include "Filter.h"
00059 #include "konsole_wcwidth.h"
00060 #include "ScreenWindow.h"
00061 #include "TerminalCharacterDecoder.h"
00062 
00063 using namespace Konsole;
00064 
00065 #ifndef loc
00066 #define loc(X,Y) ((Y)*_columns+(X))
00067 #endif
00068 
00069 #define yMouseScroll 1
00070 
00071 #define REPCHAR   "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
00072                   "abcdefgjijklmnopqrstuvwxyz" \
00073                   "0123456789./+@"

const ColorEntry Konsole::base_color_table[TABLE_COLORS] =
// The following are almost IBM standard color codes, with some slight
// gamma correction for the dim colors to compensate for bright X screens.
// It contains the 8 ansiterm/xterm colors in 2 intensities.
{
  // Fixme: could add faint colors here, also.
  // normal
  ColorEntry(QColor(0x00,0x00,0x00), 0, 0 ), ColorEntry( QColor(0xB2,0xB2,0xB2), 1, 0 ), // Dfore, Dback
  ColorEntry(QColor(0x00,0x00,0x00), 0, 0 ), ColorEntry( QColor(0xB2,0x18,0x18), 0, 0 ), // Black, Red
  ColorEntry(QColor(0x18,0xB2,0x18), 0, 0 ), ColorEntry( QColor(0xB2,0x68,0x18), 0, 0 ), // Green, Yellow
  ColorEntry(QColor(0x18,0x18,0xB2), 0, 0 ), ColorEntry( QColor(0xB2,0x18,0xB2), 0, 0 ), // Blue, Magenta
  ColorEntry(QColor(0x18,0xB2,0xB2), 0, 0 ), ColorEntry( QColor(0xB2,0xB2,0xB2), 0, 0 ), // Cyan, White
  // intensiv
  ColorEntry(QColor(0x00,0x00,0x00), 0, 1 ), ColorEntry( QColor(0xFF,0xFF,0xFF), 1, 0 ),
  ColorEntry(QColor(0x68,0x68,0x68), 0, 0 ), ColorEntry( QColor(0xFF,0x54,0x54), 0, 0 ),
  ColorEntry(QColor(0x54,0xFF,0x54), 0, 0 ), ColorEntry( QColor(0xFF,0xFF,0x54), 0, 0 ),
  ColorEntry(QColor(0x54,0x54,0xFF), 0, 0 ), ColorEntry( QColor(0xFF,0x54,0xFF), 0, 0 ),
  ColorEntry(QColor(0x54,0xFF,0xFF), 0, 0 ), ColorEntry( QColor(0xFF,0xFF,0xFF), 0, 0 )
};

// scroll increment used when dragging selection at top/bottom of window.

// static
bool TerminalDisplay::_antialiasText = true;
bool TerminalDisplay::HAVE_TRANSPARENCY = false;

// we use this to force QPainter to display text in LTR mode
// more information can be found in: http://unicode.org/reports/tr9/ 
const QChar LTR_OVERRIDE_CHAR( 0x202D );

/* ------------------------------------------------------------------------- */
/*                                                                           */
/*                                Colors                                     */
/*                                                                           */
/* ------------------------------------------------------------------------- */

/* Note that we use ANSI color order (bgr), while IBMPC color order is (rgb)

   Code        0       1       2       3       4       5       6       7
   ----------- ------- ------- ------- ------- ------- ------- ------- -------
   ANSI  (bgr) Black   Red     Green   Yellow  Blue    Magenta Cyan    White
   IBMPC (rgb) Black   Blue    Green   Cyan    Red     Magenta Yellow  White
*/

ScreenWindow* TerminalDisplay::screenWindow() const
{
    return _screenWindow;
}
void TerminalDisplay::setScreenWindow(ScreenWindow* window)
{
    // disconnect existing screen window if any
    if ( _screenWindow )
    {
        disconnect( _screenWindow , 0 , this , 0 );
    }

    _screenWindow = window;

    if ( window )
    {
#warning "The order here is not specified - does it matter whether updateImage or updateLineProperties comes first?"
00074         connect( _screenWindow , SIGNAL(outputChanged()) , this , SLOT(updateLineProperties()) );
00075         connect( _screenWindow , SIGNAL(outputChanged()) , this , SLOT(updateImage()) );
00076         window->setWindowLines(_lines);
00077     }
00078 }
00079 
00080 const ColorEntry* TerminalDisplay::colorTable() const
00081 {
00082   return _colorTable;
00083 }
00084 void TerminalDisplay::setBackgroundColor(const QColor& color)
00085 {
00086     _colorTable[DEFAULT_BACK_COLOR].color = color;
00087     QPalette p = palette();
00088     p.setColor( backgroundRole(), color ); 
00089     setPalette( p );
00090 
00091     // Avoid propagating the palette change to the scroll bar 
00092     _scrollBar->setPalette( QApplication::palette() );  
00093 
00094     update();
00095 }
00096 void TerminalDisplay::setForegroundColor(const QColor& color)
00097 {
00098     _colorTable[DEFAULT_FORE_COLOR].color = color;
00099 
00100     update();
00101 }
00102 void TerminalDisplay::setColorTable(const ColorEntry table[])
00103 {
00104   for (int i = 0; i < TABLE_COLORS; i++)
00105       _colorTable[i] = table[i];
00106 
00107   setBackgroundColor(_colorTable[DEFAULT_BACK_COLOR].color);
00108 }
00109 
00110 /* ------------------------------------------------------------------------- */
00111 /*                                                                           */
00112 /*                                   Font                                    */
00113 /*                                                                           */
00114 /* ------------------------------------------------------------------------- */
00115 
00116 /*
00117    The VT100 has 32 special graphical characters. The usual vt100 extended
00118    xterm fonts have these at 0x00..0x1f.
00119 
00120    QT's iso mapping leaves 0x00..0x7f without any changes. But the graphicals
00121    come in here as proper unicode characters.
00122 
00123    We treat non-iso10646 fonts as VT100 extended and do the requiered mapping
00124    from unicode to 0x00..0x1f. The remaining translation is then left to the
00125    QCodec.
00126 */
00127 
00128 static inline bool isLineChar(quint16 c) { return ((c & 0xFF80) == 0x2500);}
00129 static inline bool isLineCharString(const QString& string)
00130 {
00131         return (string.length() > 0) && (isLineChar(string.at(0).unicode()));
00132 }
00133                         
00134 
00135 // assert for i in [0..31] : vt100extended(vt100_graphics[i]) == i.
00136 
00137 unsigned short Konsole::vt100_graphics[32] =
00138 { // 0/8     1/9    2/10    3/11    4/12    5/13    6/14    7/15
00139   0x0020, 0x25C6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0,
00140   0x00b1, 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c,
00141   0xF800, 0xF801, 0x2500, 0xF803, 0xF804, 0x251c, 0x2524, 0x2534,
00142   0x252c, 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00b7
00143 };
00144 
00145 void TerminalDisplay::fontChange(const QFont&)
00146 {
00147   QFontMetrics fm(font());
00148   _fontHeight = fm.height() + _lineSpacing;
00149 
00150   // waba TerminalDisplay 1.123:
00151   // "Base character width on widest ASCII character. This prevents too wide
00152   //  characters in the presence of double wide (e.g. Japanese) characters."
00153   // Get the width from representative normal width characters
00154   _fontWidth = qRound((double)fm.width(REPCHAR)/(double)strlen(REPCHAR));
00155 
00156   _fixedFont = true;
00157 
00158   int fw = fm.width(REPCHAR[0]);
00159   for(unsigned int i=1; i< strlen(REPCHAR); i++)
00160   {
00161     if (fw != fm.width(REPCHAR[i]))
00162     {
00163       _fixedFont = false;
00164       break;
00165     }
00166   }
00167 
00168   if (_fontWidth < 1)
00169     _fontWidth=1;
00170 
00171   _fontAscent = fm.ascent();
00172 
00173   emit changedFontMetricSignal( _fontHeight, _fontWidth );
00174   propagateSize();
00175   update();
00176 }
00177 
00178 void TerminalDisplay::setVTFont(const QFont& f)
00179 {
00180   QFont font = f;
00181 
00182   QFontMetrics metrics(font);
00183 
00184   if ( !QFontInfo(font).fixedPitch() )
00185   {
00186       kWarning() << "Using an unsupported variable-width font in the terminal.  This may produce display errors.";
00187   }
00188 
00189   if ( metrics.height() < height() && metrics.maxWidth() < width() )
00190   {
00191     // hint that text should be drawn without anti-aliasing.  
00192     // depending on the user's font configuration, this may not be respected
00193     if (!_antialiasText)
00194         font.setStyleStrategy( QFont::NoAntialias );
00195  
00196     // experimental optimization.  Konsole assumes that the terminal is using a 
00197     // mono-spaced font, in which case kerning information should have an effect.
00198     // Disabling kerning saves some computation when rendering text. 
00199     font.setKerning(false);
00200 
00201     QWidget::setFont(font);
00202     fontChange(font);
00203   }
00204 }
00205 
00206 void TerminalDisplay::setFont(const QFont &)
00207 {
00208   // ignore font change request if not coming from konsole itself
00209 }
00210 
00211 /* ------------------------------------------------------------------------- */
00212 /*                                                                           */
00213 /*                         Constructor / Destructor                          */
00214 /*                                                                           */
00215 /* ------------------------------------------------------------------------- */
00216 
00217 TerminalDisplay::TerminalDisplay(QWidget *parent)
00218 :QWidget(parent)
00219 ,_screenWindow(0)
00220 ,_allowBell(true)
00221 ,_gridLayout(0)
00222 ,_fontHeight(1)
00223 ,_fontWidth(1)
00224 ,_fontAscent(1)
00225 ,_lines(1)
00226 ,_columns(1)
00227 ,_usedLines(1)
00228 ,_usedColumns(1)
00229 ,_contentHeight(1)
00230 ,_contentWidth(1)
00231 ,_image(0)
00232 ,_randomSeed(0)
00233 ,_resizing(false)
00234 ,_terminalSizeHint(false)
00235 ,_terminalSizeStartup(true)
00236 ,_bidiEnabled(false)
00237 ,_actSel(0)
00238 ,_wordSelectionMode(false)
00239 ,_lineSelectionMode(false)
00240 ,_preserveLineBreaks(false)
00241 ,_columnSelectionMode(false)
00242 ,_scrollbarLocation(NoScrollBar)
00243 ,_wordCharacters(":@-./_~")
00244 ,_bellMode(SystemBeepBell)
00245 ,_blinking(false)
00246 ,_hasBlinker(false)
00247 ,_cursorBlinking(false)
00248 ,_hasBlinkingCursor(false)
00249 ,_ctrlDrag(false)
00250 ,_tripleClickMode(SelectWholeLine)
00251 ,_isFixedSize(false)
00252 ,_possibleTripleClick(false)
00253 ,_resizeWidget(0)
00254 ,_resizeTimer(0)
00255 ,_flowControlWarningEnabled(false)
00256 ,_outputSuspendedLabel(0)
00257 ,_lineSpacing(0)
00258 ,_colorsInverted(false)
00259 ,_blendColor(qRgba(0,0,0,0xff))
00260 ,_filterChain(new TerminalImageFilterChain())
00261 ,_cursorShape(BlockCursor)
00262 {
00263   // terminal applications are not designed with Right-To-Left in mind,
00264   // so the layout is forced to Left-To-Right
00265   setLayoutDirection(Qt::LeftToRight);
00266 
00267   // The offsets are not yet calculated.
00268   // Do not calculate these too often to be more smoothly when resizing
00269   // konsole in opaque mode.
00270   _topMargin = DEFAULT_TOP_MARGIN;
00271   _leftMargin = DEFAULT_LEFT_MARGIN;
00272 
00273   // create scroll bar for scrolling output up and down
00274   // set the scroll bar's slider to occupy the whole area of the scroll bar initially
00275   _scrollBar = new QScrollBar(this);
00276   setScroll(0,0); 
00277   _scrollBar->setCursor( Qt::ArrowCursor );
00278   connect(_scrollBar, SIGNAL(valueChanged(int)), this, 
00279                       SLOT(scrollBarPositionChanged(int)));
00280 
00281   // setup timers for blinking cursor and text
00282   _blinkTimer   = new QTimer(this);
00283   connect(_blinkTimer, SIGNAL(timeout()), this, SLOT(blinkEvent()));
00284   _blinkCursorTimer   = new QTimer(this);
00285   connect(_blinkCursorTimer, SIGNAL(timeout()), this, SLOT(blinkCursorEvent()));
00286 
00287   KCursor::setAutoHideCursor( this, true );
00288   
00289   setUsesMouse(true);
00290   setColorTable(base_color_table);
00291   setMouseTracking(true);
00292 
00293   // Enable drag and drop 
00294   setAcceptDrops(true); // attempt
00295   dragInfo.state = diNone;
00296 
00297   setFocusPolicy( Qt::WheelFocus );
00298 
00299   // enable input method support
00300   setAttribute(Qt::WA_InputMethodEnabled, true);
00301 
00302   // this is an important optimization, it tells Qt
00303   // that TerminalDisplay will handle repainting its entire area.
00304   setAttribute(Qt::WA_OpaquePaintEvent);
00305 
00306   _gridLayout = new QGridLayout(this);
00307   _gridLayout->setMargin(0);
00308 
00309   setLayout( _gridLayout ); 
00310 
00311   new AutoScrollHandler(this);
00312 }
00313 
00314 TerminalDisplay::~TerminalDisplay()
00315 {
00316   qApp->removeEventFilter( this );
00317   
00318   delete[] _image;
00319 
00320   delete _gridLayout;
00321   delete _outputSuspendedLabel;
00322   delete _filterChain;
00323 }
00324 
00325 /* ------------------------------------------------------------------------- */
00326 /*                                                                           */
00327 /*                             Display Operations                            */
00328 /*                                                                           */
00329 /* ------------------------------------------------------------------------- */
00330 
00350 enum LineEncode
00351 {
00352     TopL  = (1<<1),
00353     TopC  = (1<<2),
00354     TopR  = (1<<3),
00355 
00356     LeftT = (1<<5),
00357     Int11 = (1<<6),
00358     Int12 = (1<<7),
00359     Int13 = (1<<8),
00360     RightT = (1<<9),
00361 
00362     LeftC = (1<<10),
00363     Int21 = (1<<11),
00364     Int22 = (1<<12),
00365     Int23 = (1<<13),
00366     RightC = (1<<14),
00367 
00368     LeftB = (1<<15),
00369     Int31 = (1<<16),
00370     Int32 = (1<<17),
00371     Int33 = (1<<18),
00372     RightB = (1<<19),
00373 
00374     BotL  = (1<<21),
00375     BotC  = (1<<22),
00376     BotR  = (1<<23)
00377 };
00378 
00379 #include "LineFont.h"
00380 
00381 static void drawLineChar(QPainter& paint, int x, int y, int w, int h, uchar code)
00382 {
00383     //Calculate cell midpoints, end points.
00384     int cx = x + w/2;
00385     int cy = y + h/2;
00386     int ex = x + w - 1;
00387     int ey = y + h - 1;
00388 
00389     quint32 toDraw = LineChars[code];
00390 
00391     //Top _lines:
00392     if (toDraw & TopL)
00393         paint.drawLine(cx-1, y, cx-1, cy-2);
00394     if (toDraw & TopC)
00395         paint.drawLine(cx, y, cx, cy-2);
00396     if (toDraw & TopR)
00397         paint.drawLine(cx+1, y, cx+1, cy-2);
00398 
00399     //Bot _lines:
00400     if (toDraw & BotL)
00401         paint.drawLine(cx-1, cy+2, cx-1, ey);
00402     if (toDraw & BotC)
00403         paint.drawLine(cx, cy+2, cx, ey);
00404     if (toDraw & BotR)
00405         paint.drawLine(cx+1, cy+2, cx+1, ey);
00406 
00407     //Left _lines:
00408     if (toDraw & LeftT)
00409         paint.drawLine(x, cy-1, cx-2, cy-1);
00410     if (toDraw & LeftC)
00411         paint.drawLine(x, cy, cx-2, cy);
00412     if (toDraw & LeftB)
00413         paint.drawLine(x, cy+1, cx-2, cy+1);
00414 
00415     //Right _lines:
00416     if (toDraw & RightT)
00417         paint.drawLine(cx+2, cy-1, ex, cy-1);
00418     if (toDraw & RightC)
00419         paint.drawLine(cx+2, cy, ex, cy);
00420     if (toDraw & RightB)
00421         paint.drawLine(cx+2, cy+1, ex, cy+1);
00422 
00423     //Intersection points.
00424     if (toDraw & Int11)
00425         paint.drawPoint(cx-1, cy-1);
00426     if (toDraw & Int12)
00427         paint.drawPoint(cx, cy-1);
00428     if (toDraw & Int13)
00429         paint.drawPoint(cx+1, cy-1);
00430 
00431     if (toDraw & Int21)
00432         paint.drawPoint(cx-1, cy);
00433     if (toDraw & Int22)
00434         paint.drawPoint(cx, cy);
00435     if (toDraw & Int23)
00436         paint.drawPoint(cx+1, cy);
00437 
00438     if (toDraw & Int31)
00439         paint.drawPoint(cx-1, cy+1);
00440     if (toDraw & Int32)
00441         paint.drawPoint(cx, cy+1);
00442     if (toDraw & Int33)
00443         paint.drawPoint(cx+1, cy+1);
00444 
00445 }
00446 
00447 void TerminalDisplay::drawLineCharString(   QPainter& painter, int x, int y, const QString& str, 
00448                                     const Character* attributes)
00449 {
00450         const QPen& currentPen = painter.pen();
00451         
00452         if ( attributes->rendition & RE_BOLD )
00453         {
00454             QPen boldPen(currentPen);
00455             boldPen.setWidth(3);
00456             painter.setPen( boldPen );
00457         }   
00458         
00459         for (int i=0 ; i < str.length(); i++)
00460         {
00461             uchar code = str[i].cell();
00462             if (LineChars[code])
00463                 drawLineChar(painter, x + (_fontWidth*i), y, _fontWidth, _fontHeight, code);
00464         }
00465 
00466         painter.setPen( currentPen );
00467 }
00468 
00469 void TerminalDisplay::setKeyboardCursorShape(KeyboardCursorShape shape)
00470 {
00471     _cursorShape = shape;
00472 }
00473 TerminalDisplay::KeyboardCursorShape TerminalDisplay::keyboardCursorShape() const
00474 {
00475     return _cursorShape;
00476 }
00477 void TerminalDisplay::setKeyboardCursorColor(bool useForegroundColor, const QColor& color)
00478 {
00479     if (useForegroundColor)
00480         _cursorColor = QColor(); // an invalid color means that
00481                                  // the foreground color of the
00482                                  // current character should
00483                                  // be used
00484 
00485     else
00486         _cursorColor = color;
00487 }
00488 QColor TerminalDisplay::keyboardCursorColor() const
00489 {
00490     return _cursorColor;
00491 }
00492 
00493 void TerminalDisplay::setOpacity(qreal opacity)
00494 {
00495     QColor color(_blendColor);
00496     color.setAlphaF(opacity);
00497 
00498     // enable automatic background filling to prevent the display
00499     // flickering if there is no transparency
00500     /*if ( color.alpha() == 255 ) 
00501     {
00502         setAutoFillBackground(true);
00503     }
00504     else
00505     {
00506         setAutoFillBackground(false);
00507     }*/
00508 
00509     _blendColor = color.rgba();
00510 }
00511 
00512 void TerminalDisplay::drawBackground(QPainter& painter, const QRect& rect, const QColor& backgroundColor, bool useOpacitySetting )
00513 {
00514         // the area of the widget showing the contents of the terminal display is drawn
00515         // using the background color from the color scheme set with setColorTable()
00516         //
00517         // the area of the widget behind the scroll-bar is drawn using the background
00518         // brush from the scroll-bar's palette, to give the effect of the scroll-bar
00519         // being outside of the terminal display and visual consistency with other KDE
00520         // applications.  
00521         //
00522         QRect scrollBarArea = _scrollBar->isVisible() ? 
00523                                     rect.intersected(_scrollBar->geometry()) :
00524                                     QRect();
00525         QRegion contentsRegion = QRegion(rect).subtracted(scrollBarArea);
00526         QRect contentsRect = contentsRegion.boundingRect();
00527 
00528         if ( HAVE_TRANSPARENCY && qAlpha(_blendColor) < 0xff && useOpacitySetting ) 
00529         {
00530             QColor color(backgroundColor);
00531             color.setAlpha(qAlpha(_blendColor));
00532 
00533             painter.save();
00534             painter.setCompositionMode(QPainter::CompositionMode_Source);
00535             painter.fillRect(contentsRect, color);
00536             painter.restore();
00537         } 
00538         else
00539             painter.fillRect(contentsRect, backgroundColor);
00540 
00541         painter.fillRect(scrollBarArea,_scrollBar->palette().background());
00542 }
00543 
00544 void TerminalDisplay::drawCursor(QPainter& painter, 
00545                                  const QRect& rect,
00546                                  const QColor& foregroundColor,
00547                                  const QColor& /*backgroundColor*/,
00548                                  bool& invertCharacterColor)
00549 {
00550     QRect cursorRect = rect;
00551     cursorRect.setHeight(_fontHeight - _lineSpacing - 1);
00552     
00553     if (!_cursorBlinking)
00554     {
00555        if ( _cursorColor.isValid() )
00556            painter.setPen(_cursorColor);
00557        else
00558            painter.setPen(foregroundColor);
00559 
00560        if ( _cursorShape == BlockCursor )
00561        {
00562             // draw the cursor outline, adjusting the area so that
00563             // it is draw entirely inside 'rect'
00564             int penWidth = qMax(1,painter.pen().width());
00565 
00566             painter.drawRect(cursorRect.adjusted(penWidth/2,
00567                                                  penWidth/2,
00568                                                  - penWidth/2 - penWidth%2,
00569                                                  - penWidth/2 - penWidth%2));
00570             if ( hasFocus() )
00571             {
00572                 painter.fillRect(cursorRect, _cursorColor.isValid() ? _cursorColor : foregroundColor);
00573             
00574                 if ( !_cursorColor.isValid() )
00575                 {
00576                     // invert the colour used to draw the text to ensure that the character at
00577                     // the cursor position is readable
00578                     invertCharacterColor = true;
00579                 }
00580             }
00581        }
00582        else if ( _cursorShape == UnderlineCursor )
00583             painter.drawLine(cursorRect.left(),
00584                              cursorRect.bottom(),
00585                              cursorRect.right(),
00586                              cursorRect.bottom());
00587        else if ( _cursorShape == IBeamCursor )
00588             painter.drawLine(cursorRect.left(),
00589                              cursorRect.top(),
00590                              cursorRect.left(),
00591                              cursorRect.bottom());
00592     
00593     }
00594 }
00595 
00596 void TerminalDisplay::drawCharacters(QPainter& painter,
00597                                      const QRect& rect,
00598                                      const QString& text,
00599                                      const Character* style,
00600                                      bool invertCharacterColor)
00601 {
00602     // don't draw text which is currently blinking
00603     if ( _blinking && (style->rendition & RE_BLINK) )
00604             return;
00605    
00606     // setup bold and underline
00607     bool useBold = style->rendition & RE_BOLD || style->isBold(_colorTable) || font().bold();
00608     bool useUnderline = style->rendition & RE_UNDERLINE || font().underline();
00609 
00610     QFont font = painter.font();
00611     if (    font.bold() != useBold 
00612          || font.underline() != useUnderline )
00613     {
00614        font.setBold(useBold);
00615        font.setUnderline(useUnderline);
00616        painter.setFont(font);
00617     }
00618 
00619     // setup pen
00620     const CharacterColor& textColor = ( invertCharacterColor ? style->backgroundColor : style->foregroundColor );
00621     const QColor color = textColor.color(_colorTable);
00622     QPen pen = painter.pen();
00623     if ( pen.color() != color )
00624     {
00625         pen.setColor(color);
00626         painter.setPen(color);
00627     }
00628 
00629     // draw text
00630     if ( isLineCharString(text) )
00631         drawLineCharString(painter,rect.x(),rect.y(),text,style);
00632     else
00633     {
00634         // the drawText(rect,flags,string) overload is used here with null flags
00635         // instead of drawText(rect,string) because the (rect,string) overload causes 
00636         // the application's default layout direction to be used instead of 
00637         // the widget-specific layout direction, which should always be
00638         // Qt::LeftToRight for this widget
00639     // This was discussed in: http://lists.kde.org/?t=120552223600002&r=1&w=2
00640         if (_bidiEnabled)
00641             painter.drawText(rect,0,text);
00642         else
00643             painter.drawText(rect,0,LTR_OVERRIDE_CHAR+text);
00644     }
00645 }
00646 
00647 void TerminalDisplay::drawTextFragment(QPainter& painter , 
00648                                        const QRect& rect,
00649                                        const QString& text, 
00650                                        const Character* style)
00651 {
00652     painter.save();
00653 
00654     // setup painter 
00655     const QColor foregroundColor = style->foregroundColor.color(_colorTable);
00656     const QColor backgroundColor = style->backgroundColor.color(_colorTable);
00657     
00658     // draw background if different from the display's background color
00659     if ( backgroundColor != palette().background().color() )
00660         drawBackground(painter,rect,backgroundColor,
00661                        false /* do not use transparency */);
00662 
00663     // draw cursor shape if the current character is the cursor
00664     // this may alter the foreground and background colors
00665     bool invertCharacterColor = false;
00666     if ( style->rendition & RE_CURSOR )
00667         drawCursor(painter,rect,foregroundColor,backgroundColor,invertCharacterColor);
00668 
00669     // draw text
00670     drawCharacters(painter,rect,text,style,invertCharacterColor);
00671 
00672     painter.restore();
00673 }
00674 
00675 void TerminalDisplay::setRandomSeed(uint randomSeed) { _randomSeed = randomSeed; }
00676 uint TerminalDisplay::randomSeed() const { return _randomSeed; }
00677 
00678 #if 0
00679 
00682 void TerminalDisplay::setCursorPos(const int curx, const int cury)
00683 {
00684     QPoint tL  = contentsRect().topLeft();
00685     int    tLx = tL.x();
00686     int    tLy = tL.y();
00687 
00688     int xpos, ypos;
00689     ypos = _topMargin + tLy + _fontHeight*(cury-1) + _fontAscent;
00690     xpos = _leftMargin + tLx + _fontWidth*curx;
00691     //setMicroFocusHint(xpos, ypos, 0, _fontHeight); //### ???
00692     // fprintf(stderr, "x/y = %d/%d\txpos/ypos = %d/%d\n", curx, cury, xpos, ypos);
00693     _cursorLine = cury;
00694     _cursorCol = curx;
00695 }
00696 #endif
00697 
00698 // scrolls the image by 'lines', down if lines > 0 or up otherwise.
00699 //
00700 // the terminal emulation keeps track of the scrolling of the character 
00701 // image as it receives input, and when the view is updated, it calls scrollImage() 
00702 // with the final scroll amount.  this improves performance because scrolling the 
00703 // display is much cheaper than re-rendering all the text for the 
00704 // part of the image which has moved up or down.  
00705 // Instead only new lines have to be drawn
00706 void TerminalDisplay::scrollImage(int lines , const QRect& screenWindowRegion)
00707 {
00708     // if the flow control warning is enabled this will interfere with the 
00709     // scrolling optimisations and cause artifacts.  the simple solution here
00710     // is to just disable the optimisation whilst it is visible
00711     if ( _outputSuspendedLabel && _outputSuspendedLabel->isVisible() )
00712         return;
00713 
00714     // constrain the region to the display
00715     // the bottom of the region is capped to the number of lines in the display's
00716     // internal image - 2, so that the height of 'region' is strictly less
00717     // than the height of the internal image.
00718     QRect region = screenWindowRegion;
00719     region.setBottom( qMin(region.bottom(),this->_lines-2) ); 
00720 
00721     // return if there is nothing to do
00722     if (    lines == 0 
00723          || _image == 0
00724          || !region.isValid() 
00725          || (region.top() + abs(lines)) >= region.bottom() 
00726          || this->_lines <= region.height() ) return;
00727 
00728     // hide terminal size label to prevent it being scrolled
00729     if (_resizeWidget && _resizeWidget->isVisible())
00730         _resizeWidget->hide();
00731 
00732     // Note:  With Qt 4.4 the left edge of the scrolled area must be at 0
00733     // to get the correct (newly exposed) part of the widget repainted.
00734     //
00735     // The right edge must be before the the left edge of the scroll bar to 
00736     // avoid triggering a repaint of the entire widget, the distance is 
00737     // given by SCROLLBAR_CONTENT_GAP
00738     //
00739     // Set the QT_FLUSH_PAINT environment variable to '1' before starting the
00740     // application to monitor repainting.
00741     //
00742     int scrollBarWidth = _scrollBar->isHidden() ? 0 : _scrollBar->width();
00743     const int SCROLLBAR_CONTENT_GAP = 1;
00744     QRect scrollRect;
00745     if ( _scrollbarLocation == ScrollBarLeft )
00746     {
00747         scrollRect.setLeft(scrollBarWidth+SCROLLBAR_CONTENT_GAP);
00748         scrollRect.setRight(width());
00749     }
00750     else
00751     {
00752         scrollRect.setLeft(0);
00753         scrollRect.setRight(width() - scrollBarWidth - SCROLLBAR_CONTENT_GAP);
00754     }
00755     void* firstCharPos = &_image[ region.top() * this->_columns ];
00756     void* lastCharPos = &_image[ (region.top() + abs(lines)) * this->_columns ];
00757 
00758     int top = _topMargin + (region.top() * _fontHeight);
00759     int linesToMove = region.height() - abs(lines);
00760     int bytesToMove = linesToMove * 
00761                       this->_columns *
00762                       sizeof(Character);
00763 
00764     Q_ASSERT( linesToMove > 0 );
00765     Q_ASSERT( bytesToMove > 0 );
00766 
00767     //scroll internal image
00768     if ( lines > 0 )
00769     {
00770         // check that the memory areas that we are going to move are valid
00771         Q_ASSERT( (char*)lastCharPos + bytesToMove < 
00772                   (char*)(_image + (this->_lines * this->_columns)) );
00773         
00774         Q_ASSERT( (lines*this->_columns) < _imageSize ); 
00775 
00776         //scroll internal image down
00777         memmove( firstCharPos , lastCharPos , bytesToMove ); 
00778       
00779         //set region of display to scroll
00780         scrollRect.setTop(top);
00781     }
00782     else
00783     {
00784         // check that the memory areas that we are going to move are valid
00785         Q_ASSERT( (char*)firstCharPos + bytesToMove < 
00786                   (char*)(_image + (this->_lines * this->_columns)) );
00787 
00788         //scroll internal image up
00789         memmove( lastCharPos , firstCharPos , bytesToMove ); 
00790      
00791         //set region of the display to scroll
00792         scrollRect.setTop(top + abs(lines) * _fontHeight); 
00793     }
00794     scrollRect.setHeight(linesToMove * _fontHeight );
00795 
00796     Q_ASSERT(scrollRect.isValid() && !scrollRect.isEmpty());
00797 
00798     //scroll the display vertically to match internal _image
00799     scroll( 0 , _fontHeight * (-lines) , scrollRect );
00800 }
00801 
00802 QRegion TerminalDisplay::hotSpotRegion() const 
00803 {
00804     QRegion region;
00805     foreach( Filter::HotSpot* hotSpot , _filterChain->hotSpots() )
00806     {
00807         QRect rect;
00808         rect.setLeft(hotSpot->startColumn());
00809         rect.setTop(hotSpot->startLine());
00810         rect.setRight(hotSpot->endColumn());
00811         rect.setBottom(hotSpot->endLine());
00812 
00813         region |= imageToWidget(rect); 
00814     }
00815     return region;
00816 }
00817 
00818 void TerminalDisplay::processFilters() 
00819 {
00820     if (!_screenWindow)
00821         return;
00822 
00823     QRegion preUpdateHotSpots = hotSpotRegion();
00824 
00825     // use _screenWindow->getImage() here rather than _image because
00826     // other classes may call processFilters() when this display's
00827     // ScreenWindow emits a scrolled() signal - which will happen before
00828     // updateImage() is called on the display and therefore _image is 
00829     // out of date at this point
00830     _filterChain->setImage( _screenWindow->getImage(),
00831                             _screenWindow->windowLines(),
00832                             _screenWindow->windowColumns(),
00833                             _screenWindow->getLineProperties() );
00834     _filterChain->process();
00835 
00836     QRegion postUpdateHotSpots = hotSpotRegion();
00837 
00838     update( preUpdateHotSpots | postUpdateHotSpots );
00839 }
00840 
00841 void TerminalDisplay::updateImage() 
00842 {
00843   if ( !_screenWindow )
00844       return;
00845 
00846   // optimization - scroll the existing image where possible and 
00847   // avoid expensive text drawing for parts of the image that 
00848   // can simply be moved up or down
00849   scrollImage( _screenWindow->scrollCount() ,
00850                _screenWindow->scrollRegion() );
00851   _screenWindow->resetScrollCount();
00852 
00853   Character* const newimg = _screenWindow->getImage();
00854   int lines = _screenWindow->windowLines();
00855   int columns = _screenWindow->windowColumns();
00856 
00857   setScroll( _screenWindow->currentLine() , _screenWindow->lineCount() );
00858 
00859   if (!_image)
00860      updateImageSize(); // Create _image
00861 
00862   Q_ASSERT( this->_usedLines <= this->_lines );
00863   Q_ASSERT( this->_usedColumns <= this->_columns );
00864 
00865   int y,x,len;
00866 
00867   QPoint tL  = contentsRect().topLeft();
00868   int    tLx = tL.x();
00869   int    tLy = tL.y();
00870   _hasBlinker = false;
00871 
00872   CharacterColor cf;       // undefined
00873   CharacterColor _clipboard;       // undefined
00874   int cr  = -1;   // undefined
00875 
00876   const int linesToUpdate = qMin(this->_lines, qMax(0,lines  ));
00877   const int columnsToUpdate = qMin(this->_columns,qMax(0,columns));
00878 
00879   QChar *disstrU = new QChar[columnsToUpdate];
00880   char *dirtyMask = new char[columnsToUpdate+2]; 
00881   QRegion dirtyRegion;
00882 
00883   // debugging variable, this records the number of lines that are found to
00884   // be 'dirty' ( ie. have changed from the old _image to the new _image ) and
00885   // which therefore need to be repainted
00886   int dirtyLineCount = 0;
00887 
00888   for (y = 0; y < linesToUpdate; y++)
00889   {
00890     const Character*       currentLine = &_image[y*this->_columns];
00891     const Character* const newLine = &newimg[y*columns];
00892 
00893     bool updateLine = false;
00894     
00895     // The dirty mask indicates which characters need repainting. We also
00896     // mark surrounding neighbours dirty, in case the character exceeds
00897     // its cell boundaries
00898     memset(dirtyMask, 0, columnsToUpdate+2);
00899    
00900     for( x = 0 ; x < columnsToUpdate ; x++)
00901     {
00902         if ( newLine[x] != currentLine[x] ) 
00903         {
00904             dirtyMask[x] = true;
00905         }
00906     }
00907 
00908     if (!_resizing) // not while _resizing, we're expecting a paintEvent
00909     for (x = 0; x < columnsToUpdate; x++)
00910     {
00911       _hasBlinker |= (newLine[x].rendition & RE_BLINK);
00912     
00913       // Start drawing if this character or the next one differs.
00914       // We also take the next one into account to handle the situation
00915       // where characters exceed their cell width.
00916       if (dirtyMask[x])
00917       {
00918         quint16 c = newLine[x+0].character;
00919         if ( !c )
00920             continue;
00921         int p = 0;
00922         disstrU[p++] = c; //fontMap(c);
00923         bool lineDraw = isLineChar(c);
00924         bool doubleWidth = (x+1 == columnsToUpdate) ? false : (newLine[x+1].character == 0);
00925         cr = newLine[x].rendition;
00926         _clipboard = newLine[x].backgroundColor;
00927         if (newLine[x].foregroundColor != cf) cf = newLine[x].foregroundColor;
00928         int lln = columnsToUpdate - x;
00929         for (len = 1; len < lln; len++)
00930         {
00931             const Character& ch = newLine[x+len];
00932 
00933             if (!ch.character)
00934                 continue; // Skip trailing part of multi-col chars.
00935 
00936             bool nextIsDoubleWidth = (x+len+1 == columnsToUpdate) ? false : (newLine[x+len+1].character == 0);
00937 
00938             if (  ch.foregroundColor != cf || 
00939                   ch.backgroundColor != _clipboard || 
00940                   ch.rendition != cr ||
00941                   !dirtyMask[x+len] || 
00942                   isLineChar(c) != lineDraw || 
00943                   nextIsDoubleWidth != doubleWidth )
00944             break;
00945 
00946           disstrU[p++] = c; //fontMap(c);
00947         }
00948 
00949         QString unistr(disstrU, p);
00950 
00951         bool saveFixedFont = _fixedFont;
00952         if (lineDraw)
00953            _fixedFont = false;
00954         if (doubleWidth)
00955            _fixedFont = false;
00956 
00957         updateLine = true;
00958 
00959         _fixedFont = saveFixedFont;
00960         x += len - 1;
00961       }
00962       
00963     }
00964 
00965     //both the top and bottom halves of double height _lines must always be redrawn
00966     //although both top and bottom halves contain the same characters, only 
00967     //the top one is actually 
00968     //drawn.
00969     if (_lineProperties.count() > y)
00970         updateLine |= (_lineProperties[y] & LINE_DOUBLEHEIGHT);
00971 
00972     // if the characters on the line are different in the old and the new _image
00973     // then this line must be repainted.    
00974     if (updateLine)
00975     {
00976         dirtyLineCount++;
00977 
00978         // add the area occupied by this line to the region which needs to be
00979         // repainted
00980         QRect dirtyRect = QRect( _leftMargin+tLx , 
00981                                  _topMargin+tLy+_fontHeight*y , 
00982                                  _fontWidth * columnsToUpdate , 
00983                                  _fontHeight );     
00984 
00985         dirtyRegion |= dirtyRect;
00986     }
00987 
00988     // replace the line of characters in the old _image with the 
00989     // current line of the new _image 
00990     memcpy((void*)currentLine,(const void*)newLine,columnsToUpdate*sizeof(Character));
00991   }
00992 
00993   // if the new _image is smaller than the previous _image, then ensure that the area
00994   // outside the new _image is cleared 
00995   if ( linesToUpdate < _usedLines )
00996   {
00997     dirtyRegion |= QRect(   _leftMargin+tLx , 
00998                             _topMargin+tLy+_fontHeight*linesToUpdate , 
00999                             _fontWidth * this->_columns , 
01000                             _fontHeight * (_usedLines-linesToUpdate) );
01001   }
01002   _usedLines = linesToUpdate;
01003   
01004   if ( columnsToUpdate < _usedColumns )
01005   {
01006     dirtyRegion |= QRect(   _leftMargin+tLx+columnsToUpdate*_fontWidth , 
01007                             _topMargin+tLy , 
01008                             _fontWidth * (_usedColumns-columnsToUpdate) , 
01009                             _fontHeight * this->_lines );
01010   }
01011   _usedColumns = columnsToUpdate;
01012 
01013   dirtyRegion |= _inputMethodData.previousPreeditRect;
01014 
01015   // update the parts of the display which have changed
01016   update(dirtyRegion);
01017 
01018   if ( _hasBlinker && !_blinkTimer->isActive()) _blinkTimer->start( BLINK_DELAY ); 
01019   if (!_hasBlinker && _blinkTimer->isActive()) { _blinkTimer->stop(); _blinking = false; }
01020   delete[] dirtyMask;
01021   delete[] disstrU;
01022 
01023 }
01024 
01025 void TerminalDisplay::showResizeNotification()
01026 {
01027   if (_terminalSizeHint && isVisible())
01028   {
01029      if (_terminalSizeStartup) {
01030             _terminalSizeStartup=false;
01031        return;
01032      }
01033      if (!_resizeWidget)
01034      {
01035         _resizeWidget = new QLabel(i18n("Size: XXX x XXX"), this);
01036         _resizeWidget->setMinimumWidth(_resizeWidget->fontMetrics().width(i18n("Size: XXX x XXX")));
01037         _resizeWidget->setMinimumHeight(_resizeWidget->sizeHint().height());
01038         _resizeWidget->setAlignment(Qt::AlignCenter);
01039 
01040         _resizeWidget->setStyleSheet("background-color:palette(window);border-style:solid;border-width:1px;border-color:palette(dark)");
01041 
01042         _resizeTimer = new QTimer(this);
01043         _resizeTimer->setSingleShot(true);
01044         connect(_resizeTimer, SIGNAL(timeout()), _resizeWidget, SLOT(hide()));
01045      }
01046      QString sizeStr = i18n("Size: %1 x %2", _columns, _lines);
01047      _resizeWidget->setText(sizeStr);
01048      _resizeWidget->move((width()-_resizeWidget->width())/2,
01049                          (height()-_resizeWidget->height())/2+20);
01050      _resizeWidget->show();
01051      _resizeTimer->start(1000);
01052   }
01053 }
01054 
01055 void TerminalDisplay::setBlinkingCursor(bool blink)
01056 {
01057   _hasBlinkingCursor=blink;
01058   
01059   if (blink && !_blinkCursorTimer->isActive()) 
01060       _blinkCursorTimer->start(BLINK_DELAY);
01061   
01062   if (!blink && _blinkCursorTimer->isActive()) 
01063   {
01064     _blinkCursorTimer->stop();
01065     if (_cursorBlinking)
01066       blinkCursorEvent();
01067     else
01068       _cursorBlinking = false;
01069   }
01070 }
01071 
01072 void TerminalDisplay::focusOutEvent(QFocusEvent*)
01073 {
01074     // trigger a repaint of the cursor so that it is both visible (in case
01075     // it was hidden during blinking)
01076     // and drawn in a focused out state
01077     _cursorBlinking = false;
01078     updateCursor();
01079 
01080     _blinkCursorTimer->stop();
01081     if (_blinking)
01082         blinkEvent();
01083 
01084     _blinkTimer->stop();
01085 }
01086 void TerminalDisplay::focusInEvent(QFocusEvent*)
01087 {
01088     if (_hasBlinkingCursor)
01089     {
01090         _blinkCursorTimer->start();
01091     }
01092     updateCursor();
01093 
01094     if (_hasBlinker)
01095         _blinkTimer->start();
01096 }
01097 
01098 void TerminalDisplay::paintEvent( QPaintEvent* pe )
01099 {
01100   QPainter paint(this);
01101 
01102   foreach (QRect rect, (pe->region() & contentsRect()).rects())
01103   {
01104     drawBackground(paint,rect,palette().background().color(),
01105                     true /* use opacity setting */);
01106     drawContents(paint, rect);
01107   }
01108   drawInputMethodPreeditString(paint,preeditRect());
01109   paintFilters(paint);
01110 }
01111 
01112 QPoint TerminalDisplay::cursorPosition() const
01113 {
01114     if (_screenWindow)
01115         return _screenWindow->cursorPosition();
01116     else
01117         return QPoint(0,0);
01118 }
01119 
01120 QRect TerminalDisplay::preeditRect() const
01121 {
01122     const int preeditLength = string_width(_inputMethodData.preeditString);
01123 
01124     if ( preeditLength == 0 )
01125         return QRect();
01126 
01127     return QRect(_leftMargin + _fontWidth*cursorPosition().x(),
01128                  _topMargin + _fontHeight*cursorPosition().y(),
01129                  _fontWidth*preeditLength,
01130                  _fontHeight);
01131 }   
01132 
01133 void TerminalDisplay::drawInputMethodPreeditString(QPainter& painter , const QRect& rect)
01134 {
01135     if ( _inputMethodData.preeditString.isEmpty() )
01136         return;
01137 
01138     const QPoint cursorPos = cursorPosition(); 
01139 
01140     bool invertColors = false;
01141     const QColor background = _colorTable[DEFAULT_BACK_COLOR].color;
01142     const QColor foreground = _colorTable[DEFAULT_FORE_COLOR].color;
01143     const Character* style = &_image[loc(cursorPos.x(),cursorPos.y())];
01144 
01145     drawBackground(painter,rect,background,true);
01146     drawCursor(painter,rect,foreground,background,invertColors);
01147     drawCharacters(painter,rect,_inputMethodData.preeditString,style,invertColors);
01148 
01149     _inputMethodData.previousPreeditRect = rect; 
01150 }
01151 
01152 FilterChain* TerminalDisplay::filterChain() const
01153 {
01154     return _filterChain;
01155 }
01156 
01157 void TerminalDisplay::paintFilters(QPainter& painter)
01158 {
01159     // get color of character under mouse and use it to draw
01160     // lines for filters
01161     QPoint cursorPos = mapFromGlobal(QCursor::pos());
01162     int cursorLine;
01163     int cursorColumn;
01164     getCharacterPosition( cursorPos , cursorLine , cursorColumn );
01165     Character cursorCharacter = _image[loc(cursorColumn,cursorLine)];
01166 
01167     painter.setPen( QPen(cursorCharacter.foregroundColor.color(colorTable())) );
01168 
01169     // iterate over hotspots identified by the display's currently active filters 
01170     // and draw appropriate visuals to indicate the presence of the hotspot
01171 
01172     QList<Filter::HotSpot*> spots = _filterChain->hotSpots();
01173     QListIterator<Filter::HotSpot*> iter(spots);
01174     while (iter.hasNext())
01175     {
01176         Filter::HotSpot* spot = iter.next();
01177 
01178         for ( int line = spot->startLine() ; line <= spot->endLine() ; line++ )
01179         {
01180             int startColumn = 0;
01181             int endColumn = _columns-1; // TODO use number of _columns which are actually 
01182                                         // occupied on this line rather than the width of the 
01183                                         // display in _columns
01184 
01185             // ignore whitespace at the end of the lines
01186             while ( QChar(_image[loc(endColumn,line)].character).isSpace() && endColumn > 0 )
01187                 endColumn--;
01188               
01189             // increment here because the column which we want to set 'endColumn' to
01190             // is the first whitespace character at the end of the line
01191             endColumn++;
01192 
01193             if ( line == spot->startLine() )
01194                 startColumn = spot->startColumn();
01195             if ( line == spot->endLine() )
01196                 endColumn = spot->endColumn();
01197 
01198             // subtract one pixel from
01199             // the right and bottom so that
01200             // we do not overdraw adjacent
01201             // hotspots
01202             //
01203             // subtracting one pixel from all sides also prevents an edge case where
01204             // moving the mouse outside a link could still leave it underlined 
01205             // because the check below for the position of the cursor
01206             // finds it on the border of the target area
01207             QRect r;
01208             r.setCoords( startColumn*_fontWidth + 1, line*_fontHeight + 1,
01209                              endColumn*_fontWidth - 1, (line+1)*_fontHeight - 1 ); 
01210                                                                            
01211             // Underline link hotspots 
01212             if ( spot->type() == Filter::HotSpot::Link )
01213             {
01214                 QFontMetrics metrics(font());
01215         
01216                 // find the baseline (which is the invisible line that the characters in the font sit on,
01217                 // with some having tails dangling below)
01218                 int baseline = r.bottom() - metrics.descent();
01219                 // find the position of the underline below that
01220                 int underlinePos = baseline + metrics.underlinePos();
01221 
01222                 if ( r.contains( mapFromGlobal(QCursor::pos()) ) )
01223                     painter.drawLine( r.left() , underlinePos , 
01224                                       r.right() , underlinePos );
01225             }
01226             // Marker hotspots simply have a transparent rectanglular shape
01227             // drawn on top of them
01228             else if ( spot->type() == Filter::HotSpot::Marker )
01229             {
01230             //TODO - Do not use a hardcoded colour for this
01231                 painter.fillRect(r,QBrush(QColor(255,0,0,120)));
01232             }
01233         }
01234     }
01235 }
01236 void TerminalDisplay::drawContents(QPainter &paint, const QRect &rect)
01237 {
01238   QPoint tL  = contentsRect().topLeft();
01239   int    tLx = tL.x();
01240   int    tLy = tL.y();
01241 
01242   int lux = qMin(_usedColumns-1, qMax(0,(rect.left()   - tLx - _leftMargin ) / _fontWidth));
01243   int luy = qMin(_usedLines-1,  qMax(0,(rect.top()    - tLy - _topMargin  ) / _fontHeight));
01244   int rlx = qMin(_usedColumns-1, qMax(0,(rect.right()  - tLx - _leftMargin ) / _fontWidth));
01245   int rly = qMin(_usedLines-1,  qMax(0,(rect.bottom() - tLy - _topMargin  ) / _fontHeight));
01246 
01247   const int bufferSize = _usedColumns;
01248   QChar *disstrU = new QChar[bufferSize];
01249   for (int y = luy; y <= rly; y++)
01250   {
01251     quint16 c = _image[loc(lux,y)].character;
01252     int x = lux;
01253     if(!c && x)
01254       x--; // Search for start of multi-column character
01255     for (; x <= rlx; x++)
01256     {
01257       int len = 1;
01258       int p = 0;
01259 
01260       // is this a single character or a sequence of characters ?
01261       if ( _image[loc(x,y)].rendition & RE_EXTENDED_CHAR )
01262       {
01263         // sequence of characters
01264         ushort extendedCharLength = 0;
01265         ushort* chars = ExtendedCharTable::instance
01266                             .lookupExtendedChar(_image[loc(x,y)].charSequence,extendedCharLength);
01267         for ( int index = 0 ; index < extendedCharLength ; index++ ) 
01268         {
01269             Q_ASSERT( p < bufferSize );
01270             disstrU[p++] = chars[index];
01271         }
01272       }
01273       else
01274       {
01275         // single character
01276         c = _image[loc(x,y)].character;
01277         if (c)
01278         {
01279              Q_ASSERT( p < bufferSize );
01280              disstrU[p++] = c; //fontMap(c);
01281         }
01282       }
01283 
01284       bool lineDraw = isLineChar(c);
01285       bool doubleWidth = (_image[ qMin(loc(x,y)+1,_imageSize) ].character == 0);
01286       CharacterColor currentForeground = _image[loc(x,y)].foregroundColor;
01287       CharacterColor currentBackground = _image[loc(x,y)].backgroundColor;
01288       quint8 currentRendition = _image[loc(x,y)].rendition;
01289       
01290       while (x+len <= rlx &&
01291              _image[loc(x+len,y)].foregroundColor == currentForeground &&
01292              _image[loc(x+len,y)].backgroundColor == currentBackground &&
01293              _image[loc(x+len,y)].rendition == currentRendition &&
01294              (_image[ qMin(loc(x+len,y)+1,_imageSize) ].character == 0) == doubleWidth &&
01295              isLineChar( c = _image[loc(x+len,y)].character) == lineDraw) // Assignment!
01296       {
01297         if (c)
01298           disstrU[p++] = c; //fontMap(c);
01299         if (doubleWidth) // assert((_image[loc(x+len,y)+1].character == 0)), see above if condition
01300           len++; // Skip trailing part of multi-column character
01301         len++;
01302       }
01303       if ((x+len < _usedColumns) && (!_image[loc(x+len,y)].character))
01304         len++; // Adjust for trailing part of multi-column character
01305 
01306          bool save__fixedFont = _fixedFont;
01307          if (lineDraw)
01308             _fixedFont = false;
01309          if (doubleWidth)
01310             _fixedFont = false;
01311          QString unistr(disstrU,p);
01312          
01313          if (y < _lineProperties.size())
01314          {
01315             if (_lineProperties[y] & LINE_DOUBLEWIDTH)
01316                 paint.scale(2,1);
01317             
01318             if (_lineProperties[y] & LINE_DOUBLEHEIGHT)
01319                 paint.scale(1,2);
01320          }
01321 
01322          //calculate the area in which the text will be drawn
01323          QRect textArea = QRect( _leftMargin+tLx+_fontWidth*x , _topMargin+tLy+_fontHeight*y , _fontWidth*len , _fontHeight);
01324         
01325          //move the calculated area to take account of scaling applied to the painter.
01326          //the position of the area from the origin (0,0) is scaled 
01327          //by the opposite of whatever
01328          //transformation has been applied to the painter.  this ensures that 
01329          //painting does actually start from textArea.topLeft() 
01330          //(instead of textArea.topLeft() * painter-scale)  
01331          QTransform inverted = paint.worldTransform().inverted();
01332          textArea.moveTopLeft( inverted.map(textArea.topLeft()) );
01333          
01334          //paint text fragment
01335          drawTextFragment(  paint,
01336                             textArea,
01337                             unistr, 
01338                             &_image[loc(x,y)] ); //, 
01339                             //0, 
01341          
01342          _fixedFont = save__fixedFont;
01343      
01344          //reset back to single-width, single-height _lines 
01345          paint.resetMatrix();
01346 
01347          if (y < _lineProperties.size()-1)
01348          {
01349             //double-height _lines are represented by two adjacent _lines 
01350             //containing the same characters
01351             //both _lines will have the LINE_DOUBLEHEIGHT attribute.  
01352             //If the current line has the LINE_DOUBLEHEIGHT attribute, 
01353             //we can therefore skip the next line
01354             if (_lineProperties[y] & LINE_DOUBLEHEIGHT)
01355                 y++;
01356          }
01357          
01358         x += len - 1;
01359     }
01360   }
01361   delete [] disstrU;
01362 }
01363 
01364 void TerminalDisplay::blinkEvent()
01365 {
01366   _blinking = !_blinking;
01367 
01368   //TODO:  Optimise to only repaint the areas of the widget 
01369   // where there is blinking text
01370   // rather than repainting the whole widget.
01371   update();
01372 }
01373 
01374 QRect TerminalDisplay::imageToWidget(const QRect& imageArea) const
01375 {
01376     QRect result;
01377     result.setLeft( _leftMargin + _fontWidth * imageArea.left() );
01378     result.setTop( _topMargin + _fontHeight * imageArea.top() );
01379     result.setWidth( _fontWidth * imageArea.width() );
01380     result.setHeight( _fontHeight * imageArea.height() );
01381 
01382     return result;
01383 }
01384 
01385 void TerminalDisplay::updateCursor()
01386 {
01387   QRect cursorRect = imageToWidget( QRect(cursorPosition(),QSize(1,1)) ); 
01388   update(cursorRect);
01389 }
01390 
01391 void TerminalDisplay::blinkCursorEvent()
01392 {
01393   _cursorBlinking = !_cursorBlinking;
01394   updateCursor();
01395 }
01396 
01397 /* ------------------------------------------------------------------------- */
01398 /*                                                                           */
01399 /*                                  Resizing                                 */
01400 /*                                                                           */
01401 /* ------------------------------------------------------------------------- */
01402 
01403 void TerminalDisplay::resizeEvent(QResizeEvent*)
01404 {
01405   updateImageSize();
01406 }
01407 
01408 void TerminalDisplay::propagateSize()
01409 {
01410   if (_isFixedSize)
01411   {
01412      setSize(_columns, _lines);
01413      QWidget::setFixedSize(sizeHint());
01414      parentWidget()->adjustSize();
01415      parentWidget()->setFixedSize(parentWidget()->sizeHint());
01416      return;
01417   }
01418   if (_image)
01419      updateImageSize();
01420 }
01421 
01422 void TerminalDisplay::updateImageSize()
01423 {
01424   Character* oldimg = _image;
01425   int oldlin = _lines;
01426   int oldcol = _columns;
01427 
01428   makeImage();
01429   
01430   // copy the old image to reduce flicker
01431   int lines = qMin(oldlin,_lines);
01432   int columns = qMin(oldcol,_columns);
01433 
01434   if (oldimg)
01435   {
01436     for (int line = 0; line < lines; line++) 
01437     {
01438       memcpy((void*)&_image[_columns*line],
01439              (void*)&oldimg[oldcol*line],columns*sizeof(Character));
01440     }
01441     delete[] oldimg;
01442   }
01443 
01444   if (_screenWindow)
01445     _screenWindow->setWindowLines(_lines);
01446 
01447   _resizing = (oldlin!=_lines) || (oldcol!=_columns);
01448 
01449   if ( _resizing )
01450   {
01451     showResizeNotification();
01452     emit changedContentSizeSignal(_contentHeight, _contentWidth); // expose resizeEvent
01453   }
01454   
01455   _resizing = false;
01456 }
01457 
01458 //showEvent and hideEvent are reimplemented here so that it appears to other classes that the 
01459 //display has been resized when the display is hidden or shown.
01460 //
01461 //this allows  
01462 //TODO: Perhaps it would be better to have separate signals for show and hide instead of using
01463 //the same signal as the one for a content size change 
01464 void TerminalDisplay::showEvent(QShowEvent*)
01465 {
01466     emit changedContentSizeSignal(_contentHeight,_contentWidth);
01467 }
01468 void TerminalDisplay::hideEvent(QHideEvent*)
01469 {
01470     emit changedContentSizeSignal(_contentHeight,_contentWidth);
01471 }
01472 
01473 /* ------------------------------------------------------------------------- */
01474 /*                                                                           */
01475 /*                                Scrollbar                                  */
01476 /*                                                                           */
01477 /* ------------------------------------------------------------------------- */
01478 
01479 void TerminalDisplay::scrollBarPositionChanged(int)
01480 {
01481   if ( !_screenWindow ) 
01482       return;
01483 
01484   _screenWindow->scrollTo( _scrollBar->value() );
01485 
01486   // if the thumb has been moved to the bottom of the _scrollBar then set
01487   // the display to automatically track new output, 
01488   // that is, scroll down automatically
01489   // to how new _lines as they are added
01490   const bool atEndOfOutput = (_scrollBar->value() == _scrollBar->maximum());
01491   _screenWindow->setTrackOutput( atEndOfOutput );
01492 
01493   updateImage();
01494 }
01495 
01496 void TerminalDisplay::setScroll(int cursor, int slines)
01497 {
01498   // update _scrollBar if the range or value has changed,
01499   // otherwise return
01500   //
01501   // setting the range or value of a _scrollBar will always trigger
01502   // a repaint, so it should be avoided if it is not necessary
01503   if ( _scrollBar->minimum() == 0                 &&
01504        _scrollBar->maximum() == (slines - _lines) &&
01505        _scrollBar->value()   == cursor )
01506   {
01507         return;
01508   }
01509 
01510   disconnect(_scrollBar, SIGNAL(valueChanged(int)), this, SLOT(scrollBarPositionChanged(int)));
01511   _scrollBar->setRange(0,slines - _lines);
01512   _scrollBar->setSingleStep(1);
01513   _scrollBar->setPageStep(_lines);
01514   _scrollBar->setValue(cursor);
01515   connect(_scrollBar, SIGNAL(valueChanged(int)), this, SLOT(scrollBarPositionChanged(int)));
01516 }
01517 
01518 void TerminalDisplay::setScrollBarPosition(ScrollBarPosition position)
01519 {
01520   if (_scrollbarLocation == position) 
01521       return; 
01522  
01523   if ( position == NoScrollBar )
01524      _scrollBar->hide();
01525   else 
01526      _scrollBar->show(); 
01527 
01528   _topMargin = _leftMargin = 1;
01529   _scrollbarLocation = position;
01530   
01531   propagateSize();
01532   update();
01533 }
01534 
01535 void TerminalDisplay::mousePressEvent(QMouseEvent* ev)
01536 {
01537   if ( _possibleTripleClick && (ev->button()==Qt::LeftButton) ) {
01538     mouseTripleClickEvent(ev);
01539     return;
01540   }
01541 
01542   if ( !contentsRect().contains(ev->pos()) ) return;
01543   
01544   if ( !_screenWindow ) return;
01545 
01546   int charLine;
01547   int charColumn;
01548   getCharacterPosition(ev->pos(),charLine,charColumn);
01549   QPoint pos = QPoint(charColumn,charLine);
01550 
01551   if ( ev->button() == Qt::LeftButton)
01552   {
01553     _lineSelectionMode = false;
01554     _wordSelectionMode = false;
01555 
01556     emit isBusySelecting(true); // Keep it steady...
01557     // Drag only when the Control key is hold
01558     bool selected = false;
01559     
01560     // The receiver of the testIsSelected() signal will adjust
01561     // 'selected' accordingly.
01562     //emit testIsSelected(pos.x(), pos.y(), selected);
01563     
01564     selected =  _screenWindow->isSelected(pos.x(),pos.y());
01565 
01566     if ((!_ctrlDrag || ev->modifiers() & Qt::ControlModifier) && selected ) {
01567       // The user clicked inside selected text
01568       dragInfo.state = diPending;
01569       dragInfo.start = ev->pos();
01570     }
01571     else {
01572       // No reason to ever start a drag event
01573       dragInfo.state = diNone;
01574 
01575       _preserveLineBreaks = !( ( ev->modifiers() & Qt::ControlModifier ) && !(ev->modifiers() & Qt::AltModifier) );
01576       _columnSelectionMode = (ev->modifiers() & Qt::AltModifier) && (ev->modifiers() & Qt::ControlModifier);
01577 
01578       if (_mouseMarks || (ev->modifiers() & Qt::ShiftModifier))
01579       {
01580          _screenWindow->clearSelection();
01581 
01582         //emit clearSelectionSignal();
01583         pos.ry() += _scrollBar->value();
01584         _iPntSel = _pntSel = pos;
01585         _actSel = 1; // left mouse button pressed but nothing selected yet.
01586         
01587       }
01588       else
01589       {
01590         emit mouseSignal( 0, charColumn + 1, charLine + 1 +_scrollBar->value() -_scrollBar->maximum() , 0);
01591       }
01592     }
01593   }
01594   else if ( ev->button() == Qt::MidButton )
01595   {
01596     if ( _mouseMarks || (!_mouseMarks && (ev->modifiers() & Qt::ShiftModifier)) )
01597       emitSelection(true,ev->modifiers() & Qt::ControlModifier);
01598     else
01599       emit mouseSignal( 1, charColumn +1, charLine +1 +_scrollBar->value() -_scrollBar->maximum() , 0);
01600   }
01601   else if ( ev->button() == Qt::RightButton )
01602   {
01603     if (_mouseMarks || (ev->modifiers() & Qt::ShiftModifier)) 
01604     {
01605         emit configureRequest( this, 
01606                                ev->modifiers() & (Qt::ShiftModifier|Qt::ControlModifier), 
01607                                ev->pos()
01608                              );
01609     }
01610     else
01611       emit mouseSignal( 2, charColumn +1, charLine +1 +_scrollBar->value() -_scrollBar->maximum() , 0);
01612   }
01613 }
01614 
01615 QList<QAction*> TerminalDisplay::filterActions(const QPoint& position)
01616 {
01617   int charLine, charColumn;
01618   getCharacterPosition(position,charLine,charColumn);
01619 
01620   Filter::HotSpot* spot = _filterChain->hotSpotAt(charLine,charColumn);
01621 
01622   return spot ? spot->actions() : QList<QAction*>();
01623 }
01624 
01625 void TerminalDisplay::mouseMoveEvent(QMouseEvent* ev)
01626 {
01627   int charLine = 0;
01628   int charColumn = 0;
01629 
01630   getCharacterPosition(ev->pos(),charLine,charColumn); 
01631 
01632   // handle filters
01633   // change link hot-spot appearance on mouse-over
01634   Filter::HotSpot* spot = _filterChain->hotSpotAt(charLine,charColumn);
01635   if ( spot && spot->type() == Filter::HotSpot::Link)
01636   {
01637     QRect previousHotspotArea = _mouseOverHotspotArea;
01638     _mouseOverHotspotArea.setCoords( qMin(spot->startColumn() , spot->endColumn()) * _fontWidth,
01639                                      spot->startLine() * _fontHeight,
01640                                      qMax(spot->startColumn() , spot->endColumn()) * _fontHeight,
01641                                      (spot->endLine()+1) * _fontHeight );
01642 
01643     // display tooltips when mousing over links
01644     // TODO: Extend this to work with filter types other than links
01645     const QString& tooltip = spot->tooltip();
01646     if ( !tooltip.isEmpty() )
01647     {
01648         QToolTip::showText( mapToGlobal(ev->pos()) , tooltip , this , _mouseOverHotspotArea );
01649     }
01650 
01651     update( _mouseOverHotspotArea | previousHotspotArea );
01652   }
01653   else if ( _mouseOverHotspotArea.isValid() )
01654   {
01655         update( _mouseOverHotspotArea );
01656         // set hotspot area to an invalid rectangle
01657         _mouseOverHotspotArea = QRect();
01658   }
01659   
01660   // for auto-hiding the cursor, we need mouseTracking
01661   if (ev->buttons() == Qt::NoButton ) return;
01662 
01663   // if the terminal is interested in mouse movements 
01664   // then emit a mouse movement signal, unless the shift
01665   // key is being held down, which overrides this.
01666   if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier))
01667   {
01668     int button = 3;
01669     if (ev->buttons() & Qt::LeftButton)
01670         button = 0;
01671     if (ev->buttons() & Qt::MidButton)
01672         button = 1;
01673     if (ev->buttons() & Qt::RightButton)
01674         button = 2;
01675 
01676         
01677         emit mouseSignal( button, 
01678                         charColumn + 1,
01679                         charLine + 1 +_scrollBar->value() -_scrollBar->maximum(),
01680              1 );
01681       
01682     return;
01683   }
01684       
01685   if (dragInfo.state == diPending) 
01686   {
01687     // we had a mouse down, but haven't confirmed a drag yet
01688     // if the mouse has moved sufficiently, we will confirm
01689 
01690    int distance = KGlobalSettings::dndEventDelay();
01691    if ( ev->x() > dragInfo.start.x() + distance || ev->x() < dragInfo.start.x() - distance ||
01692         ev->y() > dragInfo.start.y() + distance || ev->y() < dragInfo.start.y() - distance) 
01693    {
01694       // we've left the drag square, we can start a real drag operation now
01695       emit isBusySelecting(false); // Ok.. we can breath again.
01696       
01697        _screenWindow->clearSelection();
01698       doDrag();
01699     }
01700     return;
01701   } 
01702   else if (dragInfo.state == diDragging) 
01703   {
01704     // this isn't technically needed because mouseMoveEvent is suppressed during
01705     // Qt drag operations, replaced by dragMoveEvent
01706     return;
01707   }
01708 
01709   if (_actSel == 0) return;
01710 
01711  // don't extend selection while pasting
01712   if (ev->buttons() & Qt::MidButton) return;
01713 
01714   extendSelection( ev->pos() );
01715 }
01716 
01717 void TerminalDisplay::extendSelection( const QPoint& position )
01718 {
01719   QPoint pos = position;
01720 
01721   if ( !_screenWindow )
01722       return;
01723 
01724   //if ( !contentsRect().contains(ev->pos()) ) return;
01725   QPoint tL  = contentsRect().topLeft();
01726   int    tLx = tL.x();
01727   int    tLy = tL.y();
01728   int    scroll = _scrollBar->value();
01729 
01730   // we're in the process of moving the mouse with the left button pressed
01731   // the mouse cursor will kept caught within the bounds of the text in
01732   // this widget.
01733 
01734   int linesBeyondWidget = 0;
01735 
01736   QRect textBounds(tLx + _leftMargin,
01737                    tLy + _topMargin,
01738                    _usedColumns*_fontWidth-1,
01739                    _usedLines*_fontHeight-1);
01740 
01741   // Adjust position within text area bounds.
01742   QPoint oldpos = pos;
01743  
01744   pos.setX( qBound(textBounds.left(),pos.x(),textBounds.right()) );
01745   pos.setY( qBound(textBounds.top(),pos.y(),textBounds.bottom()) );
01746 
01747   if ( oldpos.y() > textBounds.bottom() ) 
01748   {
01749     linesBeyondWidget = (oldpos.y()-textBounds.bottom()) / _fontHeight;
01750     _scrollBar->setValue(_scrollBar->value()+linesBeyondWidget+1); // scrollforward
01751   }
01752   if ( oldpos.y() < textBounds.top() )
01753   {
01754     linesBeyondWidget = (textBounds.top()-oldpos.y()) / _fontHeight;
01755     _scrollBar->setValue(_scrollBar->value()-linesBeyondWidget-1); // scrollback
01756   }
01757 
01758   int charColumn = 0;
01759   int charLine = 0;
01760   getCharacterPosition(pos,charLine,charColumn);
01761 
01762   QPoint here = QPoint(charColumn,charLine); //QPoint((pos.x()-tLx-_leftMargin+(_fontWidth/2))/_fontWidth,(pos.y()-tLy-_topMargin)/_fontHeight);
01763   QPoint ohere;
01764   QPoint _iPntSelCorr = _iPntSel;
01765   _iPntSelCorr.ry() -= _scrollBar->value();
01766   QPoint _pntSelCorr = _pntSel;
01767   _pntSelCorr.ry() -= _scrollBar->value();
01768   bool swapping = false;
01769 
01770   if ( _wordSelectionMode )
01771   {
01772     // Extend to word boundaries
01773     int i;
01774     QChar selClass;
01775 
01776     bool left_not_right = ( here.y() < _iPntSelCorr.y() ||
01777        ( here.y() == _iPntSelCorr.y() && here.x() < _iPntSelCorr.x() ) );
01778     bool old_left_not_right = ( _pntSelCorr.y() < _iPntSelCorr.y() ||
01779        ( _pntSelCorr.y() == _iPntSelCorr.y() && _pntSelCorr.x() < _iPntSelCorr.x() ) );
01780     swapping = left_not_right != old_left_not_right;
01781 
01782     // Find left (left_not_right ? from here : from start)
01783     QPoint left = left_not_right ? here : _iPntSelCorr;
01784     i = loc(left.x(),left.y());
01785     if (i>=0 && i<=_imageSize) {
01786       selClass = charClass(_image[i].character);
01787       while ( ((left.x()>0) || (left.y()>0 && (_lineProperties[left.y()-1] & LINE_WRAPPED) )) 
01788                       && charClass(_image[i-1].character) == selClass )
01789       { i--; if (left.x()>0) left.rx()--; else {left.rx()=_usedColumns-1; left.ry()--;} }
01790     }
01791 
01792     // Find left (left_not_right ? from start : from here)
01793     QPoint right = left_not_right ? _iPntSelCorr : here;
01794     i = loc(right.x(),right.y());
01795     if (i>=0 && i<=_imageSize) {
01796       selClass = charClass(_image[i].character);
01797       while( ((right.x()<_usedColumns-1) || (right.y()<_usedLines-1 && (_lineProperties[right.y()] & LINE_WRAPPED) )) 
01798                       && charClass(_image[i+1].character) == selClass )
01799       { i++; if (right.x()<_usedColumns-1) right.rx()++; else {right.rx()=0; right.ry()++; } }
01800     }
01801 
01802     // Pick which is start (ohere) and which is extension (here)
01803     if ( left_not_right )
01804     {
01805       here = left; ohere = right;
01806     }
01807     else
01808     {
01809       here = right; ohere = left;
01810     }
01811     ohere.rx()++;
01812   }
01813 
01814   if ( _lineSelectionMode )
01815   {
01816     // Extend to complete line
01817     bool above_not_below = ( here.y() < _iPntSelCorr.y() );
01818 
01819     QPoint above = above_not_below ? here : _iPntSelCorr;
01820     QPoint below = above_not_below ? _iPntSelCorr : here;
01821 
01822     while (above.y()>0 && (_lineProperties[above.y()-1] & LINE_WRAPPED) )
01823       above.ry()--;
01824     while (below.y()<_usedLines-1 && (_lineProperties[below.y()] & LINE_WRAPPED) )
01825       below.ry()++;
01826 
01827     above.setX(0);
01828     below.setX(_usedColumns-1);
01829 
01830     // Pick which is start (ohere) and which is extension (here)
01831     if ( above_not_below )
01832     {
01833       here = above; ohere = below;
01834     }
01835     else
01836     {
01837       here = below; ohere = above;
01838     }
01839 
01840     QPoint newSelBegin = QPoint( ohere.x(), ohere.y() );
01841     swapping = !(_tripleSelBegin==newSelBegin);
01842     _tripleSelBegin = newSelBegin;
01843 
01844     ohere.rx()++;
01845   }
01846 
01847   int offset = 0;
01848   if ( !_wordSelectionMode && !_lineSelectionMode )
01849   {
01850     int i;
01851     QChar selClass;
01852 
01853     bool left_not_right = ( here.y() < _iPntSelCorr.y() ||
01854        ( here.y() == _iPntSelCorr.y() && here.x() < _iPntSelCorr.x() ) );
01855     bool old_left_not_right = ( _pntSelCorr.y() < _iPntSelCorr.y() ||
01856        ( _pntSelCorr.y() == _iPntSelCorr.y() && _pntSelCorr.x() < _iPntSelCorr.x() ) );
01857     swapping = left_not_right != old_left_not_right;
01858 
01859     // Find left (left_not_right ? from here : from start)
01860     QPoint left = left_not_right ? here : _iPntSelCorr;
01861 
01862     // Find left (left_not_right ? from start : from here)
01863     QPoint right = left_not_right ? _iPntSelCorr : here;
01864     if ( right.x() > 0 && !_columnSelectionMode )
01865     {
01866       i = loc(right.x(),right.y());
01867       if (i>=0 && i<=_imageSize) {
01868         selClass = charClass(_image[i-1].character);
01869        /* if (selClass == ' ')
01870         {
01871           while ( right.x() < _usedColumns-1 && charClass(_image[i+1].character) == selClass && (right.y()<_usedLines-1) && 
01872                           !(_lineProperties[right.y()] & LINE_WRAPPED))
01873           { i++; right.rx()++; }
01874           if (right.x() < _usedColumns-1)
01875             right = left_not_right ? _iPntSelCorr : here;
01876           else
01877             right.rx()++;  // will be balanced later because of offset=-1;
01878         }*/
01879       }
01880     }
01881 
01882     // Pick which is start (ohere) and which is extension (here)
01883     if ( left_not_right )
01884     {
01885       here = left; ohere = right; offset = 0;
01886     }
01887     else
01888     {
01889       here = right; ohere = left; offset = -1;
01890     }
01891   }
01892 
01893   if ((here == _pntSelCorr) && (scroll == _scrollBar->value())) return; // not moved
01894 
01895   if (here == ohere) return; // It's not left, it's not right.
01896 
01897   if ( _actSel < 2 || swapping )
01898   {
01899     if ( _columnSelectionMode && !_lineSelectionMode && !_wordSelectionMode )
01900     {
01901         _screenWindow->setSelectionStart( ohere.x() , ohere.y() , true );
01902     }
01903     else
01904     {
01905         _screenWindow->setSelectionStart( ohere.x()-1-offset , ohere.y() , false );
01906     }
01907 
01908   }
01909 
01910   _actSel = 2; // within selection
01911   _pntSel = here;
01912   _pntSel.ry() += _scrollBar->value();
01913 
01914   if ( _columnSelectionMode && !_lineSelectionMode && !_wordSelectionMode )
01915   {
01916      _screenWindow->setSelectionEnd( here.x() , here.y() );
01917   }
01918   else
01919   {
01920      _screenWindow->setSelectionEnd( here.x()+offset , here.y() );
01921   }
01922 
01923 }
01924 
01925 void TerminalDisplay::mouseReleaseEvent(QMouseEvent* ev)
01926 {
01927     if ( !_screenWindow )
01928         return;
01929 
01930     int charLine;
01931     int charColumn;
01932     getCharacterPosition(ev->pos(),charLine,charColumn);
01933 
01934   if ( ev->button() == Qt::LeftButton)
01935   {
01936     emit isBusySelecting(false); 
01937     if(dragInfo.state == diPending)
01938     {
01939       // We had a drag event pending but never confirmed.  Kill selection
01940        _screenWindow->clearSelection();
01941       //emit clearSelectionSignal();
01942     }
01943     else
01944     {
01945       if ( _actSel > 1 )
01946       {
01947           setSelection(  _screenWindow->selectedText(_preserveLineBreaks)  );
01948       }
01949 
01950       _actSel = 0;
01951 
01952       //FIXME: emits a release event even if the mouse is
01953       //       outside the range. The procedure used in `mouseMoveEvent'
01954       //       applies here, too.
01955 
01956       if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier))
01957         emit mouseSignal( 3, // release
01958                         charColumn + 1,
01959                         charLine + 1 +_scrollBar->value() -_scrollBar->maximum() , 0);
01960     }
01961     dragInfo.state = diNone;
01962   }
01963   
01964   
01965   if ( !_mouseMarks && 
01966        ((ev->button() == Qt::RightButton && !(ev->modifiers() & Qt::ShiftModifier))
01967                         || ev->button() == Qt::MidButton) ) 
01968   {
01969     emit mouseSignal( 3, 
01970                       charColumn + 1, 
01971                       charLine + 1 +_scrollBar->value() -_scrollBar->maximum() , 
01972                       0);
01973   }
01974 }
01975 
01976 void TerminalDisplay::getCharacterPosition(const QPoint& widgetPoint,int& line,int& column) const
01977 {
01978     column = (widgetPoint.x() + _fontWidth/2 -contentsRect().left()-_leftMargin) / _fontWidth;
01979     line = (widgetPoint.y()-contentsRect().top()-_topMargin) / _fontHeight;
01980 
01981     if ( line < 0 )
01982         line = 0;
01983     if ( column < 0 )
01984         column = 0;
01985 
01986     if ( line >= _usedLines )
01987         line = _usedLines-1;
01988 
01989     // the column value returned can be equal to _usedColumns, which
01990     // is the position just after the last character displayed in a line.
01991     //
01992     // this is required so that the user can select characters in the right-most
01993     // column (or left-most for right-to-left input)
01994     if ( column > _usedColumns )
01995         column = _usedColumns;
01996 }
01997 
01998 void TerminalDisplay::updateLineProperties()
01999 {
02000     if ( !_screenWindow ) 
02001         return;
02002 
02003     _lineProperties = _screenWindow->getLineProperties();    
02004 }
02005 
02006 void TerminalDisplay::mouseDoubleClickEvent(QMouseEvent* ev)
02007 {
02008   if ( ev->button() != Qt::LeftButton) return;
02009   if ( !_screenWindow ) return;
02010 
02011   int charLine = 0;
02012   int charColumn = 0;
02013 
02014   getCharacterPosition(ev->pos(),charLine,charColumn);
02015 
02016   QPoint pos(charColumn,charLine);
02017 
02018   // pass on double click as two clicks.
02019   if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier))
02020   {
02021     // Send just _ONE_ click event, since the first click of the double click
02022     // was already sent by the click handler
02023     emit mouseSignal( 0, 
02024                       pos.x()+1, 
02025                       pos.y()+1 +_scrollBar->value() -_scrollBar->maximum(),
02026                       0 ); // left button
02027     return;
02028   }
02029 
02030   _screenWindow->clearSelection();
02031   QPoint bgnSel = pos;
02032   QPoint endSel = pos;
02033   int i = loc(bgnSel.x(),bgnSel.y());
02034   _iPntSel = bgnSel;
02035   _iPntSel.ry() += _scrollBar->value();
02036 
02037   _wordSelectionMode = true;
02038 
02039   // find word boundaries...
02040   QChar selClass = charClass(_image[i].character);
02041   {
02042      // find the start of the word
02043      int x = bgnSel.x();
02044      while ( ((x>0) || (bgnSel.y()>0 && (_lineProperties[bgnSel.y()-1] & LINE_WRAPPED) )) 
02045                      && charClass(_image[i-1].character) == selClass )
02046      {  
02047        i--; 
02048        if (x>0) 
02049            x--; 
02050        else 
02051        {
02052            x=_usedColumns-1; 
02053            bgnSel.ry()--;
02054        } 
02055      }
02056 
02057      bgnSel.setX(x);
02058      _screenWindow->setSelectionStart( bgnSel.x() , bgnSel.y() , false );
02059 
02060      // find the end of the word
02061      i = loc( endSel.x(), endSel.y() );
02062      x = endSel.x();
02063      while( ((x<_usedColumns-1) || (endSel.y()<_usedLines-1 && (_lineProperties[endSel.y()] & LINE_WRAPPED) )) 
02064                      && charClass(_image[i+1].character) == selClass )
02065      { 
02066          i++; 
02067          if (x<_usedColumns-1) 
02068              x++; 
02069          else 
02070          {  
02071              x=0; 
02072              endSel.ry()++; 
02073          } 
02074      }
02075 
02076      endSel.setX(x);
02077 
02078      // In word selection mode don't select @ (64) if at end of word.
02079      if ( ( QChar( _image[i].character ) == '@' ) && ( ( endSel.x() - bgnSel.x() ) > 0 ) )
02080        endSel.setX( x - 1 );
02081 
02082 
02083      _actSel = 2; // within selection
02084      
02085      _screenWindow->setSelectionEnd( endSel.x() , endSel.y() );
02086     
02087      setSelection( _screenWindow->selectedText(_preserveLineBreaks) ); 
02088    }
02089 
02090   _possibleTripleClick=true;
02091 
02092   QTimer::singleShot(QApplication::doubleClickInterval(),this,
02093                      SLOT(tripleClickTimeout()));
02094 }
02095 
02096 void TerminalDisplay::wheelEvent( QWheelEvent* ev )
02097 {
02098   if (ev->orientation() != Qt::Vertical)
02099     return;
02100 
02101   // if the terminal program is not interested mouse events
02102   // then send the event to the scrollbar if the slider has room to move
02103   // or otherwise send simulated up / down key presses to the terminal program
02104   // for the benefit of programs such as 'less'
02105   if ( _mouseMarks )
02106   {
02107     bool canScroll = _scrollBar->maximum() > 0;
02108     if (canScroll)
02109         _scrollBar->event(ev);
02110     else
02111     {
02112         // assume that each Up / Down key event will cause the terminal application
02113         // to scroll by one line.  
02114         //
02115         // to get a reasonable scrolling speed, scroll by one line for every 5 degrees
02116         // of mouse wheel rotation.  Mouse wheels typically move in steps of 15 degrees,
02117         // giving a scroll of 3 lines
02118         int key = ev->delta() > 0 ? Qt::Key_Up : Qt::Key_Down;
02119 
02120         // QWheelEvent::delta() gives rotation in eighths of a degree
02121         int wheelDegrees = ev->delta() / 8;
02122         int linesToScroll = abs(wheelDegrees) / 5;
02123 
02124         QKeyEvent keyScrollEvent(QEvent::KeyPress,key,Qt::NoModifier);
02125 
02126         for (int i=0;i<linesToScroll;i++)
02127             emit keyPressedSignal(&keyScrollEvent);
02128     }
02129   }
02130   else
02131   {
02132     // terminal program wants notification of mouse activity
02133     
02134     int charLine;
02135     int charColumn;
02136     getCharacterPosition( ev->pos() , charLine , charColumn );
02137     
02138     emit mouseSignal( ev->delta() > 0 ? 4 : 5, 
02139                       charColumn + 1, 
02140                       charLine + 1 +_scrollBar->value() -_scrollBar->maximum() , 
02141                       0);
02142   }
02143 }
02144 
02145 void TerminalDisplay::tripleClickTimeout()
02146 {
02147   _possibleTripleClick=false;
02148 }
02149 
02150 void TerminalDisplay::mouseTripleClickEvent(QMouseEvent* ev)
02151 {
02152   if ( !_screenWindow ) return;
02153 
02154   int charLine;
02155   int charColumn;
02156   getCharacterPosition(ev->pos(),charLine,charColumn);
02157   _iPntSel = QPoint(charColumn,charLine);
02158 
02159   _screenWindow->clearSelection();
02160 
02161   _lineSelectionMode = true;
02162   _wordSelectionMode = false;
02163 
02164   _actSel = 2; // within selection
02165   emit isBusySelecting(true); // Keep it steady...
02166 
02167   while (_iPntSel.y()>0 && (_lineProperties[_iPntSel.y()-1] & LINE_WRAPPED) )
02168     _iPntSel.ry()--;
02169   
02170   if (_tripleClickMode == SelectForwardsFromCursor) {
02171     // find word boundary start
02172     int i = loc(_iPntSel.x(),_iPntSel.y());
02173     QChar selClass = charClass(_image[i].character);
02174     int x = _iPntSel.x();
02175     
02176     while ( ((x>0) || 
02177              (_iPntSel.y()>0 && (_lineProperties[_iPntSel.y()-1] & LINE_WRAPPED) )
02178             ) 
02179             && charClass(_image[i-1].character) == selClass )
02180     {
02181         i--; 
02182         if (x>0) 
02183             x--; 
02184         else 
02185         {
02186             x=_columns-1; 
02187             _iPntSel.ry()--;
02188         } 
02189     }
02190 
02191     _screenWindow->setSelectionStart( x , _iPntSel.y() , false );
02192     _tripleSelBegin = QPoint( x, _iPntSel.y() );
02193   }
02194   else if (_tripleClickMode == SelectWholeLine) {
02195     _screenWindow->setSelectionStart( 0 , _iPntSel.y() , false );
02196     _tripleSelBegin = QPoint( 0, _iPntSel.y() );
02197   }
02198 
02199   while (_iPntSel.y()<_lines-1 && (_lineProperties[_iPntSel.y()] & LINE_WRAPPED) )
02200     _iPntSel.ry()++;
02201   
02202   _screenWindow->setSelectionEnd( _columns - 1 , _iPntSel.y() );
02203 
02204   setSelection(_screenWindow->selectedText(_preserveLineBreaks));
02205 
02206   _iPntSel.ry() += _scrollBar->value();
02207 }
02208 
02209 
02210 bool TerminalDisplay::focusNextPrevChild( bool next )
02211 {
02212   if (next)
02213     return false; // This disables changing the active part in konqueror
02214                   // when pressing Tab
02215   return QWidget::focusNextPrevChild( next );
02216 }
02217 
02218 
02219 QChar TerminalDisplay::charClass(QChar qch) const
02220 {
02221     if ( qch.isSpace() ) return ' ';
02222 
02223     if ( qch.isLetterOrNumber() || _wordCharacters.contains(qch, Qt::CaseInsensitive ) )
02224     return 'a';
02225 
02226     return qch;
02227 }
02228 
02229 void TerminalDisplay::setWordCharacters(const QString& wc)
02230 {
02231     _wordCharacters = wc;
02232 }
02233 
02234 void TerminalDisplay::setUsesMouse(bool on)
02235 {
02236   _mouseMarks = on;
02237   setCursor( _mouseMarks ? Qt::IBeamCursor : Qt::ArrowCursor );
02238 }
02239 bool TerminalDisplay::usesMouse() const
02240 {
02241     return _mouseMarks;
02242 }
02243 
02244 /* ------------------------------------------------------------------------- */
02245 /*                                                                           */
02246 /*                               Clipboard                                   */
02247 /*                                                                           */
02248 /* ------------------------------------------------------------------------- */
02249 
02250 #undef KeyPress
02251 
02252 void TerminalDisplay::emitSelection(bool useXselection,bool appendReturn)
02253 {
02254   if ( !_screenWindow ) 
02255       return;
02256 
02257   // Paste Clipboard by simulating keypress events
02258   QString text = QApplication::clipboard()->text(useXselection ? QClipboard::Selection :
02259                                                                  QClipboard::Clipboard);
02260   if(appendReturn)
02261     text.append("\r");
02262   if ( ! text.isEmpty() )
02263   {
02264     text.replace("\n", "\r");
02265     QKeyEvent e(QEvent::KeyPress, 0, Qt::NoModifier, text);
02266     emit keyPressedSignal(&e); // expose as a big fat keypress event
02267     
02268     _screenWindow->clearSelection();
02269   }
02270 }
02271 
02272 void TerminalDisplay::setSelection(const QString& t)
02273 {
02274   QApplication::clipboard()->setText(t, QClipboard::Selection);
02275 }
02276 
02277 void TerminalDisplay::copyClipboard()
02278 {
02279   if ( !_screenWindow )
02280       return;
02281 
02282   QString text = _screenWindow->selectedText(_preserveLineBreaks);
02283   QApplication::clipboard()->setText(text);
02284 }
02285 
02286 void TerminalDisplay::pasteClipboard()
02287 {
02288   emitSelection(false,false);
02289 }
02290 
02291 void TerminalDisplay::pasteSelection()
02292 {
02293   emitSelection(true,false);
02294 }
02295 
02296 /* ------------------------------------------------------------------------- */
02297 /*                                                                           */
02298 /*                                Keyboard                                   */
02299 /*                                                                           */
02300 /* ------------------------------------------------------------------------- */
02301 
02302 void TerminalDisplay::setFlowControlWarningEnabled( bool enable )
02303 {
02304     _flowControlWarningEnabled = enable;
02305     
02306     // if the dialog is currently visible and the flow control warning has 
02307     // been disabled then hide the dialog
02308     if (!enable)
02309         outputSuspended(false);
02310 }
02311 
02312 void TerminalDisplay::keyPressEvent( QKeyEvent* event )
02313 {
02314     bool emitKeyPressSignal = true;
02315 
02316     // Keyboard-based navigation
02317     if ( event->modifiers() == Qt::ShiftModifier )
02318     {
02319         bool update = true;
02320 
02321         if ( event->key() == Qt::Key_PageUp )
02322         {
02323             _screenWindow->scrollBy( ScreenWindow::ScrollPages , -1 );
02324         }
02325         else if ( event->key() == Qt::Key_PageDown )
02326         {
02327             _screenWindow->scrollBy( ScreenWindow::ScrollPages , 1 );
02328         }
02329         else if ( event->key() == Qt::Key_Up )
02330         {
02331             _screenWindow->scrollBy( ScreenWindow::ScrollLines , -1 );
02332         }
02333         else if ( event->key() == Qt::Key_Down )
02334         {
02335             _screenWindow->scrollBy( ScreenWindow::ScrollLines , 1 );
02336         }
02337         else
02338             update = false;
02339 
02340         if ( update )
02341         {
02342             _screenWindow->setTrackOutput( _screenWindow->atEndOfOutput() );
02343             
02344             updateLineProperties();
02345             updateImage();
02346 
02347             // do not send key press to terminal
02348             emitKeyPressSignal = false;
02349         }
02350     }
02351 
02352     _actSel=0; // Key stroke implies a screen update, so TerminalDisplay won't
02353               // know where the current selection is.
02354 
02355     if (_hasBlinkingCursor) 
02356     {
02357       _blinkCursorTimer->start(BLINK_DELAY);
02358       if (_cursorBlinking)
02359         blinkCursorEvent();
02360       else
02361         _cursorBlinking = false;
02362     }
02363 
02364     if ( emitKeyPressSignal )
02365         emit keyPressedSignal(event);
02366 
02367     event->accept();
02368 }
02369 
02370 void TerminalDisplay::inputMethodEvent( QInputMethodEvent* event )
02371 {
02372     QKeyEvent keyEvent(QEvent::KeyPress,0,Qt::NoModifier,event->commitString());
02373     emit keyPressedSignal(&keyEvent);
02374 
02375     _inputMethodData.preeditString = event->preeditString();
02376     update(preeditRect() | _inputMethodData.previousPreeditRect);
02377     
02378     event->accept();
02379 }
02380 QVariant TerminalDisplay::inputMethodQuery( Qt::InputMethodQuery query ) const
02381 {
02382     const QPoint cursorPos = _screenWindow ? _screenWindow->cursorPosition() : QPoint(0,0);
02383     switch ( query ) 
02384     {
02385         case Qt::ImMicroFocus:
02386                 return imageToWidget(QRect(cursorPos.x(),cursorPos.y(),1,1));
02387             break;
02388         case Qt::ImFont:
02389                 return font();
02390             break;
02391         case Qt::ImCursorPosition:
02392                 // return the cursor position within the current line
02393                 return cursorPos.x();
02394             break;
02395         case Qt::ImSurroundingText:
02396             {
02397                 // return the text from the current line
02398                 QString lineText;
02399                 QTextStream stream(&lineText);
02400                 PlainTextDecoder decoder;
02401                 decoder.begin(&stream);
02402                 decoder.decodeLine(&_image[loc(0,cursorPos.y())],_usedColumns,_lineProperties[cursorPos.y()]);
02403                 decoder.end();
02404                 return lineText;
02405             }
02406             break;
02407         case Qt::ImCurrentSelection:
02408                 return QString();
02409             break;
02410     }
02411 
02412     return QVariant();
02413 }
02414 
02415 bool TerminalDisplay::event( QEvent *e )
02416 {
02417   if ( e->type() == QEvent::ShortcutOverride )
02418   {
02419     QKeyEvent* keyEvent = static_cast<QKeyEvent *>( e );
02420     int modifiers = keyEvent->modifiers();
02421 
02422     //  When a possible shortcut combination is pressed, 
02423     //  emit the overrideShortcutCheck() signal to allow the host
02424     //  to decide whether the terminal should override it or not.
02425     if (modifiers != Qt::NoModifier) 
02426     {
02427         int modifierCount = 0;
02428         unsigned int currentModifier = Qt::ShiftModifier;
02429 
02430         while (currentModifier <= Qt::KeypadModifier)
02431         {
02432             if (modifiers & currentModifier)
02433                 modifierCount++;
02434             currentModifier <<= 1;
02435         }
02436         if (modifierCount < 2) 
02437         {
02438             bool override = false;
02439             emit overrideShortcutCheck(keyEvent,override);
02440             if (override)
02441             {
02442                 keyEvent->accept();
02443                 return true;
02444             }
02445         }
02446     }
02447 
02448     // Override any of the following shortcuts because
02449     // they are needed by the terminal
02450     int keyCode = keyEvent->key() | modifiers;
02451     switch ( keyCode )
02452     {
02453       // list is taken from the QLineEdit::event() code
02454       case Qt::Key_Tab:
02455       case Qt::Key_Delete:
02456       case Qt::Key_Home:
02457       case Qt::Key_End:
02458       case Qt::Key_Backspace:
02459       case Qt::Key_Left:
02460       case Qt::Key_Right:
02461         keyEvent->accept();
02462         return true;
02463     }
02464   }
02465   return QWidget::event( e );
02466 }
02467 
02468 void TerminalDisplay::setBellMode(int mode)
02469 {
02470   _bellMode=mode;
02471 }
02472 
02473 void TerminalDisplay::enableBell()
02474 {
02475     _allowBell = true;
02476 }
02477 
02478 void TerminalDisplay::bell(const QString& message)
02479 {
02480   if (_bellMode==NoBell) return;
02481 
02482   //limit the rate at which bells can occur 
02483   //...mainly for sound effects where rapid bells in sequence 
02484   //produce a horrible noise
02485   if ( _allowBell )
02486   {
02487     _allowBell = false;
02488     QTimer::singleShot(500,this,SLOT(enableBell()));
02489  
02490     if (_bellMode==SystemBeepBell) 
02491     {
02492         KNotification::beep();
02493     } 
02494     else if (_bellMode==NotifyBell) 
02495     {
02496         KNotification::event("BellVisible", message,QPixmap(),this);
02497     } 
02498     else if (_bellMode==VisualBell) 
02499     {
02500         swapColorTable();
02501         QTimer::singleShot(200,this,SLOT(swapColorTable()));
02502     }
02503   }
02504 }
02505 
02506 void TerminalDisplay::swapColorTable()
02507 {
02508   ColorEntry color = _colorTable[1];
02509   _colorTable[1]=_colorTable[0];
02510   _colorTable[0]= color;
02511   _colorsInverted = !_colorsInverted;
02512   update();
02513 }
02514 
02515 void TerminalDisplay::clearImage()
02516 {
02517   // We initialize _image[_imageSize] too. See makeImage()
02518   for (int i = 0; i <= _imageSize; i++)
02519   {
02520     _image[i].character = ' ';
02521     _image[i].foregroundColor = CharacterColor(COLOR_SPACE_DEFAULT,
02522                                                DEFAULT_FORE_COLOR);
02523     _image[i].backgroundColor = CharacterColor(COLOR_SPACE_DEFAULT,
02524                                                DEFAULT_BACK_COLOR);
02525     _image[i].rendition = DEFAULT_RENDITION;
02526   }
02527 }
02528 
02529 void TerminalDisplay::calcGeometry()
02530 {
02531   _scrollBar->resize(QApplication::style()->pixelMetric(QStyle::PM_ScrollBarExtent),
02532                     contentsRect().height());
02533   switch(_scrollbarLocation)
02534   {
02535     case NoScrollBar :
02536      _leftMargin = DEFAULT_LEFT_MARGIN;
02537      _contentWidth = contentsRect().width() - 2 * DEFAULT_LEFT_MARGIN;
02538      break;
02539     case ScrollBarLeft :
02540      _leftMargin = DEFAULT_LEFT_MARGIN + _scrollBar->width();
02541      _contentWidth = contentsRect().width() - 2 * DEFAULT_LEFT_MARGIN - _scrollBar->width();
02542      _scrollBar->move(contentsRect().topLeft());
02543      break;
02544     case ScrollBarRight:
02545      _leftMargin = DEFAULT_LEFT_MARGIN;
02546      _contentWidth = contentsRect().width()  - 2 * DEFAULT_LEFT_MARGIN - _scrollBar->width();
02547      _scrollBar->move(contentsRect().topRight() - QPoint(_scrollBar->width()-1,0));
02548      break;
02549   }
02550 
02551   _topMargin = DEFAULT_TOP_MARGIN;
02552   _contentHeight = contentsRect().height() - 2 * DEFAULT_TOP_MARGIN + /* mysterious */ 1;
02553    
02554   if (!_isFixedSize)
02555   {
02556      // ensure that display is always at least one column wide
02557      _columns = qMax(1,_contentWidth / _fontWidth);
02558      _usedColumns = qMin(_usedColumns,_columns);
02559      
02560      // ensure that display is always at least one line high
02561      _lines = qMax(1,_contentHeight / _fontHeight);
02562      _usedLines = qMin(_usedLines,_lines);
02563   }
02564 }
02565 
02566 void TerminalDisplay::makeImage()
02567 {
02568   calcGeometry();
02569 
02570   // confirm that array will be of non-zero size, since the painting code 
02571   // assumes a non-zero array length
02572   Q_ASSERT( _lines > 0 && _columns > 0 );
02573   Q_ASSERT( _usedLines <= _lines && _usedColumns <= _columns );
02574 
02575   _imageSize=_lines*_columns;
02576   
02577   // We over-commit one character so that we can be more relaxed in dealing with
02578   // certain boundary conditions: _image[_imageSize] is a valid but unused position
02579   _image = new Character[_imageSize+1];
02580 
02581   clearImage();
02582 }
02583 
02584 // calculate the needed size, this must be synced with calcGeometry()
02585 void TerminalDisplay::setSize(int columns, int lines)
02586 {
02587   int scrollBarWidth = _scrollBar->isHidden() ? 0 :  
02588                         style()->pixelMetric(QStyle::PM_ScrollBarExtent);
02589   int horizontalMargin = 2 * DEFAULT_LEFT_MARGIN;
02590   int verticalMargin = 2 * DEFAULT_TOP_MARGIN;
02591 
02592   QSize newSize = QSize( horizontalMargin + scrollBarWidth + (columns * _fontWidth)  ,
02593                  verticalMargin + (lines * _fontHeight)   );
02594 
02595   if ( newSize != size() )
02596   {
02597     _size = newSize;
02598     updateGeometry();
02599   }
02600 }
02601 
02602 void TerminalDisplay::setFixedSize(int cols, int lins)
02603 {
02604   _isFixedSize = true;
02605   
02606   //ensure that display is at least one line by one column in size
02607   _columns = qMax(1,cols);
02608   _lines = qMax(1,lins);
02609   _usedColumns = qMin(_usedColumns,_columns);
02610   _usedLines = qMin(_usedLines,_lines);
02611 
02612   if (_image)
02613   {
02614      delete[] _image;
02615      makeImage();
02616   }
02617   setSize(cols, lins);
02618   QWidget::setFixedSize(_size);
02619 }
02620 
02621 QSize TerminalDisplay::sizeHint() const
02622 {
02623   return _size;
02624 }
02625 
02626 
02627 /* --------------------------------------------------------------------- */
02628 /*                                                                       */
02629 /* Drag & Drop                                                           */
02630 /*                                                                       */
02631 /* --------------------------------------------------------------------- */
02632 
02633 void TerminalDisplay::dragEnterEvent(QDragEnterEvent* event)
02634 {
02635   if (event->mimeData()->hasFormat("text/plain"))
02636       event->acceptProposedAction();
02637 }
02638 
02639 void TerminalDisplay::dropEvent(QDropEvent* event)
02640 {
02641   KUrl::List urls = KUrl::List::fromMimeData(event->mimeData());
02642 
02643   QString dropText;
02644   if (!urls.isEmpty()) 
02645   {
02646     for ( int i = 0 ; i < urls.count() ; i++ ) 
02647     {
02648         KUrl url = KIO::NetAccess::mostLocalUrl( urls[i] , 0 );
02649         QString urlText;
02650 
02651         if (url.isLocalFile())
02652             urlText = url.path(); 
02653         else
02654             urlText = url.url();
02655     
02656         // in future it may be useful to be able to insert file names with drag-and-drop
02657         // without quoting them (this only affects paths with spaces in) 
02658         urlText = KShell::quoteArg(urlText);
02659       
02660         dropText += urlText;
02661 
02662         if ( i != urls.count()-1 ) 
02663             dropText += ' ';
02664     }
02665   }
02666   else 
02667   {
02668     dropText = event->mimeData()->text();
02669   }
02670 
02671   if(event->mimeData()->hasFormat("text/plain")) 
02672   {
02673     emit sendStringToEmu(dropText.toLocal8Bit());
02674   }
02675 }
02676 
02677 void TerminalDisplay::doDrag()
02678 {
02679   dragInfo.state = diDragging;
02680   dragInfo.dragObject = new QDrag(this);
02681   QMimeData *mimeData = new QMimeData;
02682   mimeData->setText(QApplication::clipboard()->text(QClipboard::Selection));
02683   dragInfo.dragObject->setMimeData(mimeData);
02684   dragInfo.dragObject->start(Qt::CopyAction);
02685   // Don't delete the QTextDrag object.  Qt will delete it when it's done with it.
02686 }
02687 
02688 void TerminalDisplay::outputSuspended(bool suspended)
02689 {
02690     //create the label when this function is first called
02691     if (!_outputSuspendedLabel)
02692     {
02693             //This label includes a link to an English language website
02694             //describing the 'flow control' (Xon/Xoff) feature found in almost 
02695             //all terminal emulators.
02696             //If there isn't a suitable article available in the target language the link
02697             //can simply be removed.
02698             _outputSuspendedLabel = new QLabel( i18n("<qt>Output has been "
02699                                                 "<a href=\"http://en.wikipedia.org/wiki/Flow_control\">suspended</a>"
02700                                                 " by pressing Ctrl+S."
02701                                                "  Press <b>Ctrl+Q</b> to resume.</qt>"),
02702                                                this );
02703 
02704             QPalette palette(_outputSuspendedLabel->palette());
02705             KColorScheme::adjustBackground(palette,KColorScheme::NeutralBackground);
02706             _outputSuspendedLabel->setPalette(palette);
02707             _outputSuspendedLabel->setAutoFillBackground(true);
02708             _outputSuspendedLabel->setBackgroundRole(QPalette::Base);
02709             _outputSuspendedLabel->setFont(QApplication::font());
02710             _outputSuspendedLabel->setMargin(5);
02711 
02712             //enable activation of "Xon/Xoff" link in label
02713             _outputSuspendedLabel->setTextInteractionFlags(Qt::LinksAccessibleByMouse | 
02714                                                           Qt::LinksAccessibleByKeyboard);
02715             _outputSuspendedLabel->setOpenExternalLinks(true);
02716             _outputSuspendedLabel->setVisible(false);
02717 
02718             _gridLayout->addWidget(_outputSuspendedLabel);       
02719             _gridLayout->addItem( new QSpacerItem(0,0,QSizePolicy::Expanding,
02720                                                       QSizePolicy::Expanding),
02721                                  1,0);
02722 
02723     }
02724 
02725     _outputSuspendedLabel->setVisible(suspended);
02726 }
02727 
02728 uint TerminalDisplay::lineSpacing() const
02729 {
02730   return _lineSpacing;
02731 }
02732 
02733 void TerminalDisplay::setLineSpacing(uint i)
02734 {
02735   _lineSpacing = i;
02736   setVTFont(font()); // Trigger an update.
02737 }
02738 
02739 AutoScrollHandler::AutoScrollHandler(QWidget* parent)
02740 : QObject(parent)
02741 , _timerId(0)
02742 {
02743     parent->installEventFilter(this);
02744 }
02745 void AutoScrollHandler::timerEvent(QTimerEvent* event)
02746 {
02747     if (event->timerId() != _timerId)
02748         return;
02749 
02750     QMouseEvent mouseEvent( QEvent::MouseMove,
02751                             widget()->mapFromGlobal(QCursor::pos()),
02752                             Qt::NoButton,
02753                             Qt::LeftButton,
02754                             Qt::NoModifier);
02755 
02756     QApplication::sendEvent(widget(),&mouseEvent);  
02757 }
02758 bool AutoScrollHandler::eventFilter(QObject* watched,QEvent* event)
02759 {
02760     Q_ASSERT( watched == parent() );
02761 
02762     QMouseEvent* mouseEvent = (QMouseEvent*)event;
02763     switch (event->type())
02764     {
02765         case QEvent::MouseMove:
02766         {
02767             bool mouseInWidget = widget()->rect().contains(mouseEvent->pos());
02768 
02769             if (mouseInWidget)
02770             {
02771                 if (_timerId)
02772                     killTimer(_timerId);
02773                 _timerId = 0;
02774             }
02775             else
02776             {
02777                 if (!_timerId && (mouseEvent->buttons() & Qt::LeftButton))
02778                     _timerId = startTimer(100);
02779             }
02780                 break;
02781         }
02782         case QEvent::MouseButtonRelease:
02783             if (_timerId && (mouseEvent->buttons() & ~Qt::LeftButton))
02784             {
02785                 killTimer(_timerId);
02786                 _timerId = 0;
02787             }
02788         break;
02789         default:
02790         break;
02791     };
02792 
02793     return false;
02794 }
02795 
02796 #include "TerminalDisplay.moc"
02797 

Konsole

Skip menu "Konsole"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

API Reference

Skip menu "API Reference"
  • Konsole
  • Libraries
  •   libkonq
Generated for API Reference by doxygen 1.5.4
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal