fixes for event handling & repaint
[texmacs.git] / src / src / Plugins / Qt / QTMWidget.cpp
blob3e18cf369f56ccddb4dd82e32152aded775edf3a
2 /******************************************************************************
3 * MODULE : QTMWidget.cpp
4 * DESCRIPTION: QT Texmacs widget class
5 * COPYRIGHT : (C) 2008 Massimiliano Gubinelli and Joris van der Hoeven
6 *******************************************************************************
7 * This software falls under the GNU general public license version 3 or later.
8 * It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE
9 * in the root directory or <http://www.gnu.org/licenses/gpl-3.0.html>.
10 ******************************************************************************/
12 #include <QtGui>
14 #include "QTMWidget.hpp"
15 #include "qt_renderer.hpp"
16 #include "qt_gui.hpp"
17 #include "qt_utilities.hpp"
18 #include "qt_simple_widget.hpp"
19 #include "converter.hpp"
21 #include "config.h"
22 #include "message.hpp"
24 #ifdef USE_CAIRO
25 #include "Cairo/cairo_renderer.hpp"
26 #include "Cairo/tm_cairo.hpp"
28 #if defined(Q_WS_X11)
29 #include <QX11Info>
30 extern Drawable qt_x11Handle(const QPaintDevice *pd);
31 extern const QX11Info *qt_x11Info(const QPaintDevice *pd);
32 #undef KeyPress // conflict between QEvent::KeyPrees and X11 defintion
33 #endif // Q_WS_X11
35 #endif // USE_CAIRO
37 #include <QEvent>
39 #define PIXEL 256
41 QSet<QTMWidget*> QTMWidget::all_widgets;
44 hashmap<int,string> qtkeymap (0);
45 hashmap<int,string> qtdeadmap (0);
47 inline void
48 scale (QPoint& point) {
49 point.rx() *= PIXEL; point.ry() *= -PIXEL;
52 inline void
53 map (int code, string name) {
54 qtkeymap(code) = name;
56 inline void
57 deadmap (int code, string name) {
58 qtdeadmap(code) = name;
61 void
62 initkeymap () {
63 map (Qt::Key_Space , "space");
64 map (Qt::Key_Return , "return");
65 map (Qt::Key_Tab , "tab");
66 map (Qt::Key_Backspace , "backspace");
67 map (Qt::Key_Enter , "enter");
68 map (Qt::Key_Escape , "escape");
69 map (Qt::Key_Backspace , "backspace");
70 map (Qt::Key_Up , "up" );
71 map (Qt::Key_Down , "down" );
72 map (Qt::Key_Left , "left" );
73 map (Qt::Key_Right , "right" );
74 map (Qt::Key_F1 , "F1" );
75 map (Qt::Key_F2 , "F2" );
76 map (Qt::Key_F3 , "F3" );
77 map (Qt::Key_F4 , "F4" );
78 map (Qt::Key_F5 , "F5" );
79 map (Qt::Key_F6 , "F6" );
80 map (Qt::Key_F7 , "F7" );
81 map (Qt::Key_F8 , "F8" );
82 map (Qt::Key_F9 , "F9" );
83 map (Qt::Key_F10 , "F10" );
84 map (Qt::Key_F11 , "F11" );
85 map (Qt::Key_F12 , "F12" );
86 map (Qt::Key_F13 , "F13" );
87 map (Qt::Key_F14 , "F14" );
88 map (Qt::Key_F15 , "F15" );
89 map (Qt::Key_F16 , "F16" );
90 map (Qt::Key_F17 , "F17" );
91 map (Qt::Key_F18 , "F18" );
92 map (Qt::Key_F19 , "F19" );
93 map (Qt::Key_F20 , "F20" );
94 map (Qt::Key_F21 , "F21" );
95 map (Qt::Key_F22 , "F22" );
96 map (Qt::Key_F23 , "F23" );
97 map (Qt::Key_F24 , "F24" );
98 map (Qt::Key_F25 , "F25" );
99 map (Qt::Key_F26 , "F26" );
100 map (Qt::Key_F27 , "F27" );
101 map (Qt::Key_F28 , "F28" );
102 map (Qt::Key_F29 , "F29" );
103 map (Qt::Key_F30 , "F30" );
104 map (Qt::Key_F31 , "F31" );
105 map (Qt::Key_F32 , "F32" );
106 map (Qt::Key_F33 , "F33" );
107 map (Qt::Key_F34 , "F34" );
108 map (Qt::Key_F35 , "F35" );
109 map (Qt::Key_Insert , "insert" );
110 map (Qt::Key_Delete , "delete" );
111 map (Qt::Key_Home , "home" );
112 map (Qt::Key_End , "end" );
113 map (Qt::Key_PageUp , "pageup" );
114 map (Qt::Key_PageDown , "pagedown" );
115 map (Qt::Key_ScrollLock, "scrolllock" );
116 map (Qt::Key_Pause , "pause" );
117 map (Qt::Key_SysReq , "sysreq" );
118 map (Qt::Key_Stop , "stop" );
119 map (Qt::Key_Menu , "menu" );
120 map (Qt::Key_Print , "print" );
121 map (Qt::Key_Select , "select" );
122 map (Qt::Key_Execute , "execute" );
123 map (Qt::Key_Help , "help" );
125 deadmap (Qt::Key_Dead_Acute , "acute");
126 deadmap (Qt::Key_Dead_Grave , "grave");
127 deadmap (Qt::Key_Dead_Diaeresis , "umlaut");
128 deadmap (Qt::Key_Dead_Circumflex, "hat");
129 deadmap (Qt::Key_Dead_Tilde , "tilde");
131 // map (0x0003 , "K-enter");
132 // map (Qt::Key_Begin , "begin" );
133 // map (Qt::Key_PrintScreen , "printscreen" );
134 // map (Qt::Key_Break , "break" );
135 // map (Qt::Key_User , "user" );
136 // map (Qt::Key_System , "system" );
137 // map (Qt::Key_Reset , "reset" );
138 // map (Qt::Key_ClearLine , "clear" );
139 // map (Qt::Key_ClearDisplay, "cleardisplay" );
140 // map (Qt::Key_InsertLine , "insertline" );
141 // map (Qt::Key_DeleteLine , "deleteline" );
142 // map (Qt::Key_InsertChar , "insert" );
143 // map (Qt::Key_DeleteChar , "delete" );
144 // map (Qt::Key_Prev , "prev" );
145 // map (Qt::Key_Next , "next" );
146 // map (Qt::Key_Undo , "undo" );
147 // map (Qt::Key_Redo , "redo" );
148 // map (Qt::Key_Find , "find" );
149 // map (Qt::Key_ModeSwitchFunctionKey, "modeswitch" );
154 QTMWidget::QTMWidget (simple_widget_rep *_wid)
155 : QTMScrollView (), backingPixmap() {
156 setObjectName("A QTMWidget");
157 setProperty ("texmacs_widget", QVariant::fromValue ((void*) _wid));
158 QAbstractScrollArea::viewport()->setMouseTracking (true);
159 QAbstractScrollArea::viewport()->setFocusPolicy (Qt::StrongFocus);
160 backing_pos = origin;
164 QTMWidget::~QTMWidget () {
167 void
168 QTMWidget::invalidate_rect (int x1, int y1, int x2, int y2) {
169 rectangle r = rectangle (x1, y1, x2, y2);
170 // cout << "invalidating " << r << LF;
171 invalid_regions = invalid_regions | rectangles (r);
174 void
175 QTMWidget::invalidate_all () {
176 QSize sz = QAbstractScrollArea::viewport()->size();
177 //cout << "invalidate all " << LF;
178 invalid_regions = rectangles();
179 invalidate_rect (0, 0, sz.width(), sz.height());
183 basic_renderer_rep*
184 QTMWidget::getRenderer() {
185 #ifdef USE_CAIRO
186 cairo_renderer_rep *ren = the_cairo_renderer ();
187 cairo_surface_t *surf;
188 #ifdef Q_WS_X11
189 //const QX11Info & info = x11Info();//qt_x11Info(this);
190 // Display *dpy = x11Info().display();
191 //backingPixmap = QPixmap(width(),height());
192 //cout << backingPixmap.width() << LF;
193 Display *dpy = QX11Info::display();
194 Drawable drawable = backingPixmap.handle();
195 Visual *visual = (Visual*)(backingPixmap.x11Info().visual());
196 surf = tm_cairo_xlib_surface_create (dpy, drawable, visual,
197 backingPixmap.width (), backingPixmap.height ());
198 #elif defined(Q_WS_MAC)
199 surf = tm_cairo_quartz_surface_create_for_cg_context (
200 (CGContextRef)(this->macCGHandle()), width(), height());
201 #endif
202 cairo_t *ct = tm_cairo_create (surf);
203 ren->begin (ct);
204 tm_cairo_surface_destroy (surf);
205 tm_cairo_destroy (ct);
206 #else
207 qt_renderer_rep * ren = the_qt_renderer();
208 ren->begin(&backingPixmap);
209 #endif
210 return ren;
213 void
214 QTMWidget::repaint_invalid_regions () {
216 // this function is called by the qt_gui::update method to keep the backing
217 // store in sync and propagate the changes to the surface on screen.
218 // first we check that the backing store geometry is right and then we
219 // request to the texmacs canvas widget to repaint the regions which were
220 // marked invalid. Subsequently, for each succesfully repainted region, we
221 // propagate its contents from the backing store to the onscreen surface.
222 // If repaint has been interrupted we do not propagate the changes and proceed
223 // to mark the region invalid again.
225 QRegion qrgn;
226 // qrgn is to keep track of the area on the sceen which needs to be updated
228 // update backing store origin wrt. TeXmacs document
229 if ( backing_pos != origin ) {
231 int dx = origin.x() - backing_pos.x();
232 int dy = origin.y() - backing_pos.y();
233 backing_pos = origin;
235 QPixmap newBackingPixmap (backingPixmap.size());
236 QPainter p (&newBackingPixmap);
237 //newBackingPixmap.fill(Qt::black);
238 p.drawPixmap(-dx,-dy,backingPixmap);
239 p.end();
240 backingPixmap = newBackingPixmap;
241 //cout << "SCROLL CONTENTS BY " << dx << " " << dy << LF;
243 QSize sz = backingPixmap.size();
245 rectangles invalid;
246 while (!is_nil(invalid_regions)) {
247 rectangle r = invalid_regions->item ;
248 // rectangle q = rectangle(r->x1+dx,r->y1-dy,r->x2+dx,r->y2-dy);
249 rectangle q = rectangle(r->x1-dx,r->y1-dy,r->x2-dx,r->y2-dy);
250 invalid = rectangles (q, invalid);
251 //cout << r << " ---> " << q << LF;
252 invalid_regions = invalid_regions->next;
254 invalid_regions= invalid &
255 rectangles(rectangle(0,0,
256 sz.width(),sz.height())) ;
258 if (dy<0)
259 invalidate_rect(0,0,sz.width(),min(sz.height(),-dy));
260 else if (dy>0)
261 invalidate_rect(0,max(0,sz.height()-dy),sz.width(),sz.height());
263 if (dx<0)
264 invalidate_rect(0,0,min(-dx,sz.width()),sz.height());
265 else if (dx>0)
266 invalidate_rect(max(0,sz.width()-dx),0,sz.width(),sz.height());
268 // we cal update now to allow repainint of invalid regions
269 // this cannot be done directly since interpose handler needs
270 // to be run at least once in some situations
271 // (for example when scrolling is initiated by TeXmacs itself)
272 //the_gui->update();
273 // QAbstractScrollArea::viewport()->scroll(-dx,-dy);
274 // QAbstractScrollArea::viewport()->update();
275 qrgn += QRect(QPoint(0,0),sz);
278 // update backing store size
280 QSize _oldSize = backingPixmap.size();
281 QSize _newSize = QAbstractScrollArea::viewport()->size();
282 if (_newSize != _oldSize) {
283 // cout << "RESIZING BITMAP"<< LF;
284 QPixmap newBackingPixmap (_newSize);
285 //QPainter p (&newBackingPixmap);
286 //p.drawPixmap(0,0,backingPixmap);
287 //p.end();
288 backingPixmap = newBackingPixmap;
289 invalidate_all();
290 the_gui -> process_resize(tm_widget(), 0, 0); // FIXME
294 // repaint invalid rectangles
296 rectangles new_regions;
297 if (!is_nil (invalid_regions)) {
298 rectangle lub= least_upper_bound (invalid_regions);
299 if (area (lub) < 1.2 * area (invalid_regions))
300 invalid_regions= rectangles (lub);
302 basic_renderer_rep* ren = getRenderer();
303 tm_widget()->set_current_renderer(ren);
305 SI ox = -backing_pos.x()*PIXEL;
306 SI oy = backing_pos.y()*PIXEL;
308 rectangles rects = invalid_regions;
309 invalid_regions = rectangles();
311 while (!is_nil (rects)) {
312 rectangle r = copy (rects->item);
313 rectangle r0 = rects->item;
314 QRect qr = QRect(r0->x1, r0->y1, r0->x2 - r0->x1, r0->y2 - r0->y1);
315 //cout << "repainting " << r0 << "\n";
316 ren->set_origin(ox,oy);
317 ren->encode (r->x1, r->y1);
318 ren->encode (r->x2, r->y2);
319 ren->set_clipping (r->x1, r->y2, r->x2, r->y1);
320 tm_widget()->handle_repaint (r->x1, r->y2, r->x2, r->y1);
321 if (ren->interrupted ()) {
322 //cout << "interrupted repainting of " << r0 << "\n";
323 //ren->set_color(green);
324 //ren->line(r->x1, r->y1, r->x2, r->y2);
325 //ren->line(r->x1, r->y2, r->x2, r->y1);
326 invalidate_rect (r0->x1, r0->y1, r0->x2, r0->y2);
328 qrgn += qr;
329 rects = rects->next;
332 tm_widget()->set_current_renderer(NULL);
333 ren->end();
334 } // !is_nil(invalid_regions)
338 // propagate immediatly the changes to the screen
339 QAbstractScrollArea::viewport()->repaint(qrgn);
343 void
344 QTMWidget::scrollContentsBy ( int dx, int dy ) {
345 QTMScrollView::scrollContentsBy (dx,dy);
346 // the_gui::update needs to be run as soon as possible to refresh the status
347 // of the widget.
348 needs_update();
351 void
352 QTMWidget::resizeEvent( QResizeEvent* event ) {
353 // cout << "QTMWidget::resizeEvent (" << event->size().width()
354 // << "," << event->size().height() << ")" << LF;
355 QTMScrollView::resizeEvent (event);
356 // the_gui::update needs to be run as soon as possible to refresh the status
357 // of the widget.
358 needs_update();
361 void
362 QTMWidget::paintEvent (QPaintEvent* event) {
363 // In the current implementation repainting take place during the call to
364 // the widget's repaint_invalid_regions method in the_gui::update. All
365 // we have to do is to take the backing store and put it on screen according
366 // to the QRegion marked invalid.
367 // CHECK: Maybe just put onscreen all the region bounding rectangle could not
368 // be so expensive.
371 if (DEBUG_QT)
373 QRect rect = event->rect ();
374 cout << "paintEvent ("<< rect.x() << "," << rect.y()
375 << "," << rect.width() << "," << rect.height()
376 << ") regions:" << event->region().numRects() << LF ;
380 QPainter p (QAbstractScrollArea::viewport());
381 QVector<QRect> rects = event->region().rects();
382 for (int i=0; i< rects.count(); i++) {
383 QRect qr = rects.at(i);
384 p.drawPixmap(qr,backingPixmap,qr);
391 void
392 QTMWidget::keyPressEvent (QKeyEvent* event) {
393 static bool fInit = false;
394 if (!fInit) {
395 if (DEBUG_QT)
396 cout << "Initializing keymap\n";
397 initkeymap();
398 fInit= true;
401 if (DEBUG_QT)
402 cout << "keypressed\n";
403 simple_widget_rep *wid = tm_widget();
404 if (!wid) return;
407 int key = event->key();
408 Qt::KeyboardModifiers mods = event->modifiers();
410 if (DEBUG_QT) {
411 cout << "key : " << key << LF;
412 cout << "text : " << event->text().toAscii().data() << LF;
413 cout << "count: " << event->text().count() << LF;
414 if (mods & Qt::ShiftModifier) cout << "shift\n";
415 if (mods & Qt::MetaModifier) cout << "meta\n";
416 if (mods & Qt::ControlModifier) cout << "control\n";
417 if (mods & Qt::KeypadModifier) cout << "keypad\n";
418 if (mods & Qt::AltModifier) cout << "alt\n";
421 string r;
422 if (qtkeymap->contains (key)) {
423 r = qtkeymap[key];
424 } else if (qtdeadmap->contains (key)) {
425 mods &=~ Qt::ShiftModifier;
426 r = qtdeadmap[key];
427 } else {
428 QString nss = event->text();
429 unsigned short unic= nss.data()[0].unicode();
430 if (unic < 32 && key < 128) {
431 if (((char) key) >= 'A' && ((char) key) <= 'Z') {
432 if ((mods & Qt::ShiftModifier) == 0)
433 key= (int) (key + ((int) 'a') - ((int) 'A'));
435 mods &=~ Qt::ShiftModifier;
436 r= string ((char) key);
437 } else {
438 switch(unic) {
439 case 96: r= "`";
440 // unicode to cork conversion not appropriate for this case...
441 #ifdef Q_WS_MAC
442 // CHECKME: are these two MAC exceptions really needed?
443 if (mods & Qt::AltModifier) r= "grave";
444 #endif
445 break;
446 case 168: r= "umlaut"; break;
447 case 180: r= "acute"; break;
448 // the following combining characters should be caught by qtdeadmap
449 case 0x300: r= "grave"; break;
450 case 0x301: r= "acute"; break;
451 case 0x302: r= "hat"; break;
452 case 0x308: r= "umlaut"; break;
453 case 0x33e: r= "tilde"; break;
454 default:
455 QByteArray buf= nss.toUtf8();
456 string rr (buf.constData(), buf.count());
457 r= utf8_to_cork (rr);
458 if (r == "<less>") r= "<";
459 if (r == "<gtr>") r= ">";
461 #ifdef Q_WS_MAC
462 // CHECKME: are these two MAC exceptions really needed?
463 mods &=~ Qt::AltModifier;
464 #endif
465 mods &=~ Qt::ShiftModifier;
469 if (r == "") return;
471 #ifdef Q_WS_MAC
472 if (mods & Qt::ShiftModifier) r= "S-" * r;
473 if (mods & Qt::MetaModifier) r= "C-" * r; // The "Control" key
474 if (mods & Qt::ControlModifier) r= "Mod1-" * r; // The "Command" key
475 //if (mods & Qt::KeypadModifier) r= "Mod3-" * r;
476 if (mods & Qt::AltModifier) r= "Mod4-" * r;
477 #else
478 if (mods & Qt::ShiftModifier) r= "S-" * r;
479 if (mods & Qt::ControlModifier) r= "C-" * r;
480 if (mods & Qt::AltModifier) r= "Mod1-" * r;
481 //if (mods & Qt::KeypadModifier) r= "Mod3-" * r;
482 if (mods & Qt::MetaModifier) r= "Mod4-" * r; // The "Windows" key
483 #endif
485 if (DEBUG_QT)
486 cout << "key press: " << r << LF;
487 //int start= texmacs_time ();
488 //wid -> handle_keypress (r, texmacs_time());
489 the_gui -> process_keypress (wid, r, texmacs_time());
490 //int end= texmacs_time ();
491 //if (end > start) cout << "Keypress " << end - start << "\n";
492 // the_gui->update (); // FIXME: remove this line when
493 // edit_typeset_rep::get_env_value will be faster
495 // needs_update();
499 static unsigned int
500 mouse_state (QMouseEvent* event, bool flag) {
501 unsigned int i= 0;
502 Qt::MouseButtons bstate= event->buttons ();
503 Qt::MouseButton tstate= event->button ();
504 Qt::KeyboardModifiers kstate= event->modifiers ();
505 if (flag) bstate= bstate | tstate;
506 if ((bstate & Qt::LeftButton ) != 0) i += 1;
507 if ((bstate & Qt::MidButton ) != 0) i += 2;
508 if ((bstate & Qt::RightButton ) != 0) i += 4;
509 if ((bstate & Qt::XButton1 ) != 0) i += 8;
510 if ((bstate & Qt::XButton2 ) != 0) i += 16;
511 #ifdef Q_WS_MAC
512 if ((kstate & Qt::AltModifier ) != 0) i = 2;
513 if ((kstate & Qt::MetaModifier ) != 0) i = 4;
514 if ((kstate & Qt::ShiftModifier ) != 0) i += 256;
515 if ((kstate & Qt::ControlModifier) != 0) i += 2048;
516 #else
517 if ((kstate & Qt::ShiftModifier ) != 0) i += 256;
518 if ((kstate & Qt::ControlModifier) != 0) i += 512;
519 if ((kstate & Qt::AltModifier ) != 0) i += 2048;
520 if ((kstate & Qt::MetaModifier ) != 0) i += 16384;
521 #endif
522 return i;
525 static string
526 mouse_decode (unsigned int mstate) {
527 if (mstate & 1 ) return "left";
528 else if (mstate & 2 ) return "middle";
529 else if (mstate & 4 ) return "right";
530 else if (mstate & 8 ) return "up";
531 else if (mstate & 16) return "down";
532 return "unknown";
535 void
536 QTMWidget::mousePressEvent (QMouseEvent* event) {
537 simple_widget_rep *wid= tm_widget ();
538 if (!wid) return;
539 QPoint point = event->pos() + origin;
540 scale (point);
541 unsigned int mstate= mouse_state (event, false);
542 string s= "press-" * mouse_decode (mstate);
543 the_gui -> process_mouse (wid, s, point.x (), point.y (),
544 mstate, texmacs_time ());
545 // wid -> handle_mouse (s, point.x (), point.y (), mstate, texmacs_time ());
546 if (DEBUG_QT)
547 cout << "mouse event: " << s << " at "
548 << point.x () << ", " << point.y () << LF;
551 void
552 QTMWidget::mouseReleaseEvent (QMouseEvent* event) {
553 simple_widget_rep *wid = tm_widget();
554 if (!wid) return;
555 QPoint point = event->pos() + origin;;
556 scale (point);
557 unsigned int mstate= mouse_state (event, true);
558 string s= "release-" * mouse_decode (mstate);
559 the_gui -> process_mouse (wid, s, point.x (), point.y (),
560 mstate, texmacs_time ());
561 // wid -> handle_mouse (s, point.x (), point.y (), mstate, texmacs_time ());
562 if (DEBUG_QT)
563 cout << "mouse event: " << s << " at "
564 << point.x () << ", " << point.y () << LF;
567 void
568 QTMWidget::mouseMoveEvent (QMouseEvent* event) {
569 simple_widget_rep *wid = tm_widget();
570 if (!wid) return;
571 QPoint point = event->pos() + origin;
572 scale (point);
573 unsigned int mstate= mouse_state (event, false);
574 string s= "move";
575 the_gui -> process_mouse (wid, s, point.x (), point.y (),
576 mstate, texmacs_time ());
577 // wid -> handle_mouse (s, point.x (), point.y (), mstate, texmacs_time ());
578 if (DEBUG_QT)
579 cout << "mouse event: " << s << " at "
580 << point.x () << ", " << point.y () << LF;
584 bool
585 QTMWidget::event (QEvent* event) {
586 if (event->type() == QEvent::KeyPress) {
587 QKeyEvent *ke = static_cast<QKeyEvent*> (event);
588 keyPressEvent (ke);
589 return true;
591 return QTMScrollView::event (event);
595 void
596 QTMWidget::focusInEvent ( QFocusEvent * event ) {
597 if (DEBUG_QT) cout << "FOCUSIN" << LF;
598 simple_widget_rep *wid = tm_widget ();
599 if (wid) {
600 the_gui -> process_keyboard_focus (wid, true, texmacs_time());
601 //wid -> handle_keyboard_focus (true, texmacs_time ());
603 QTMScrollView::focusInEvent (event);
606 void
607 QTMWidget::focusOutEvent ( QFocusEvent * event ) {
608 if (DEBUG_QT) cout << "FOCUSOUT" << LF;
609 simple_widget_rep *wid = tm_widget ();
610 if (wid) {
611 the_gui -> process_keyboard_focus (wid, false, texmacs_time());
612 // wid -> handle_keyboard_focus (false, texmacs_time ());
614 QTMScrollView::focusOutEvent (event);