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

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.7
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