]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiWorkArea.C
* src/frontends/qt4/GuiWorkArea.[Ch] (focusInEvent, focusOutEvent):
[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 #include <config.h>
13
14 #include "GuiWorkArea.h"
15
16 #include "Application.h"
17 #include "ColorCache.h"
18 #include "QLPainter.h"
19 #include "QLyXKeySym.h"
20 #include "qt_helpers.h"
21
22 #include "LyXView.h"
23
24 #include "BufferView.h"
25 #include "debug.h"
26 #include "funcrequest.h"
27 #include "LColor.h"
28
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, LyXView & lyx_view)
121 : WorkArea(lyx_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
141         synthetic_mouse_event_.timeout.timeout.connect(
142                 boost::bind(&GuiWorkArea::generateSyntheticMouseEvent,
143                             this));
144
145         // Initialize the vertical Scroll Bar
146         QObject::connect(verticalScrollBar(), SIGNAL(actionTriggered(int)),
147                 this, SLOT(adjustViewWithScrollBar(int)));
148
149         // PageStep only depends on the viewport height.
150         verticalScrollBar()->setPageStep(viewport()->height());
151
152         lyxerr[Debug::GUI] << BOOST_CURRENT_FUNCTION
153                 << "\n Area width\t" << width()
154                 << "\n Area height\t" << height()
155                 << "\n viewport width\t" << viewport()->width()
156                 << "\n viewport height\t" << viewport()->height()
157                 << endl;
158
159         if (USE_EVENT_PRUNING) {
160                 // This is the keyboard buffering stuff...
161                 // I don't see any need for this under windows. The keyboard is reactive
162                 // enough...
163
164                 if ( !QObject::connect(&step_timer_, SIGNAL(timeout()),
165                         this, SLOT(keyeventTimeout())) )
166                         lyxerr[Debug::GUI] << "ERROR: keyeventTimeout cannot connect!" << endl;
167
168                 // Start the timer, one-shot.
169                 step_timer_.setSingleShot(true);
170                 step_timer_.start(50);
171         }
172
173         // Enables input methods for asian languages.
174         // Must be set when creating custom text editing widgets.
175         setAttribute(Qt::WA_InputMethodEnabled, true);
176 }
177
178
179 GuiWorkArea::~GuiWorkArea()
180 {
181 }
182
183
184 void GuiWorkArea::setScrollbarParams(int h, int scroll_pos, int scroll_line_step)
185 {
186         verticalScrollBar()->setTracking(false);
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         verticalScrollBar()->setTracking(true);
197 }
198
199
200 void GuiWorkArea::adjustViewWithScrollBar(int)
201 {
202         scrollBufferView(verticalScrollBar()->sliderPosition());
203 }
204
205
206 void GuiWorkArea::dragEnterEvent(QDragEnterEvent * event)
207 {
208         if (event->mimeData()->hasUrls())
209                 event->accept();
210         /// \todo Ask lyx-devel is this is enough:
211         /// if (event->mimeData()->hasFormat("text/plain"))
212         ///     event->acceptProposedAction();
213 }
214
215
216 void GuiWorkArea::dropEvent(QDropEvent* event)
217 {
218         QList<QUrl> files = event->mimeData()->urls();
219         if (files.isEmpty())
220                 return;
221
222         lyxerr[Debug::GUI] << "GuiWorkArea::dropEvent: got URIs!" << endl;
223         for (int i = 0; i!=files.size(); ++i) {
224                 string const file = os::internal_path(fromqstr(files.at(i).toString()));
225                 if (!file.empty())
226                         dispatch(FuncRequest(LFUN_FILE_OPEN, file));
227         }
228 }
229
230
231 void GuiWorkArea::mousePressEvent(QMouseEvent * e)
232 {
233         if (dc_event_.active && dc_event_ == *e) {
234                 dc_event_.active = false;
235                 FuncRequest cmd(LFUN_MOUSE_TRIPLE,
236                         dc_event_.x, dc_event_.y,
237                         q_button_state(dc_event_.state));
238                 dispatch(cmd);
239                 return;
240         }
241
242         FuncRequest const cmd(LFUN_MOUSE_PRESS, e->x(), e->y(),
243                               q_button_state(e->button()));
244         dispatch(cmd);
245 }
246
247
248 void GuiWorkArea::mouseReleaseEvent(QMouseEvent * e)
249 {
250         if (synthetic_mouse_event_.timeout.running())
251                 synthetic_mouse_event_.timeout.stop();
252
253         FuncRequest const cmd(LFUN_MOUSE_RELEASE, e->x(), e->y(),
254                               q_button_state(e->button()));
255         dispatch(cmd);
256 }
257
258
259 void GuiWorkArea::mouseMoveEvent(QMouseEvent * e)
260 {
261         FuncRequest cmd(LFUN_MOUSE_MOTION, e->x(), e->y(),
262                               q_motion_state(e->button()));
263
264         // If we're above or below the work area...
265         if (e->y() <= 20 || e->y() >= viewport()->height() - 20) {
266                 // Make sure only a synthetic event can cause a page scroll,
267                 // so they come at a steady rate:
268                 if (e->y() <= 20)
269                         // _Force_ a scroll up:
270                         cmd.y = -40;
271                 else
272                         cmd.y = viewport()->height();
273                 // Store the event, to be handled when the timeout expires.
274                 synthetic_mouse_event_.cmd = cmd;
275
276                 if (synthetic_mouse_event_.timeout.running())
277                         // Discard the event. Note that it _may_ be handled
278                         // when the timeout expires if
279                         // synthetic_mouse_event_.cmd has not been overwritten.
280                         // Ie, when the timeout expires, we handle the
281                         // most recent event but discard all others that
282                         // occurred after the one used to start the timeout
283                         // in the first place.
284                         return;
285                 else {
286                         synthetic_mouse_event_.restart_timeout = true;
287                         synthetic_mouse_event_.timeout.start();
288                         // Fall through to handle this event...
289                 }
290
291         } else if (synthetic_mouse_event_.timeout.running()) {
292                 // Store the event, to be possibly handled when the timeout
293                 // expires.
294                 // Once the timeout has expired, normal control is returned
295                 // to mouseMoveEvent (restart_timeout = false).
296                 // This results in a much smoother 'feel' when moving the
297                 // mouse back into the work area.
298                 synthetic_mouse_event_.cmd = cmd;
299                 synthetic_mouse_event_.restart_timeout = false;
300                 return;
301         }
302
303         // Has anything changed on-screen since the last QMouseEvent
304         // was received?
305         double const scrollbar_value = verticalScrollBar()->value();
306         if (e->x() != synthetic_mouse_event_.x_old ||
307             e->y() != synthetic_mouse_event_.y_old ||
308             scrollbar_value != synthetic_mouse_event_.scrollbar_value_old) {
309                 // Yes it has. Store the params used to check this.
310                 synthetic_mouse_event_.x_old = e->x();
311                 synthetic_mouse_event_.y_old = e->y();
312                 synthetic_mouse_event_.scrollbar_value_old = scrollbar_value;
313
314                 // ... and dispatch the event to the LyX core.
315                 dispatch(cmd);
316         }
317 }
318
319
320 void GuiWorkArea::wheelEvent(QWheelEvent * e)
321 {
322         // Wheel rotation by one notch results in a delta() of 120 (see
323         // documentation of QWheelEvent)
324         int const lines = qApp->wheelScrollLines() * e->delta() / 120;
325         verticalScrollBar()->setValue(verticalScrollBar()->value() -
326                         lines *  verticalScrollBar()->singleStep());
327         adjustViewWithScrollBar();
328 }
329
330
331 void GuiWorkArea::generateSyntheticMouseEvent()
332 {
333 // Set things off to generate the _next_ 'pseudo' event.
334         if (synthetic_mouse_event_.restart_timeout)
335                 synthetic_mouse_event_.timeout.start();
336
337         // Has anything changed on-screen since the last timeout signal
338         // was received?
339         double const scrollbar_value = verticalScrollBar()->value();
340         if (scrollbar_value != synthetic_mouse_event_.scrollbar_value_old) {
341                 // Yes it has. Store the params used to check this.
342                 synthetic_mouse_event_.scrollbar_value_old = scrollbar_value;
343
344                 // ... and dispatch the event to the LyX core.
345                 dispatch(synthetic_mouse_event_.cmd);
346         }
347 }
348
349
350 void GuiWorkArea::keyPressEvent(QKeyEvent * e)
351 {
352         lyxerr[Debug::KEY] << BOOST_CURRENT_FUNCTION
353                 << " count=" << e->count()
354                 << " text=" << fromqstr(e->text())
355                 << " isAutoRepeat=" << e->isAutoRepeat()
356                 << " key=" << e->key()
357                 << endl;
358
359         if (USE_EVENT_PRUNING) {
360                 keyeventQueue_.push(boost::shared_ptr<QKeyEvent>(new QKeyEvent(*e)));
361         }
362         else {
363                 boost::shared_ptr<QLyXKeySym> sym(new QLyXKeySym);
364                 sym->set(e);
365                 processKeySym(sym, q_key_state(e->modifiers()));
366         }
367 }
368
369
370 // This is used only if USE_EVENT_PRUNING is defined...
371 void GuiWorkArea::keyeventTimeout()
372 {
373         bool handle_autos = true;
374
375         while (!keyeventQueue_.empty()) {
376                 boost::shared_ptr<QKeyEvent> ev = keyeventQueue_.front();
377
378                 // We never handle more than one auto repeated
379                 // char in a list of queued up events.
380                 if (!handle_autos && ev->isAutoRepeat()) {
381                         keyeventQueue_.pop();
382                         continue;
383                 }
384
385                 boost::shared_ptr<QLyXKeySym> sym(new QLyXKeySym);
386                 sym->set(ev.get());
387
388                 lyxerr[Debug::GUI] << BOOST_CURRENT_FUNCTION
389                                    << " count=" << ev->count()
390                                    << " text=" <<  fromqstr(ev->text())
391                                    << " isAutoRepeat=" << ev->isAutoRepeat()
392                                    << " key=" << ev->key()
393                                    << endl;
394
395                 processKeySym(sym, q_key_state(ev->modifiers()));
396                 keyeventQueue_.pop();
397
398                 handle_autos = false;
399         }
400
401         // Restart the timer.
402         step_timer_.setSingleShot(true);
403         step_timer_.start(25);
404 }
405
406
407 void GuiWorkArea::mouseDoubleClickEvent(QMouseEvent * e)
408 {
409         dc_event_ = double_click(e);
410
411         if (!dc_event_.active)
412                 return;
413
414         dc_event_.active = false;
415
416         FuncRequest cmd(LFUN_MOUSE_DOUBLE,
417                 dc_event_.x, dc_event_.y,
418                 q_button_state(dc_event_.state));
419         dispatch(cmd);
420 }
421
422
423 void GuiWorkArea::resizeEvent(QResizeEvent *)
424 {
425         verticalScrollBar()->setPageStep(viewport()->height());
426         paint_device_ = QPixmap(viewport()->width(), viewport()->height());
427         resizeBufferView();
428 }
429
430
431 void GuiWorkArea::update(int x, int y, int w, int h)
432 {
433         viewport()->update(x, y, w, h);
434 }
435
436
437 void GuiWorkArea::paintEvent(QPaintEvent * e)
438 {
439         /*
440         lyxerr[Debug::GUI] << BOOST_CURRENT_FUNCTION
441                 << "\n QWidget width\t" << this->width()
442                 << "\n QWidget height\t" << this->height()
443                 << "\n viewport width\t" << viewport()->width()
444                 << "\n viewport height\t" << viewport()->height()
445                 << "\n pixmap width\t" << pixmap_->width()
446                 << "\n pixmap height\t" << pixmap_->height()
447                 << "\n QPaintEvent x\t" << e->rect().x()
448                 << "\n QPaintEvent y\t" << e->rect().y()
449                 << "\n QPaintEvent w\t" << e->rect().width()
450                 << "\n QPaintEvent h\t" << e->rect().height()
451                 << endl;
452         */
453
454         QPainter q(viewport());
455         q.drawPixmap(e->rect(), paint_device_, e->rect());
456
457         if (show_vcursor_)
458                 q.drawPixmap(cursor_x_, cursor_y_, vcursor_);
459
460         if (show_hcursor_)
461                 q.drawPixmap(cursor_x_, cursor_y_ + cursor_h_ - 1, hcursor_);
462 }
463
464
465 QPixmap GuiWorkArea::copyScreen(int x, int y, int w, int h) const
466 {
467         return paint_device_.copy(x, y, w, h);
468 }
469
470
471 void GuiWorkArea::drawScreen(int x, int y, QPixmap pixmap)
472 {
473         QPainter q(&paint_device_);
474         q.drawPixmap(x, y, pixmap);
475         update(x, y, pixmap.width(), pixmap.height());
476 }
477
478
479 void GuiWorkArea::expose(int x, int y, int w, int h)
480 {
481         /*
482         if (x == 0 && y == 0 && w == viewport()->width() && h == viewport()->height()) {
483                 viewport()->repaint(x, y, w, h);
484                 return;
485         }
486         */
487
488         update(x, y, w, h);
489 }
490
491
492 void GuiWorkArea::showCursor(int x, int y, int h, CursorShape shape)
493 {
494         if (!qApp->focusWidget())
495                 return;
496
497         show_vcursor_ = true;
498
499         QColor const & required_color = lcolorcache.get(LColor::cursor);
500
501         if (x==cursor_x_ && y==cursor_y_ && h==cursor_h_
502                 && cursor_color_ == required_color
503                 && cursor_shape_ == shape) {
504                 show_hcursor_ = lshape_cursor_;
505                 update(cursor_x_, cursor_y_, cursor_w_, cursor_h_);
506                 return;
507         }
508
509         // Cache the dimensions of the cursor.
510         cursor_x_ = x;
511         cursor_y_ = y;
512         cursor_h_ = h;
513         cursor_color_ = required_color;
514         cursor_shape_ = shape;
515
516         switch (cursor_shape_) {
517         case BAR_SHAPE:
518                 // FIXME the cursor width shouldn't be hard-coded!
519                 cursor_w_ = 2;
520                 lshape_cursor_ = false;
521                 break;
522         case L_SHAPE:
523                 cursor_w_ = cursor_h_ / 3;
524                 lshape_cursor_ = true;
525                 break;
526         case REVERSED_L_SHAPE:
527                 cursor_w_ = cursor_h_ / 3;
528                 cursor_x_ -= cursor_w_ - 1;
529                 lshape_cursor_ = true;
530                 break;
531         }
532
533         // We cache two pixmaps:
534         // 1 the vertical line of the cursor.
535         // 2 the horizontal line of the L-shaped cursor (if necessary).
536
537         // Draw the new (vertical) cursor.
538         vcursor_ = QPixmap(cursor_w_, cursor_h_);
539         vcursor_.fill(cursor_color_);
540
541         // Draw the new (horizontal) cursor if necessary.
542         if (lshape_cursor_) {
543                 hcursor_ = QPixmap(cursor_w_, 1);
544                 hcursor_.fill(cursor_color_);
545                 show_hcursor_ = true;
546         }
547
548         update(cursor_x_, cursor_y_, cursor_w_, cursor_h_);
549 }
550
551
552 void GuiWorkArea::removeCursor()
553 {
554         show_vcursor_ = false;
555         show_hcursor_ = false;
556
557         update(cursor_x_, cursor_y_, cursor_w_, cursor_h_);
558 }
559
560
561 void GuiWorkArea::inputMethodEvent(QInputMethodEvent * e)
562 {
563         QString const & text = e->commitString();
564         if (!text.isEmpty()) {
565
566                 lyxerr[Debug::KEY] << BOOST_CURRENT_FUNCTION
567                         << " preeditString =" << fromqstr(e->preeditString())
568                         << " commitString  =" << fromqstr(e->commitString())
569                         << endl;
570
571                 int key = 0;
572                 // needed to make math superscript work on some systems
573                 // ideally, such special coding should not be necessary
574                 if (text == "^")
575                         key = Qt::Key_AsciiCircum;
576                 // FIXME: Needs for investigation, this key is not really used,
577                 // the ctor below just check if key is different from 0.
578                 QKeyEvent ev(QEvent::KeyPress, key,
579                         Qt::NoModifier, text);
580                 keyPressEvent(&ev);
581         }
582         e->accept();
583 }
584
585
586 void GuiWorkArea::focusInEvent(QFocusEvent * ev)
587 {
588         QAbstractScrollArea::focusInEvent(ev);
589         lyx_view_.updateToolbars();
590 }
591
592
593 void GuiWorkArea::focusOutEvent(QFocusEvent * ev)
594 {
595         QAbstractScrollArea::focusOutEvent(ev);
596         lyx_view_.updateToolbars();
597 }
598
599 } // namespace frontend
600 } // namespace lyx
601
602 #include "GuiWorkArea_moc.cpp"