summaryrefslogtreecommitdiff
path: root/noncore/apps/opie-console/TEWidget.cpp
Unidiff
Diffstat (limited to 'noncore/apps/opie-console/TEWidget.cpp') (more/less context) (ignore whitespace changes)
-rw-r--r--noncore/apps/opie-console/TEWidget.cpp1264
1 files changed, 1264 insertions, 0 deletions
diff --git a/noncore/apps/opie-console/TEWidget.cpp b/noncore/apps/opie-console/TEWidget.cpp
new file mode 100644
index 0000000..75c438c
--- a/dev/null
+++ b/noncore/apps/opie-console/TEWidget.cpp
@@ -0,0 +1,1264 @@
1/* ------------------------------------------------------------------------ */
2/* */
3/* [TEWidget.C] Terminal Emulation Widget */
4/* */
5/* ------------------------------------------------------------------------ */
6/* */
7/* Copyright (c) 1997,1998 by Lars Doelle <lars.doelle@on-line.de> */
8/* */
9/* This file is part of Konsole - an X terminal for KDE */
10/* */
11/* ------------------------------------------------------------------------ */
12/* */
13/* Ported Konsole to Qt/Embedded */
14/* */
15/* Copyright (C) 2000 by John Ryland <jryland@trolltech.com> */
16/* */
17/* -------------------------------------------------------------------------- */
18/*! \class TEWidget
19
20 \brief Visible screen contents
21
22 This class is responsible to map the `image' of a terminal emulation to the
23 display. All the dependency of the emulation to a specific GUI or toolkit is
24 localized here. Further, this widget has no knowledge about being part of an
25 emulation, it simply work within the terminal emulation framework by exposing
26 size and key events and by being ordered to show a new image.
27
28 <ul>
29 <li> The internal image has the size of the widget (evtl. rounded up)
30 <li> The external image used in setImage can have any size.
31 <li> (internally) the external image is simply copied to the internal
32 when a setImage happens. During a resizeEvent no painting is done
33 a paintEvent is expected to follow anyway.
34 </ul>
35
36 \sa TEScreen \sa Emulation
37*/
38
39/* FIXME:
40 - 'image' may also be used uninitialized (it isn't in fact) in resizeEvent
41 - 'font_a' not used in mouse events
42 - add destructor
43*/
44
45/* TODO
46 - evtl. be sensitive to `paletteChange' while using default colors.
47 - set different 'rounding' styles? I.e. have a mode to show clipped chars?
48*/
49
50// #include "config.h"
51#include "TEWidget.h"
52#include "session.h"
53#include <qpe/config.h>
54
55#include <qapplication.h>
56#include <qcursor.h>
57#include <qregexp.h>
58#include <qpainter.h>
59#include <qclipboard.h>
60#include <qstyle.h>
61#include <qfile.h>
62#include <qdragobject.h>
63
64#include <stdio.h>
65#include <stdlib.h>
66#include <unistd.h>
67#include <ctype.h>
68#include <sys/stat.h>
69#include <sys/types.h>
70#include <signal.h>
71
72#include <assert.h>
73
74// #include "TEWidget.moc"
75//#include <kapp.h>
76//#include <kcursor.h>
77//#include <kurl.h>
78//#include <kdebug.h>
79//#include <klocale.h>
80
81#define HERE printf("%s(%d): %s\n",__FILE__,__LINE__,__FUNCTION__)
82#define HCNT(Name) // { static int cnt = 1; printf("%s(%d): %s %d\n",__FILE__,__LINE__,Name,cnt++); }
83
84#define loc(X,Y) ((Y)*columns+(X))
85
86//FIXME: the rim should normally be 1, 0 only when running in full screen mode.
87#define rimX 0 // left/right rim width
88#define rimY 0 // top/bottom rim high
89
90#define SCRWIDTH 16 // width of the scrollbar
91
92#define yMouseScroll 1
93// scroll increment used when dragging selection at top/bottom of window.
94
95/* ------------------------------------------------------------------------- */
96/* */
97/* Colors */
98/* */
99/* ------------------------------------------------------------------------- */
100
101//FIXME: the default color table is in session.C now.
102// We need a way to get rid of this one, here.
103static const ColorEntry base_color_table[TABLE_COLORS] =
104// The following are almost IBM standard color codes, with some slight
105// gamma correction for the dim colors to compensate for bright X screens.
106// It contains the 8 ansiterm/xterm colors in 2 intensities.
107{
108 // Fixme: could add faint colors here, also.
109 // normal
110 ColorEntry(QColor(0x00,0x00,0x00), 0, 0 ), ColorEntry( QColor(0xB2,0xB2,0xB2), 1, 0 ), // Dfore, Dback
111 ColorEntry(QColor(0x00,0x00,0x00), 0, 0 ), ColorEntry( QColor(0xB2,0x18,0x18), 0, 0 ), // Black, Red
112 ColorEntry(QColor(0x18,0xB2,0x18), 0, 0 ), ColorEntry( QColor(0xB2,0x68,0x18), 0, 0 ), // Green, Yellow
113 ColorEntry(QColor(0x18,0x18,0xB2), 0, 0 ), ColorEntry( QColor(0xB2,0x18,0xB2), 0, 0 ), // Blue, Magenta
114 ColorEntry(QColor(0x18,0xB2,0xB2), 0, 0 ), ColorEntry( QColor(0xB2,0xB2,0xB2), 0, 0 ), // Cyan, White
115 // intensiv
116 ColorEntry(QColor(0x00,0x00,0x00), 0, 1 ), ColorEntry( QColor(0xFF,0xFF,0xFF), 1, 0 ),
117 ColorEntry(QColor(0x68,0x68,0x68), 0, 0 ), ColorEntry( QColor(0xFF,0x54,0x54), 0, 0 ),
118 ColorEntry(QColor(0x54,0xFF,0x54), 0, 0 ), ColorEntry( QColor(0xFF,0xFF,0x54), 0, 0 ),
119 ColorEntry(QColor(0x54,0x54,0xFF), 0, 0 ), ColorEntry( QColor(0xB2,0x18,0xB2), 0, 0 ),
120 ColorEntry(QColor(0x54,0xFF,0xFF), 0, 0 ), ColorEntry( QColor(0xFF,0xFF,0xFF), 0, 0 )
121};
122
123/* Note that we use ANSI color order (bgr), while IBMPC color order is (rgb)
124
125 Code 0 1 2 3 4 5 6 7
126 ----------- ------- ------- ------- ------- ------- ------- ------- -------
127 ANSI (bgr) Black Red Green Yellow Blue Magenta Cyan White
128 IBMPC (rgb) Black Blue Green Cyan Red Magenta Yellow White
129*/
130
131QColor TEWidget::getDefaultBackColor()
132{
133 return color_table[DEFAULT_BACK_COLOR].color;
134}
135
136const ColorEntry* TEWidget::getColorTable() const
137{
138 return color_table;
139}
140
141const ColorEntry* TEWidget::getdefaultColorTable() const
142{
143 return base_color_table;
144}
145
146
147const QPixmap *TEWidget::backgroundPixmap()
148{
149 static QPixmap *bg = new QPixmap("~/qpim/main/pics/faded_bg.xpm");
150 const QPixmap *pm = bg;
151 return pm;
152}
153
154void TEWidget::setColorTable(const ColorEntry table[])
155{
156 for (int i = 0; i < TABLE_COLORS; i++) color_table[i] = table[i];
157
158 const QPixmap* pm = backgroundPixmap();
159 if (!pm) setBackgroundColor(color_table[DEFAULT_BACK_COLOR].color);
160 update();
161}
162
163//FIXME: add backgroundPixmapChanged.
164
165/* ------------------------------------------------------------------------- */
166/* */
167/* Font */
168/* */
169/* ------------------------------------------------------------------------- */
170
171/*
172 The VT100 has 32 special graphical characters. The usual vt100 extended
173 xterm fonts have these at 0x00..0x1f.
174
175 QT's iso mapping leaves 0x00..0x7f without any changes. But the graphicals
176 come in here as proper unicode characters.
177
178 We treat non-iso10646 fonts as VT100 extended and do the requiered mapping
179 from unicode to 0x00..0x1f. The remaining translation is then left to the
180 QCodec.
181*/
182
183// assert for i in [0..31] : vt100extended(vt100_graphics[i]) == i.
184
185unsigned short vt100_graphics[32] =
186{ // 0/8 1/9 2/10 3/11 4/12 5/13 6/14 7/15
187 0x0020, 0x25C6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0,
188 0x00b1, 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c,
189 0xF800, 0xF801, 0x2500, 0xF803, 0xF804, 0x251c, 0x2524, 0x2534,
190 0x252c, 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00b7
191};
192
193static QChar vt100extended(QChar c)
194{
195 switch (c.unicode())
196 {
197 case 0x25c6 : return 1;
198 case 0x2592 : return 2;
199 case 0x2409 : return 3;
200 case 0x240c : return 4;
201 case 0x240d : return 5;
202 case 0x240a : return 6;
203 case 0x00b0 : return 7;
204 case 0x00b1 : return 8;
205 case 0x2424 : return 9;
206 case 0x240b : return 10;
207 case 0x2518 : return 11;
208 case 0x2510 : return 12;
209 case 0x250c : return 13;
210 case 0x2514 : return 14;
211 case 0x253c : return 15;
212 case 0xf800 : return 16;
213 case 0xf801 : return 17;
214 case 0x2500 : return 18;
215 case 0xf803 : return 19;
216 case 0xf804 : return 20;
217 case 0x251c : return 21;
218 case 0x2524 : return 22;
219 case 0x2534 : return 23;
220 case 0x252c : return 24;
221 case 0x2502 : return 25;
222 case 0x2264 : return 26;
223 case 0x2265 : return 27;
224 case 0x03c0 : return 28;
225 case 0x2260 : return 29;
226 case 0x00a3 : return 30;
227 case 0x00b7 : return 31;
228 }
229 return c;
230}
231
232static QChar identicalMap(QChar c)
233{
234 return c;
235}
236
237void TEWidget::fontChange(const QFont &)
238{
239 QFontMetrics fm(font());
240 font_h = fm.height();
241 font_w = fm.maxWidth();
242 font_a = fm.ascent();
243//printf("font_h: %d\n",font_h);
244//printf("font_w: %d\n",font_w);
245//printf("font_a: %d\n",font_a);
246//printf("charset: %s\n",QFont::encodingName(font().charSet()).ascii());
247//printf("rawname: %s\n",font().rawName().ascii());
248 fontMap =
249#if QT_VERSION < 300
250 strcmp(QFont::encodingName(font().charSet()).ascii(),"iso10646")
251 ? vt100extended
252 :
253#endif
254 identicalMap;
255 propagateSize();
256 update();
257}
258
259void TEWidget::setVTFont(const QFont& f)
260{
261 QFrame::setFont(f);
262}
263
264QFont TEWidget::getVTFont() {
265 return font();
266}
267
268void TEWidget::setFont(const QFont &)
269{
270 // ignore font change request if not coming from konsole itself
271}
272
273/* ------------------------------------------------------------------------- */
274/* */
275/* Constructor / Destructor */
276/* */
277/* ------------------------------------------------------------------------- */
278
279TEWidget::TEWidget(QWidget *parent, const char *name) : QFrame(parent,name)
280{
281#ifndef QT_NO_CLIPBOARD
282 cb = QApplication::clipboard();
283 QObject::connect( (QObject*)cb, SIGNAL(dataChanged()),
284 this, SLOT(onClearSelection()) );
285#endif
286
287 scrollbar = new QScrollBar(this);
288 scrollbar->setCursor( arrowCursor );
289 connect(scrollbar, SIGNAL(valueChanged(int)), this, SLOT(scrollChanged(int)));
290
291 Config cfg("Konsole");
292 cfg.setGroup("ScrollBar");
293 switch( cfg.readNumEntry("Position",2)){
294 case 0:
295 scrollLoc = SCRNONE;
296 break;
297 case 1:
298 scrollLoc = SCRLEFT;
299 break;
300 case 2:
301 scrollLoc = SCRRIGHT;
302 break;
303 };
304
305 blinkT = new QTimer(this);
306 connect(blinkT, SIGNAL(timeout()), this, SLOT(blinkEvent()));
307 // blinking = FALSE;
308 blinking = TRUE;
309
310 resizing = FALSE;
311 actSel = 0;
312 image = 0;
313 lines = 1;
314 columns = 1;
315 font_w = 1;
316 font_h = 1;
317 font_a = 1;
318 word_selection_mode = FALSE;
319
320 setMouseMarks(TRUE);
321 setVTFont( QFont("fixed") );
322 setColorTable(base_color_table); // init color table
323
324 qApp->installEventFilter( this ); //FIXME: see below
325// KCursor::setAutoHideCursor( this, true );
326
327 // Init DnD ////////////////////////////////////////////////////////////////
328 currentSession = NULL;
329// setAcceptDrops(true); // attempt
330// m_drop = new QPopupMenu(this);
331// m_drop->insertItem( QString("Paste"), 0);
332// m_drop->insertItem( QString("cd"), 1);
333// connect(m_drop, SIGNAL(activated(int)), SLOT(drop_menu_activated(int)));
334
335 // we need focus so that the auto-hide cursor feature works
336 setFocus();
337 setFocusPolicy( WheelFocus );
338}
339
340//FIXME: make proper destructor
341// Here's a start (David)
342TEWidget::~TEWidget()
343{
344 qApp->removeEventFilter( this );
345 if (image) free(image);
346}
347
348/* ------------------------------------------------------------------------- */
349/* */
350/* Display Operations */
351/* */
352/* ------------------------------------------------------------------------- */
353
354/*!
355 attributed string draw primitive
356*/
357
358void TEWidget::drawAttrStr(QPainter &paint, QRect rect,
359 QString& str, ca attr, BOOL pm, BOOL clear)
360{
361 if (pm && color_table[attr.b].transparent)
362 {
363 paint.setBackgroundMode( TransparentMode );
364 if (clear) erase(rect);
365 }
366 else
367 {
368 if (blinking)
369 paint.fillRect(rect, color_table[attr.b].color);
370 else
371 {
372 paint.setBackgroundMode( OpaqueMode );
373 paint.setBackgroundColor( color_table[attr.b].color );
374 }
375 }
376
377 if (color_table[attr.f].bold)
378 paint.setPen(QColor( 0x8F, 0x00, 0x00 ));
379 else
380 paint.setPen(color_table[attr.f].color);
381
382 paint.drawText(rect.x(),rect.y()+font_a, str);
383
384 if (attr.r & RE_UNDERLINE)
385 paint.drawLine(rect.left(), rect.y()+font_a+1, rect.right(),rect.y()+font_a+1 );
386}
387
388/*!
389 The image can only be set completely.
390
391 The size of the new image may or may not match the size of the widget.
392*/
393
394void TEWidget::setImage(const ca* const newimg, int lines, int columns)
395{ int y,x,len;
396 const QPixmap* pm = backgroundPixmap();
397 QPainter paint;
398 setUpdatesEnabled(FALSE);
399 paint.begin( this );
400HCNT("setImage");
401
402 QPoint tL = contentsRect().topLeft();
403 int tLx = tL.x();
404 int tLy = tL.y();
405 hasBlinker = FALSE;
406
407 int cf = -1; // undefined
408 int cb = -1; // undefined
409 int cr = -1; // undefined
410
411 int lins = QMIN(this->lines, QMAX(0,lines ));
412 int cols = QMIN(this->columns,QMAX(0,columns));
413 QChar *disstrU = new QChar[cols];
414
415//{ static int cnt = 0; printf("setImage %d\n",cnt++); }
416 for (y = 0; y < lins; y++)
417 {
418 const ca* lcl = &image[y*this->columns];
419 const ca* const ext = &newimg[y*columns];
420 if (!resizing) // not while resizing, we're expecting a paintEvent
421 for (x = 0; x < cols; x++)
422 {
423 hasBlinker |= (ext[x].r & RE_BLINK);
424 if (ext[x] != lcl[x])
425 {
426 cr = ext[x].r;
427 cb = ext[x].b;
428 if (ext[x].f != cf) cf = ext[x].f;
429 int lln = cols - x;
430 disstrU[0] = fontMap(ext[x+0].c);
431 for (len = 1; len < lln; len++)
432 {
433 if (ext[x+len].f != cf || ext[x+len].b != cb || ext[x+len].r != cr ||
434 ext[x+len] == lcl[x+len] )
435 break;
436 disstrU[len] = fontMap(ext[x+len].c);
437 }
438 QString unistr(disstrU,len);
439 drawAttrStr(paint,
440 QRect(blX+tLx+font_w*x,bY+tLy+font_h*y,font_w*len,font_h),
441 unistr, ext[x], pm != NULL, true);
442 x += len - 1;
443 }
444 }
445 // finally, make `image' become `newimg'.
446 memcpy((void*)lcl,(const void*)ext,cols*sizeof(ca));
447 }
448 drawFrame( &paint );
449 paint.end();
450 setUpdatesEnabled(TRUE);
451 if ( hasBlinker && !blinkT->isActive()) blinkT->start(1000); // 1000 ms
452 if (!hasBlinker && blinkT->isActive()) { blinkT->stop(); blinking = FALSE; }
453 delete [] disstrU;
454}
455
456// paint Event ////////////////////////////////////////////////////
457
458/*!
459 The difference of this routine vs. the `setImage' is,
460 that the drawing does not include a difference analysis
461 between the old and the new image. Instead, the internal
462 image is used and the painting bound by the PaintEvent box.
463*/
464
465void TEWidget::paintEvent( QPaintEvent* pe )
466{
467
468//{ static int cnt = 0; printf("paint %d\n",cnt++); }
469 const QPixmap* pm = backgroundPixmap();
470 QPainter paint;
471 setUpdatesEnabled(FALSE);
472 paint.begin( this );
473 paint.setBackgroundMode( TransparentMode );
474HCNT("paintEvent");
475
476 // Note that the actual widget size can be slightly larger
477 // that the image (the size is truncated towards the smaller
478 // number of characters in `resizeEvent'. The paint rectangle
479 // can thus be larger than the image, but less then the size
480 // of one character.
481
482 QRect rect = pe->rect().intersect(contentsRect());
483
484 QPoint tL = contentsRect().topLeft();
485 int tLx = tL.x();
486 int tLy = tL.y();
487
488 int lux = QMIN(columns-1, QMAX(0,(rect.left() - tLx - blX ) / font_w));
489 int luy = QMIN(lines-1, QMAX(0,(rect.top() - tLy - bY ) / font_h));
490 int rlx = QMIN(columns-1, QMAX(0,(rect.right() - tLx - blX ) / font_w));
491 int rly = QMIN(lines-1, QMAX(0,(rect.bottom() - tLy - bY ) / font_h));
492
493 /*
494 printf("paintEvent: %d..%d, %d..%d (%d..%d, %d..%d)\n",lux,rlx,luy,rly,
495 rect.left(), rect.right(), rect.top(), rect.bottom());
496 */
497
498 // if (pm != NULL && color_table[image->b].transparent)
499 // erase(rect);
500 // BL: I have no idea why we need this, and it breaks the refresh.
501
502 QChar *disstrU = new QChar[columns];
503 for (int y = luy; y <= rly; y++)
504 for (int x = lux; x <= rlx; x++)
505 {
506 int len = 1;
507 disstrU[0] = fontMap(image[loc(x,y)].c);
508 int cf = image[loc(x,y)].f;
509 int cb = image[loc(x,y)].b;
510 int cr = image[loc(x,y)].r;
511 while (x+len <= rlx &&
512 image[loc(x+len,y)].f == cf &&
513 image[loc(x+len,y)].b == cb &&
514 image[loc(x+len,y)].r == cr )
515 {
516 disstrU[len] = fontMap(image[loc(x+len,y)].c);
517 len += 1;
518 }
519 QString unistr(disstrU,len);
520 drawAttrStr(paint,
521 QRect(blX+tLx+font_w*x,bY+tLy+font_h*y,font_w*len,font_h),
522 unistr, image[loc(x,y)], pm != NULL, false);
523 x += len - 1;
524 }
525 delete [] disstrU;
526 drawFrame( &paint );
527 paint.end();
528 setUpdatesEnabled(TRUE);
529}
530
531void TEWidget::blinkEvent()
532{
533 blinking = !blinking;
534 repaint(FALSE);
535}
536
537/* ------------------------------------------------------------------------- */
538/* */
539/* Resizing */
540/* */
541/* ------------------------------------------------------------------------- */
542
543void TEWidget::resizeEvent(QResizeEvent* ev)
544{
545// printf("resize: %d,%d\n",ev->size().width(),ev->size().height());
546 //printf("approx: %d,%d\n",ev->size().width()/font_w,ev->size().height()/font_h);
547 //printf("leaves: %d,%d\n",ev->size().width()%font_w,ev->size().height()%font_h);
548 //printf("curren: %d,%d\n",width(),height());
549HCNT("resizeEvent");
550
551 // see comment in `paintEvent' concerning the rounding.
552 //FIXME: could make a routine here; check width(),height()
553 assert(ev->size().width() == width());
554 assert(ev->size().height() == height());
555
556 propagateSize();
557}
558
559void TEWidget::propagateSize()
560{
561 ca* oldimg = image;
562 int oldlin = lines;
563 int oldcol = columns;
564 makeImage();
565 // we copy the old image to reduce flicker
566 int lins = QMIN(oldlin,lines);
567 int cols = QMIN(oldcol,columns);
568 if (oldimg)
569 {
570 for (int lin = 0; lin < lins; lin++)
571 memcpy((void*)&image[columns*lin],
572 (void*)&oldimg[oldcol*lin],cols*sizeof(ca));
573 free(oldimg); //FIXME: try new,delete
574 }
575 else
576 clearImage();
577
578 //NOTE: control flows from the back through the chest right into the eye.
579 // `emu' will call back via `setImage'.
580
581 resizing = TRUE;
582 emit changedImageSizeSignal(lines, columns); // expose resizeEvent
583 resizing = FALSE;
584}
585
586/* ------------------------------------------------------------------------- */
587/* */
588/* Scrollbar */
589/* */
590/* ------------------------------------------------------------------------- */
591
592void TEWidget::scrollChanged(int)
593{
594 emit changedHistoryCursor(scrollbar->value()); //expose
595}
596
597void TEWidget::setScroll(int cursor, int slines)
598{
599 disconnect(scrollbar, SIGNAL(valueChanged(int)), this, SLOT(scrollChanged(int)));
600 scrollbar->setRange(0,slines);
601 scrollbar->setSteps(1,lines);
602 scrollbar->setValue(cursor);
603 connect(scrollbar, SIGNAL(valueChanged(int)), this, SLOT(scrollChanged(int)));
604}
605
606void TEWidget::setScrollbarLocation(int loc)
607{
608 if (scrollLoc == loc) return; // quickly
609 scrollLoc = loc;
610 propagateSize();
611 update();
612}
613
614/* ------------------------------------------------------------------------- */
615/* */
616/* Mouse */
617/* */
618/* ------------------------------------------------------------------------- */
619
620/*!
621 Three different operations can be performed using the mouse, and the
622 routines in this section serve all of them:
623
624 1) The press/release events are exposed to the application
625 2) Marking (press and move left button) and Pasting (press middle button)
626 3) The right mouse button is used from the configuration menu
627
628 NOTE: During the marking process we attempt to keep the cursor within
629 the bounds of the text as being displayed by setting the mouse position
630 whenever the mouse has left the text area.
631
632 Two reasons to do so:
633 1) QT does not allow the `grabMouse' to confine-to the TEWidget.
634 Thus a `XGrapPointer' would have to be used instead.
635 2) Even if so, this would not help too much, since the text area
636 of the TEWidget is normally not identical with it's bounds.
637
638 The disadvantage of the current handling is, that the mouse can visibly
639 leave the bounds of the widget and is then moved back. Because of the
640 current construction, and the reasons mentioned above, we cannot do better
641 without changing the overall construction.
642*/
643
644/*!
645*/
646
647void TEWidget::mousePressEvent(QMouseEvent* ev)
648{
649//printf("press [%d,%d] %d\n",ev->x()/font_w,ev->y()/font_h,ev->button());
650 if ( !contentsRect().contains(ev->pos()) ) return;
651 QPoint tL = contentsRect().topLeft();
652 int tLx = tL.x();
653 int tLy = tL.y();
654
655 word_selection_mode = FALSE;
656
657//printf("press top left [%d,%d] by=%d\n",tLx,tLy, bY);
658 if ( ev->button() == LeftButton)
659 {
660 QPoint pos = QPoint((ev->x()-tLx-blX)/font_w,(ev->y()-tLy-bY)/font_h);
661
662 if ( ev->state() & ControlButton ) preserve_line_breaks = FALSE ;
663
664 if (mouse_marks || (ev->state() & ShiftButton))
665 {
666 emit clearSelectionSignal();
667 iPntSel = pntSel = pos;
668 actSel = 1; // left mouse button pressed but nothing selected yet.
669 grabMouse( /*crossCursor*/ ); // handle with care!
670 }
671 else
672 {
673 emit mouseSignal( 0, pos.x() + 1, pos.y() + 1 ); // left button
674 }
675 }
676 if ( ev->button() == MidButton )
677 {
678 emitSelection();
679 }
680 if ( ev->button() == RightButton ) // Configure
681 {
682 emit configureRequest( this, ev->state()&(ShiftButton|ControlButton), ev->x(), ev->y() );
683 }
684}
685
686void TEWidget::mouseMoveEvent(QMouseEvent* ev)
687{
688 // for auto-hiding the cursor, we need mouseTracking
689 if (ev->state() == NoButton ) return;
690
691 if (actSel == 0) return;
692
693 // don't extend selection while pasting
694 if (ev->state() & MidButton) return;
695
696 //if ( !contentsRect().contains(ev->pos()) ) return;
697 QPoint tL = contentsRect().topLeft();
698 int tLx = tL.x();
699 int tLy = tL.y();
700 int scroll = scrollbar->value();
701
702 // we're in the process of moving the mouse with the left button pressed
703 // the mouse cursor will kept catched within the bounds of the text in
704 // this widget.
705
706 // Adjust position within text area bounds. See FIXME above.
707 QPoint pos = ev->pos();
708 if ( pos.x() < tLx+blX ) pos.setX( tLx+blX );
709 if ( pos.x() > tLx+blX+columns*font_w-1 ) pos.setX( tLx+blX+columns*font_w );
710 if ( pos.y() < tLy+bY ) pos.setY( tLy+bY );
711 if ( pos.y() > tLy+bY+lines*font_h-1 ) pos.setY( tLy+bY+lines*font_h-1 );
712 // check if we produce a mouse move event by this
713 if ( pos != ev->pos() ) cursor().setPos(mapToGlobal(pos));
714
715 if ( pos.y() == tLy+bY+lines*font_h-1 )
716 {
717 scrollbar->setValue(scrollbar->value()+yMouseScroll); // scrollforward
718 }
719 if ( pos.y() == tLy+bY )
720 {
721 scrollbar->setValue(scrollbar->value()-yMouseScroll); // scrollback
722 }
723
724 QPoint here = QPoint((pos.x()-tLx-blX)/font_w,(pos.y()-tLy-bY)/font_h);
725 QPoint ohere;
726 bool swapping = FALSE;
727
728 if ( word_selection_mode )
729 {
730 // Extend to word boundaries
731 int i;
732 int selClass;
733
734 bool left_not_right = ( here.y() < iPntSel.y() ||
735 here.y() == iPntSel.y() && here.x() < iPntSel.x() );
736 bool old_left_not_right = ( pntSel.y() < iPntSel.y() ||
737 pntSel.y() == iPntSel.y() && pntSel.x() < iPntSel.x() );
738 swapping = left_not_right != old_left_not_right;
739
740 // Find left (left_not_right ? from here : from start)
741 QPoint left = left_not_right ? here : iPntSel;
742 i = loc(left.x(),left.y());
743 selClass = charClass(image[i].c);
744 while ( left.x() > 0 && charClass(image[i-1].c) == selClass )
745 { i--; left.rx()--; }
746
747 // Find left (left_not_right ? from start : from here)
748 QPoint right = left_not_right ? iPntSel : here;
749 i = loc(right.x(),right.y());
750 selClass = charClass(image[i].c);
751 while ( right.x() < columns-1 && charClass(image[i+1].c) == selClass )
752 { i++; right.rx()++; }
753
754 // Pick which is start (ohere) and which is extension (here)
755 if ( left_not_right )
756 {
757 here = left; ohere = right;
758 }
759 else
760 {
761 here = right; ohere = left;
762 }
763 }
764
765 if (here == pntSel && scroll == scrollbar->value()) return; // not moved
766
767 if ( word_selection_mode ) {
768 if ( actSel < 2 || swapping ) {
769 emit beginSelectionSignal( ohere.x(), ohere.y() );
770 }
771 } else if ( actSel < 2 ) {
772 emit beginSelectionSignal( pntSel.x(), pntSel.y() );
773 }
774
775 actSel = 2; // within selection
776 pntSel = here;
777 emit extendSelectionSignal( here.x(), here.y() );
778}
779
780void TEWidget::mouseReleaseEvent(QMouseEvent* ev)
781{
782//printf("release [%d,%d] %d\n",ev->x()/font_w,ev->y()/font_h,ev->button());
783 if ( ev->button() == LeftButton)
784 {
785 if ( actSel > 1 ) emit endSelectionSignal(preserve_line_breaks);
786 preserve_line_breaks = TRUE;
787 actSel = 0;
788
789 //FIXME: emits a release event even if the mouse is
790 // outside the range. The procedure used in `mouseMoveEvent'
791 // applies here, too.
792
793 QPoint tL = contentsRect().topLeft();
794 int tLx = tL.x();
795 int tLy = tL.y();
796
797 if (!mouse_marks && !(ev->state() & ShiftButton))
798 emit mouseSignal( 3, // release
799 (ev->x()-tLx-blX)/font_w + 1,
800 (ev->y()-tLy-bY)/font_h + 1 );
801 releaseMouse();
802 }
803}
804
805void TEWidget::mouseDoubleClickEvent(QMouseEvent* ev)
806{
807 if ( ev->button() != LeftButton) return;
808
809 QPoint tL = contentsRect().topLeft();
810 int tLx = tL.x();
811 int tLy = tL.y();
812 QPoint pos = QPoint((ev->x()-tLx-blX)/font_w,(ev->y()-tLy-bY)/font_h);
813
814 // pass on double click as two clicks.
815 if (!mouse_marks && !(ev->state() & ShiftButton))
816 {
817 emit mouseSignal( 0, pos.x()+1, pos.y()+1 ); // left button
818 emit mouseSignal( 3, pos.x()+1, pos.y()+1 ); // release
819 emit mouseSignal( 0, pos.x()+1, pos.y()+1 ); // left button
820 return;
821 }
822
823
824 emit clearSelectionSignal();
825 QPoint bgnSel = pos;
826 QPoint endSel = QPoint((ev->x()-tLx-blX)/font_w,(ev->y()-tLy-bY)/font_h);
827 int i = loc(bgnSel.x(),bgnSel.y());
828 iPntSel = bgnSel;
829
830 word_selection_mode = TRUE;
831
832 // find word boundaries...
833 int selClass = charClass(image[i].c);
834 {
835 // set the start...
836 int x = bgnSel.x();
837 while ( x > 0 && charClass(image[i-1].c) == selClass )
838 { i--; x--; }
839 bgnSel.setX(x);
840 emit beginSelectionSignal( bgnSel.x(), bgnSel.y() );
841
842 // set the end...
843 i = loc( endSel.x(), endSel.y() );
844 x = endSel.x();
845 while( x < columns-1 && charClass(image[i+1].c) == selClass )
846 { i++; x++ ; }
847 endSel.setX(x);
848 actSel = 2; // within selection
849 emit extendSelectionSignal( endSel.x(), endSel.y() );
850 emit endSelectionSignal(preserve_line_breaks);
851 preserve_line_breaks = TRUE;
852 }
853}
854
855void TEWidget::focusInEvent( QFocusEvent * )
856{
857
858 // do nothing, to prevent repainting
859}
860
861
862void TEWidget::focusOutEvent( QFocusEvent * )
863{
864 // do nothing, to prevent repainting
865}
866
867bool TEWidget::focusNextPrevChild( bool next )
868{
869 if (next)
870 return false; // This disables changing the active part in konqueror
871 // when pressing Tab
872 return QFrame::focusNextPrevChild( next );
873}
874
875
876int TEWidget::charClass(char ch) const
877{
878 // This might seem like overkill, but imagine if ch was a Unicode
879 // character (Qt 2.0 QChar) - it might then be sensible to separate
880 // the different language ranges, etc.
881
882 if ( isspace(ch) ) return ' ';
883
884 static const char *word_characters = ":@-./_~";
885 if ( isalnum(ch) || strchr(word_characters, ch) )
886 return 'a';
887
888 // Everything else is weird
889 return 1;
890}
891
892void TEWidget::setMouseMarks(bool on)
893{
894 mouse_marks = on;
895 setCursor( mouse_marks ? ibeamCursor : arrowCursor );
896}
897
898/* ------------------------------------------------------------------------- */
899/* */
900/* Clipboard */
901/* */
902/* ------------------------------------------------------------------------- */
903
904#undef KeyPress
905
906void TEWidget::emitSelection()
907// Paste Clipboard by simulating keypress events
908{
909#ifndef QT_NO_CLIPBOARD
910 QString text = QApplication::clipboard()->text();
911 if ( ! text.isNull() )
912 {
913 text.replace(QRegExp("\n"), "\r");
914 QKeyEvent e(QEvent::KeyPress, 0, -1, 0, text);
915 emit keyPressedSignal(&e); // expose as a big fat keypress event
916 emit clearSelectionSignal();
917 }
918#endif
919}
920
921void TEWidget::emitText(QString text)
922{
923 QKeyEvent e(QEvent::KeyPress, 0, -1, 0, text);
924 emit keyPressedSignal(&e); // expose as a big fat keypress event
925}
926
927void TEWidget::pasteClipboard( )
928{
929 emitSelection();
930}
931
932void TEWidget::setSelection(const QString& t)
933{
934#ifndef QT_NO_CLIPBOARD
935 // Disconnect signal while WE set the clipboard
936 QObject *cb = QApplication::clipboard();
937 QObject::disconnect( cb, SIGNAL(dataChanged()),
938 this, SLOT(onClearSelection()) );
939
940 QApplication::clipboard()->setText(t);
941
942 QObject::connect( cb, SIGNAL(dataChanged()),
943 this, SLOT(onClearSelection()) );
944#endif
945}
946
947void TEWidget::onClearSelection()
948{
949 emit clearSelectionSignal();
950}
951
952/* ------------------------------------------------------------------------- */
953/* */
954/* Keyboard */
955/* */
956/* ------------------------------------------------------------------------- */
957
958//FIXME: an `eventFilter' has been installed instead of a `keyPressEvent'
959// due to a bug in `QT' or the ignorance of the author to prevent
960// repaint events being emitted to the screen whenever one leaves
961// or reenters the screen to/from another application.
962//
963// Troll says one needs to change focusInEvent() and focusOutEvent(),
964// which would also let you have an in-focus cursor and an out-focus
965// cursor like xterm does.
966
967// for the auto-hide cursor feature, I added empty focusInEvent() and
968// focusOutEvent() so that update() isn't called.
969// For auto-hide, we need to get keypress-events, but we only get them when
970// we have focus.
971
972void TEWidget::doScroll(int lines)
973{
974 scrollbar->setValue(scrollbar->value()+lines);
975}
976
977bool TEWidget::eventFilter( QObject *obj, QEvent *e )
978{
979 if ( (e->type() == QEvent::Accel ||
980 e->type() == QEvent::AccelAvailable ) && qApp->focusWidget() == this ) {
981 static_cast<QKeyEvent *>( e )->ignore();
982 return true;
983 }
984 if ( obj != this /* when embedded */ && obj != parent() /* when standalone */ )
985 return FALSE; // not us
986 if ( e->type() == QEvent::Wheel) {
987 QApplication::sendEvent(scrollbar, e);
988 }
989
990#ifdef FAKE_CTRL_AND_ALT
991 static bool control = FALSE;
992 static bool alt = FALSE;
993// qDebug(" Has a keyboard with no CTRL and ALT keys, but we fake it:");
994 bool dele=FALSE;
995 if ( e->type() == QEvent::KeyPress || e->type() == QEvent::KeyRelease ) {
996 QKeyEvent* ke = (QKeyEvent*)e;
997 bool keydown = e->type() == QEvent::KeyPress || ke->isAutoRepeat();
998 switch (ke->key()) {
999 case Key_F9: // let this be "Control"
1000 control = keydown;
1001 e = new QKeyEvent(QEvent::KeyPress, Key_Control, 0, ke->state());
1002 dele=TRUE;
1003 break;
1004 case Key_F13: // let this be "Alt"
1005 alt = keydown;
1006 e = new QKeyEvent(QEvent::KeyPress, Key_Alt, 0, ke->state());
1007 dele=TRUE;
1008 break;
1009 default:
1010 if ( control ) {
1011 int a = toupper(ke->ascii())-64;
1012 if ( a >= 0 && a < ' ' ) {
1013 e = new QKeyEvent(e->type(), ke->key(),
1014 a, ke->state()|ControlButton, QChar(a,0));
1015 dele=TRUE;
1016 }
1017 }
1018 if ( alt ) {
1019 e = new QKeyEvent(e->type(), ke->key(),
1020 ke->ascii(), ke->state()|AltButton, ke->text());
1021 dele=TRUE;
1022 }
1023 }
1024 }
1025#endif
1026
1027 if ( e->type() == QEvent::KeyPress ) {
1028 QKeyEvent* ke = (QKeyEvent*)e;
1029 actSel=0; // Key stroke implies a screen update, so TEWidget won't
1030 // know where the current selection is.
1031
1032// qDebug("key pressed is 0x%x",ke->key());
1033
1034 if( ke->state() == ShiftButton && ke->key() == Key_Tab) { //lets hardcode this sucker
1035
1036// qDebug("key pressed 2 is 0x%x",ke->key());
1037 emitText("\\"); // expose
1038 } else
1039 emit keyPressedSignal(ke); // expose
1040 ke->accept();
1041#ifdef FAKE_CTRL_AND_ALT
1042 if ( dele ) delete e;
1043#endif
1044 return true; // stop the event
1045 }
1046 if ( e->type() == QEvent::Enter ) {
1047 QObject::disconnect( (QObject*)cb, SIGNAL(dataChanged()),
1048 this, SLOT(onClearSelection()) );
1049 }
1050 if ( e->type() == QEvent::Leave ) {
1051 QObject::connect( (QObject*)cb, SIGNAL(dataChanged()),
1052 this, SLOT(onClearSelection()) );
1053 }
1054 return QFrame::eventFilter( obj, e );
1055}
1056
1057/* ------------------------------------------------------------------------- */
1058/* */
1059/* Frame */
1060/* */
1061/* ------------------------------------------------------------------------- */
1062
1063void TEWidget::frameChanged()
1064{
1065 propagateSize();
1066 update();
1067}
1068
1069/* ------------------------------------------------------------------------- */
1070/* */
1071/* Sound */
1072/* */
1073/* ------------------------------------------------------------------------- */
1074
1075void TEWidget::Bell()
1076{
1077 QApplication::beep();
1078}
1079
1080/* ------------------------------------------------------------------------- */
1081/* */
1082/* Auxiluary */
1083/* */
1084/* ------------------------------------------------------------------------- */
1085
1086void TEWidget::clearImage()
1087// initialize the image
1088// for internal use only
1089{
1090 for (int y = 0; y < lines; y++)
1091 for (int x = 0; x < columns; x++)
1092 {
1093 image[loc(x,y)].c = 0xff; //' ';
1094 image[loc(x,y)].f = 0xff; //DEFAULT_FORE_COLOR;
1095 image[loc(x,y)].b = 0xff; //DEFAULT_BACK_COLOR;
1096 image[loc(x,y)].r = 0xff; //DEFAULT_RENDITION;
1097 }
1098}
1099
1100// Create Image ///////////////////////////////////////////////////////
1101
1102void TEWidget::calcGeometry()
1103{
1104 //FIXME: set rimX == rimY == 0 when running in full screen mode.
1105
1106 scrollbar->resize(QApplication::style().scrollBarExtent().width(),
1107 contentsRect().height());
1108 switch(scrollLoc)
1109 {
1110 case SCRNONE :
1111 columns = ( contentsRect().width() - 2 * rimX ) / font_w;
1112 blX = (contentsRect().width() - (columns*font_w) ) / 2;
1113 brX = blX;
1114 scrollbar->hide();
1115 break;
1116 case SCRLEFT :
1117 columns = ( contentsRect().width() - 2 * rimX - scrollbar->width()) / font_w;
1118 brX = (contentsRect().width() - (columns*font_w) - scrollbar->width() ) / 2;
1119 blX = brX + scrollbar->width();
1120 scrollbar->move(contentsRect().topLeft());
1121 scrollbar->show();
1122 break;
1123 case SCRRIGHT:
1124 columns = ( contentsRect().width() - 2 * rimX - scrollbar->width()) / font_w;
1125 blX = (contentsRect().width() - (columns*font_w) - scrollbar->width() ) / 2;
1126 brX = blX;
1127 scrollbar->move(contentsRect().topRight() - QPoint(scrollbar->width()-1,0));
1128 scrollbar->show();
1129 break;
1130 }
1131 //FIXME: support 'rounding' styles
1132 lines = ( contentsRect().height() - 2 * rimY ) / font_h;
1133 bY = (contentsRect().height() - (lines *font_h)) / 2;
1134}
1135
1136void TEWidget::makeImage()
1137//FIXME: rename 'calcGeometry?
1138{
1139 calcGeometry();
1140 image = (ca*) malloc(lines*columns*sizeof(ca));
1141 clearImage();
1142}
1143
1144// calculate the needed size
1145QSize TEWidget::calcSize(int cols, int lins) const
1146{
1147 int frw = width() - contentsRect().width();
1148 int frh = height() - contentsRect().height();
1149 int scw = (scrollLoc==SCRNONE?0:scrollbar->width());
1150 return QSize( font_w*cols + 2*rimX + frw + scw, font_h*lins + 2*rimY + frh );
1151}
1152
1153QSize TEWidget::sizeHint() const
1154{
1155 return size();
1156}
1157
1158void TEWidget::styleChange(QStyle &)
1159{
1160 propagateSize();
1161}
1162
1163#ifndef QT_NO_DRAGANDDROP
1164
1165/* --------------------------------------------------------------------- */
1166/* */
1167/* Drag & Drop */
1168/* */
1169/* --------------------------------------------------------------------- */
1170
1171
1172void TEWidget::dragEnterEvent(QDragEnterEvent* e)
1173{
1174 e->accept(QTextDrag::canDecode(e) ||
1175 QUriDrag::canDecode(e));
1176}
1177
1178void TEWidget::dropEvent(QDropEvent* event)
1179{
1180 // The current behaviour when url(s) are dropped is
1181 // * if there is only ONE url and if it's a LOCAL one, ask for paste or cd
1182 // * in all other cases, just paste
1183 // (for non-local ones, or for a list of URLs, 'cd' is nonsense)
1184 QStrList strlist;
1185 int file_count = 0;
1186 dropText = "";
1187 bool bPopup = true;
1188
1189 if(QUriDrag::decode(event, strlist)) {
1190 if (strlist.count()) {
1191 for(const char* p = strlist.first(); p; p = strlist.next()) {
1192 if(file_count++ > 0) {
1193 dropText += " ";
1194 bPopup = false; // more than one file, don't popup
1195 }
1196
1197/*
1198 KURL url(p);
1199 if (url.isLocalFile()) {
1200 dropText += url.path(); // local URL : remove protocol
1201 }
1202 else {
1203 dropText += url.prettyURL();
1204 bPopup = false; // a non-local file, don't popup
1205 }
1206*/
1207
1208 }
1209
1210 if (bPopup)
1211 // m_drop->popup(pos() + event->pos());
1212 m_drop->popup(mapToGlobal(event->pos()));
1213 else
1214 {
1215 if (currentSession) {
1216 currentSession->getEmulation()->sendString(dropText.local8Bit());
1217 }
1218// kdDebug() << "Drop:" << dropText.local8Bit() << "\n";
1219 }
1220 }
1221 }
1222 else if(QTextDrag::decode(event, dropText)) {
1223// kdDebug() << "Drop:" << dropText.local8Bit() << "\n";
1224 if (currentSession) {
1225 currentSession->getEmulation()->sendString(dropText.local8Bit());
1226 }
1227 // Paste it
1228 }
1229}
1230#endif
1231
1232
1233void TEWidget::drop_menu_activated(int item)
1234{
1235#ifndef QT_NO_DRAGANDDROP
1236 switch (item)
1237 {
1238 case 0: // paste
1239 currentSession->getEmulation()->sendString(dropText.local8Bit());
1240// KWM::activate((Window)this->winId());
1241 break;
1242 case 1: // cd ...
1243 currentSession->getEmulation()->sendString("cd ");
1244 struct stat statbuf;
1245 if ( ::stat( QFile::encodeName( dropText ), &statbuf ) == 0 )
1246 {
1247 if ( !S_ISDIR(statbuf.st_mode) )
1248 {
1249/*
1250 KURL url;
1251 url.setPath( dropText );
1252 dropText = url.directory( true, false ); // remove filename
1253*/
1254 }
1255 }
1256 dropText.replace(QRegExp(" "), "\\ "); // escape spaces
1257 currentSession->getEmulation()->sendString(dropText.local8Bit());
1258 currentSession->getEmulation()->sendString("\n");
1259// KWM::activate((Window)this->winId());
1260 break;
1261 }
1262#endif
1263}
1264