]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiWorkArea.C
GuiWorkArea: #undef QT3_SUPPORT
[lyx.git] / src / frontends / qt4 / GuiWorkArea.C
1 /**
2  * \file GuiWorkArea.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author John Levon
7  * \author Abdelrazak Younes
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 #undef QT3_SUPPORT
13
14 #include <config.h>
15
16
17 #include "GuiWorkArea.h"
18
19 #include "QLPainter.h"
20 #include "QLyXKeySym.h"
21
22 #include "ColorCache.h"
23 #include "qt_helpers.h"
24 #include "Application.h"
25 #include "BufferView.h"
26 #include "debug.h"
27 #include "funcrequest.h"
28 #include "LColor.h"
29 #include "support/os.h"
30
31 #include <QLayout>
32 #include <QMainWindow>
33 #include <QMimeData>
34 #include <QUrl>
35 #include <QDragEnterEvent>
36 #include <QPixmap>
37 #include <QPainter>
38 #include <QScrollBar>
39
40 #include <boost/bind.hpp>
41 #include <boost/current_function.hpp>
42
43 // Abdel (26/06/2006):
44 // On windows-XP the UserGuide PageDown scroll test is faster without event pruning (16 s)
45 // than with it (23 s).
46 #ifdef Q_WS_WIN
47  #define USE_EVENT_PRUNING 0
48 #else
49  #define USE_EVENT_PRUNING 0
50 #endif
51
52 using std::endl;
53 using std::string;
54
55 namespace os = lyx::support::os;
56
57 namespace {
58
59 /// return the LyX key state from Qt's
60 key_modifier::state q_key_state(Qt::KeyboardModifiers state)
61 {
62         key_modifier::state k = key_modifier::none;
63         if (state & Qt::ControlModifier)
64                 k |= key_modifier::ctrl;
65         if (state & Qt::ShiftModifier)
66                 k |= key_modifier::shift;
67         if (state & Qt::AltModifier || state & Qt::MetaModifier)
68                 k |= key_modifier::alt;
69         return k;
70 }
71
72
73 /// return the LyX mouse button state from Qt's
74 mouse_button::state q_button_state(Qt::MouseButton button)
75 {
76         mouse_button::state b = mouse_button::none;
77         switch (button) {
78                 case Qt::LeftButton:
79                         b = mouse_button::button1;
80                         break;
81                 case Qt::MidButton:
82                         b = mouse_button::button2;
83                         break;
84                 case Qt::RightButton:
85                         b = mouse_button::button3;
86                         break;
87                 default:
88                         break;
89         }
90         return b;
91 }
92
93
94 /// return the LyX mouse button state from Qt's
95 mouse_button::state q_motion_state(Qt::MouseButton state)
96 {
97         mouse_button::state b = mouse_button::none;
98         if (state & Qt::LeftButton)
99                 b |= mouse_button::button1;
100         if (state & Qt::MidButton)
101                 b |= mouse_button::button2;
102         if (state & Qt::RightButton)
103                 b |= mouse_button::button3;
104         return b;
105 }
106
107 } // namespace anon
108
109 namespace lyx {
110 namespace frontend {
111
112 // This is a 'heartbeat' generating synthetic mouse move events when the
113 // cursor is at the top or bottom edge of the viewport. One scroll per 0.2 s
114 SyntheticMouseEvent::SyntheticMouseEvent()
115         : timeout(200), restart_timeout(true),
116           x_old(-1), y_old(-1), scrollbar_value_old(-1.0)
117 {}
118
119
120 GuiWorkArea::GuiWorkArea(int w, int h, QWidget * parent, BufferView * buffer_view)
121 : QAbstractScrollArea(parent), WorkArea(buffer_view), painter_(this)
122 {
123         setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
124         setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
125
126         setAcceptDrops(true);
127
128         setMinimumSize(100, 70);
129
130         viewport()->setAutoFillBackground(false);
131         viewport()->setAttribute(Qt::WA_OpaquePaintEvent);
132
133         viewport()->setFocusPolicy(Qt::WheelFocus);
134         viewport()->setFocus();
135         setFocusPolicy(Qt::WheelFocus);
136
137         viewport()->setCursor(Qt::IBeamCursor);
138
139         resize(w, h);
140         show();
141         workWidth_ = w;
142         workHeight_ = h;
143
144         synthetic_mouse_event_.timeout.timeout.connect(
145                 boost::bind(&GuiWorkArea::generateSyntheticMouseEvent,
146                             this));
147
148         // Initialize the vertical Scroll Bar
149         QObject::connect(verticalScrollBar(), SIGNAL(actionTriggered(int)),
150                 this, SLOT(adjustViewWithScrollBar(int)));
151
152         // PageStep only depends on the viewport height.
153         verticalScrollBar()->setPageStep(workHeight_);
154
155         lyxerr[Debug::GUI] << BOOST_CURRENT_FUNCTION
156                 << "\n Area width\t" << width()
157                 << "\n Area height\t" << height()
158                 << "\n viewport width\t" << viewport()->width()
159                 << "\n viewport height\t" << viewport()->height()
160                 << endl;
161
162         if (USE_EVENT_PRUNING) {
163                 // This is the keyboard buffering stuff...
164                 // I don't see any need for this under windows. The keyboard is reactive
165                 // enough...
166
167                 if ( !QObject::connect(&step_timer_, SIGNAL(timeout()),
168                         this, SLOT(keyeventTimeout())) )
169                         lyxerr[Debug::GUI] << "ERROR: keyeventTimeout cannot connect!" << endl;
170
171                 // Start the timer, one-shot.
172                 step_timer_.setSingleShot(true);
173                 step_timer_.start(50);
174         }
175
176         // Enables input methods for asian languages.
177         // Must be set when creating custom text editing widgets.
178         setAttribute(Qt::WA_InputMethodEnabled, true);
179 }
180
181 GuiWorkArea::~GuiWorkArea()
182 {
183 }
184
185
186 void GuiWorkArea::setScrollbarParams(int h, int scroll_pos, int scroll_line_step)
187 {
188         // do what cursor movement does (some grey)
189         h += height() / 4;
190         int scroll_max_ = std::max(0, h - height());
191
192         verticalScrollBar()->setRange(0, scroll_max_);
193         verticalScrollBar()->setSliderPosition(scroll_pos);
194         verticalScrollBar()->setSingleStep(scroll_line_step);
195 }
196
197
198 void GuiWorkArea::adjustViewWithScrollBar(int)
199 {
200         /*
201         lyxerr[Debug::GUI] << BOOST_CURRENT_FUNCTION
202                 << " verticalScrollBar val=" << verticalScrollBar()->value()
203                 << " verticalScrollBar pos=" << verticalScrollBar()->sliderPosition()
204                 << " min=" << verticalScrollBar()->minimum()
205                 << " max=" << verticalScrollBar()->maximum()
206                 << " pagestep=" << verticalScrollBar()->pageStep()
207                 << " linestep=" << verticalScrollBar()->lineStep()
208                 << endl;
209         */
210         buffer_view_->scrollDocView(verticalScrollBar()->sliderPosition());
211         redraw();
212 }
213
214
215 void GuiWorkArea::dragEnterEvent(QDragEnterEvent * event)
216 {
217         if (event->mimeData()->hasUrls())
218                 event->accept();
219         /// \todo Ask lyx-devel is this is enough:
220         /// if (event->mimeData()->hasFormat("text/plain"))
221         ///     event->acceptProposedAction();
222 }
223
224
225 void GuiWorkArea::dropEvent(QDropEvent* event)
226 {
227         QList<QUrl> files = event->mimeData()->urls();
228         if (files.isEmpty())
229                 return;
230
231         lyxerr[Debug::GUI] << "GuiWorkArea::dropEvent: got URIs!" << endl;
232         for (int i = 0; i!=files.size(); ++i) {
233                 string const file = os::internal_path(fromqstr(files.at(i).toString()));
234                 if (!file.empty())
235                         dispatch(FuncRequest(LFUN_FILE_OPEN, file));
236         }
237 }
238
239
240 void GuiWorkArea::mousePressEvent(QMouseEvent * e)
241 {
242         if (dc_event_.active && dc_event_ == *e) {
243                 dc_event_.active = false;
244                 FuncRequest cmd(LFUN_MOUSE_TRIPLE,
245                         dc_event_.x, dc_event_.y,
246                         q_button_state(dc_event_.state));
247                 dispatch(cmd);
248                 return;
249         }
250
251         FuncRequest const cmd(LFUN_MOUSE_PRESS, e->x(), e->y(),
252                               q_button_state(e->button()));
253         dispatch(cmd);
254 }
255
256
257 void GuiWorkArea::mouseReleaseEvent(QMouseEvent * e)
258 {
259         if (synthetic_mouse_event_.timeout.running())
260                 synthetic_mouse_event_.timeout.stop();
261
262         FuncRequest const cmd(LFUN_MOUSE_RELEASE, e->x(), e->y(),
263                               q_button_state(e->button()));
264         dispatch(cmd);
265 }
266
267
268 void GuiWorkArea::mouseMoveEvent(QMouseEvent * e)
269 {
270         FuncRequest cmd(LFUN_MOUSE_MOTION, e->x(), e->y(),
271                               q_motion_state(e->button()));
272
273         // If we're above or below the work area...
274         if (e->y() <= 20 || e->y() >= viewport()->height() - 20) {
275                 // Make sure only a synthetic event can cause a page scroll,
276                 // so they come at a steady rate:
277                 if (e->y() <= 20)
278                         // _Force_ a scroll up:
279                         cmd.y = -40;
280                 else
281                         cmd.y = viewport()->height();
282                 // Store the event, to be handled when the timeout expires.
283                 synthetic_mouse_event_.cmd = cmd;
284
285                 if (synthetic_mouse_event_.timeout.running())
286                         // Discard the event. Note that it _may_ be handled
287                         // when the timeout expires if
288                         // synthetic_mouse_event_.cmd has not been overwritten.
289                         // Ie, when the timeout expires, we handle the
290                         // most recent event but discard all others that
291                         // occurred after the one used to start the timeout
292                         // in the first place.
293                         return;
294                 else {
295                         synthetic_mouse_event_.restart_timeout = true;
296                         synthetic_mouse_event_.timeout.start();
297                         // Fall through to handle this event...
298                 }
299
300         } else if (synthetic_mouse_event_.timeout.running()) {
301                 // Store the event, to be possibly handled when the timeout
302                 // expires.
303                 // Once the timeout has expired, normal control is returned
304                 // to mouseMoveEvent (restart_timeout = false).
305                 // This results in a much smoother 'feel' when moving the
306                 // mouse back into the work area.
307                 synthetic_mouse_event_.cmd = cmd;
308                 synthetic_mouse_event_.restart_timeout = false;
309                 return;
310         }
311
312         // Has anything changed on-screen since the last QMouseEvent
313         // was received?
314         double const scrollbar_value = verticalScrollBar()->value();
315         if (e->x() != synthetic_mouse_event_.x_old ||
316             e->y() != synthetic_mouse_event_.y_old ||
317             scrollbar_value != synthetic_mouse_event_.scrollbar_value_old) {
318                 // Yes it has. Store the params used to check this.
319                 synthetic_mouse_event_.x_old = e->x();
320                 synthetic_mouse_event_.y_old = e->y();
321                 synthetic_mouse_event_.scrollbar_value_old = scrollbar_value;
322
323                 // ... and dispatch the event to the LyX core.
324                 dispatch(cmd);
325         }
326 }
327
328
329 void GuiWorkArea::wheelEvent(QWheelEvent * e)
330 {
331         // Wheel rotation by one notch results in a delta() of 120 (see
332         // documentation of QWheelEvent)
333         int const lines = qApp->wheelScrollLines() * e->delta() / 120;
334         verticalScrollBar()->setValue(verticalScrollBar()->value() -
335                         lines *  verticalScrollBar()->singleStep());
336         adjustViewWithScrollBar();
337 }
338
339
340 void GuiWorkArea::generateSyntheticMouseEvent()
341 {
342         // Set things off to generate the _next_ 'pseudo' event.
343         if (synthetic_mouse_event_.restart_timeout)
344                 synthetic_mouse_event_.timeout.start();
345
346         // Has anything changed on-screen since the last timeout signal
347         // was received?
348         double const scrollbar_value = verticalScrollBar()->value();
349         if (scrollbar_value != synthetic_mouse_event_.scrollbar_value_old) {
350                 // Yes it has. Store the params used to check this.
351                 synthetic_mouse_event_.scrollbar_value_old = scrollbar_value;
352
353                 // ... and dispatch the event to the LyX core.
354                 dispatch(synthetic_mouse_event_.cmd);
355         }
356 }
357
358
359 void GuiWorkArea::keyPressEvent(QKeyEvent * e)
360 {
361         lyxerr[Debug::KEY] << BOOST_CURRENT_FUNCTION
362                 << " count=" << e->count()
363                 << " text=" << fromqstr(e->text())
364                 << " isAutoRepeat=" << e->isAutoRepeat()
365                 << " key=" << e->key()
366                 << endl;
367
368         if (USE_EVENT_PRUNING) {
369                 keyeventQueue_.push(boost::shared_ptr<QKeyEvent>(new QKeyEvent(*e)));
370         }
371         else {
372                 boost::shared_ptr<QLyXKeySym> sym(new QLyXKeySym);
373                 sym->set(e);
374                 processKeySym(sym, q_key_state(e->modifiers()));
375         }
376 }
377
378
379 // This is used only if USE_EVENT_PRUNING is defined...
380 void GuiWorkArea::keyeventTimeout()
381 {
382         bool handle_autos = true;
383
384         while (!keyeventQueue_.empty()) {
385                 boost::shared_ptr<QKeyEvent> ev = keyeventQueue_.front();
386
387                 // We never handle more than one auto repeated
388                 // char in a list of queued up events.
389                 if (!handle_autos && ev->isAutoRepeat()) {
390                         keyeventQueue_.pop();
391                         continue;
392                 }
393
394                 boost::shared_ptr<QLyXKeySym> sym(new QLyXKeySym);
395                 sym->set(ev.get());
396
397                 lyxerr[Debug::GUI] << BOOST_CURRENT_FUNCTION
398                                    << " count=" << ev->count()
399                                    << " text=" <<  fromqstr(ev->text())
400                                    << " isAutoRepeat=" << ev->isAutoRepeat()
401                                    << " key=" << ev->key()
402                                    << endl;
403
404                 processKeySym(sym, q_key_state(ev->modifiers()));
405                 keyeventQueue_.pop();
406
407                 handle_autos = false;
408         }
409
410         // Restart the timer.
411         step_timer_.setSingleShot(true);
412         step_timer_.start(25);
413 }
414
415
416 void GuiWorkArea::mouseDoubleClickEvent(QMouseEvent * e)
417 {
418         dc_event_ = double_click(e);
419
420         if (!dc_event_.active)
421                 return;
422
423         dc_event_.active = false;
424
425         FuncRequest cmd(LFUN_MOUSE_DOUBLE,
426                 dc_event_.x, dc_event_.y,
427                 q_button_state(dc_event_.state));
428         dispatch(cmd);
429 }
430
431
432 void GuiWorkArea::resizeEvent(QResizeEvent *)
433 {
434         workWidth_ = viewport()->width();
435         workHeight_ = viewport()->height();
436
437         verticalScrollBar()->setPageStep(viewport()->height());
438
439 //      screen_device_ = QPixmap(viewport()->width(), viewport()->height());
440 //      paint_device_ = QImage(viewport()->width(), viewport()->height(), QImage::Format_RGB32);
441         paint_device_ = QPixmap(viewport()->width(), viewport()->height());
442
443         resizeBufferView();
444
445         /*
446         lyxerr[Debug::GUI] << BOOST_CURRENT_FUNCTION
447                 << "\n QWidget width\t" << this->QWidget::width()
448                 << "\n QWidget height\t" << this->QWidget::height()
449                 << "\n viewport width\t" << viewport()->width()
450                 << "\n viewport height\t" << viewport()->height()
451                 << "\n QResizeEvent rect left\t" << rect().left()
452                 << "\n QResizeEvent rect right\t" << rect().right()
453                 << endl;
454                 */
455 }
456
457
458 void GuiWorkArea::update(int x, int y, int w, int h)
459 {
460         //screen_device_.fromImage(paint_device_);
461         //QPainter q(&screen_device_);
462         //q.drawImage(x, y, paint_device_.copy(x, y, w, h));
463
464         viewport()->update(x, y, w, h);
465 }
466
467
468 void GuiWorkArea::paintEvent(QPaintEvent * e)
469 {
470         /*
471         lyxerr[Debug::GUI] << BOOST_CURRENT_FUNCTION
472                 << "\n QWidget width\t" << this->width()
473                 << "\n QWidget height\t" << this->height()
474                 << "\n viewport width\t" << viewport()->width()
475                 << "\n viewport height\t" << viewport()->height()
476                 << "\n pixmap width\t" << pixmap_->width()
477                 << "\n pixmap height\t" << pixmap_->height()
478                 << "\n QPaintEvent x\t" << e->rect().x()
479                 << "\n QPaintEvent y\t" << e->rect().y()
480                 << "\n QPaintEvent w\t" << e->rect().width()
481                 << "\n QPaintEvent h\t" << e->rect().height()
482                 << endl;
483         */
484
485         QPainter q(viewport());
486         q.drawPixmap(e->rect(), paint_device_, e->rect());
487
488         if (show_vcursor_)
489                 q.drawPixmap(cursor_x_, cursor_y_, vcursor_);
490
491         if (show_hcursor_)
492                 q.drawPixmap(cursor_x_, cursor_y_ + cursor_h_ - 1, hcursor_);
493
494         buffer_view_->updateScrollbar();
495
496         ScrollbarParameters const & scroll_ = buffer_view_->scrollbarParameters();
497
498         verticalScrollBar()->setTracking(false);
499         setScrollbarParams(scroll_.height, scroll_.position,
500                 scroll_.lineScrollHeight);
501         verticalScrollBar()->setTracking(true);
502 }
503
504
505 QPixmap GuiWorkArea::copyScreen(int x, int y, int w, int h) const
506 {
507         return paint_device_.copy(x, y, w, h);
508 }
509
510
511 void GuiWorkArea::drawScreen(int x, int y, QPixmap pixmap)
512 {
513         QPainter q(&paint_device_);
514         q.drawPixmap(x, y, pixmap);
515         update(x, y, pixmap.width(), pixmap.height());
516 }
517
518
519 void GuiWorkArea::expose(int x, int y, int w, int h)
520 {
521         /*
522         if (x == 0 && y == 0 && w == viewport()->width() && h == viewport()->height()) {
523                 viewport()->repaint(x, y, w, h);
524                 return;
525         }
526         */
527
528         update(x, y, w, h);
529 }
530
531
532 void GuiWorkArea::showCursor(int x, int y, int h, CursorShape shape)
533 {
534         if (!qApp->focusWidget())
535                 return;
536
537         show_vcursor_ = true;
538
539         QColor const & required_color = lcolorcache.get(LColor::cursor);
540
541         if (x==cursor_x_ && y==cursor_y_ && h==cursor_h_
542                 && cursor_color_ == required_color
543                 && cursor_shape_ == shape) {
544                 show_hcursor_ = lshape_cursor_;
545                 update(cursor_x_, cursor_y_, cursor_w_, cursor_h_);
546                 return;
547         }
548
549         // Cache the dimensions of the cursor.
550         cursor_x_ = x;
551         cursor_y_ = y;
552         cursor_h_ = h;
553         cursor_color_ = required_color;
554         cursor_shape_ = shape;
555
556         switch (cursor_shape_) {
557         case BAR_SHAPE:
558                 // FIXME the cursor width shouldn't be hard-coded!
559                 cursor_w_ = 2;
560                 lshape_cursor_ = false;
561                 break;
562         case L_SHAPE:
563                 cursor_w_ = cursor_h_ / 3;
564                 lshape_cursor_ = true;
565                 break;
566         case REVERSED_L_SHAPE:
567                 cursor_w_ = cursor_h_ / 3;
568                 cursor_x_ -= cursor_w_ - 1;
569                 lshape_cursor_ = true;
570                 break;
571         }
572
573         // We cache two pixmaps:
574         // 1 the vertical line of the cursor.
575         // 2 the horizontal line of the L-shaped cursor (if necessary).
576
577         // Draw the new (vertical) cursor.
578         vcursor_ = QPixmap(cursor_w_, cursor_h_);
579         vcursor_.fill(cursor_color_);
580
581         // Draw the new (horizontal) cursor if necessary.
582         if (lshape_cursor_) {
583                 hcursor_ = QPixmap(cursor_w_, 1);
584                 hcursor_.fill(cursor_color_);
585                 show_hcursor_ = true;
586         }
587
588         update(cursor_x_, cursor_y_, cursor_w_, cursor_h_);
589 }
590
591
592 void GuiWorkArea::removeCursor()
593 {
594         show_vcursor_ = false;
595         show_hcursor_ = false;
596
597         update(cursor_x_, cursor_y_, cursor_w_, cursor_h_);
598 }
599
600
601 void GuiWorkArea::inputMethodEvent(QInputMethodEvent * e)
602 {
603         QString const & text = e->commitString();
604         if (!text.isEmpty()) {
605
606                 lyxerr[Debug::KEY] << BOOST_CURRENT_FUNCTION
607                         << " preeditString =" << fromqstr(e->preeditString())
608                         << " commitString  =" << fromqstr(e->commitString())
609                         << endl;
610
611                 int key = 0;
612                 // needed to make math superscript work on some systems
613                 // ideally, such special coding should not be necessary
614                 if (text == "^")
615                         key = Qt::Key_AsciiCircum;
616                 QKeyEvent ev(QEvent::KeyPress, key, 0, text);
617                 keyPressEvent(&ev);
618         }
619         e->accept();
620 }
621
622
623 } // namespace frontend
624 } // namespace lyx
625
626 #include "GuiWorkArea_moc.cpp"