]> git.lyx.org Git - lyx.git/blob - src/frontends/qt3/QContentPane.C
Extracted from 14281
[lyx.git] / src / frontends / qt3 / QContentPane.C
1 /**
2  * \file QContentPane.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  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "BufferView.h"
14 #include "frontends/LyXView.h"
15
16 // Qt defines a macro 'signals' that clashes with a boost namespace.
17 // All is well if the namespace is visible first.
18 #include "QWorkArea.h"
19
20 #include "QContentPane.h"
21 #include "QLyXKeySym.h"
22
23 #include <qapplication.h>
24 #include <qpainter.h>
25
26 #include <boost/bind.hpp>
27
28 namespace {
29
30 /// return the LyX key state from Qt's
31 key_modifier::state q_key_state(Qt::ButtonState state)
32 {
33         key_modifier::state k = key_modifier::none;
34         if (state & Qt::ControlButton)
35                 k |= key_modifier::ctrl;
36         if (state & Qt::ShiftButton)
37                 k |= key_modifier::shift;
38         if (state & Qt::AltButton)
39                 k |= key_modifier::alt;
40         return k;
41 }
42
43
44 /// return the LyX mouse button state from Qt's
45 mouse_button::state q_button_state(Qt::ButtonState button)
46 {
47         mouse_button::state b = mouse_button::none;
48         switch (button) {
49                 case Qt::LeftButton:
50                         b = mouse_button::button1;
51                         break;
52                 case Qt::MidButton:
53                         b = mouse_button::button2;
54                         break;
55                 case Qt::RightButton:
56                         b = mouse_button::button3;
57                         break;
58                 default:
59                         break;
60         }
61         return b;
62 }
63
64
65 /// return the LyX mouse button state from Qt's
66 mouse_button::state q_motion_state(Qt::ButtonState state)
67 {
68         mouse_button::state b = mouse_button::none;
69         if (state & Qt::LeftButton)
70                 b |= mouse_button::button1;
71         if (state & Qt::MidButton)
72                 b |= mouse_button::button2;
73         if (state & Qt::RightButton)
74                 b |= mouse_button::button3;
75         return b;
76 }
77
78 } // namespace anon
79
80
81 namespace lyx {
82 namespace frontend {
83
84 // This is a 'heartbeat' generating synthetic mouse move events when the
85 // cursor is at the top or bottom edge of the viewport. One scroll per 0.2 s
86 SyntheticMouseEvent::SyntheticMouseEvent()
87         : timeout(200), restart_timeout(true),
88           x_old(-1), y_old(-1), scrollbar_value_old(-1.0)
89 {}
90
91
92 QContentPane::QContentPane(QWorkArea * parent)
93         : QWidget(parent, "content_pane", WRepaintNoErase),
94           track_scrollbar_(true), wa_(parent)
95 {
96         synthetic_mouse_event_.timeout.timeout.connect(
97                 boost::bind(&QContentPane::generateSyntheticMouseEvent,
98                             this));
99
100         connect(&step_timer_, SIGNAL(timeout()), SLOT(keyeventTimeout()));
101
102         setFocusPolicy(QWidget::WheelFocus);
103         setFocus();
104         setCursor(ibeamCursor);
105 #if USE_INPUT_METHODS
106         // to make qt-immodule work
107         setInputMethodEnabled(true);
108 #endif
109
110         // stupid moc strikes again
111         connect(wa_->scrollbar_, SIGNAL(valueChanged(int)),
112                 this, SLOT(scrollBarChanged(int)));
113
114         // Start the timer, one-shot.
115         step_timer_.start(50, true);
116 }
117
118
119 #if USE_INPUT_METHODS
120 // to make qt-immodule work
121 void QContentPane::imStartEvent(QIMEvent *e)
122 {
123         e->accept();
124 }
125
126
127 void QContentPane::imComposeEvent(QIMEvent *e)
128 {
129         e->accept();
130 }
131
132
133 void QContentPane::imEndEvent(QIMEvent *e)
134 {
135         QString const text = e->text();
136         if (!text.isEmpty()) {
137                 int key = 0;
138                 // needed to make math superscript work on some systems
139                 // ideally, such special coding should not be necessary
140                 if (text == "^")
141                         key = Qt::Key_AsciiCircum;
142                 QKeyEvent ev(QEvent::KeyPress, key, *text.ascii(), 0, text);
143                 keyPressEvent(&ev);
144         }
145         e->accept();
146 }
147 #endif
148
149
150 void QContentPane::generateSyntheticMouseEvent()
151 {
152         // Set things off to generate the _next_ 'pseudo' event.
153         if (synthetic_mouse_event_.restart_timeout)
154                 synthetic_mouse_event_.timeout.start();
155
156         // Has anything changed on-screen since the last timeout signal
157         // was received?
158         double const scrollbar_value = wa_->scrollbar_->value();
159         if (scrollbar_value != synthetic_mouse_event_.scrollbar_value_old) {
160                 // Yes it has. Store the params used to check this.
161                 synthetic_mouse_event_.scrollbar_value_old = scrollbar_value;
162
163                 // ... and dispatch the event to the LyX core.
164                 wa_->view().view()->workAreaDispatch(synthetic_mouse_event_.cmd);
165         }
166 }
167
168
169 void QContentPane::scrollBarChanged(int val)
170 {
171         if (track_scrollbar_)
172                 wa_->view().view()->scrollDocView(val);
173 }
174
175
176 void QContentPane::mousePressEvent(QMouseEvent * e)
177 {
178         if (dc_event_.active && dc_event_ == *e) {
179                 dc_event_.active = false;
180                 FuncRequest cmd(LFUN_MOUSE_TRIPLE,
181                         dc_event_.x, dc_event_.y,
182                         q_button_state(dc_event_.state));
183                 wa_->view().view()->workAreaDispatch(cmd);
184                 return;
185         }
186
187         FuncRequest const cmd(LFUN_MOUSE_PRESS, e->x(), e->y(),
188                               q_button_state(e->button()));
189         wa_->view().view()->workAreaDispatch(cmd);
190 }
191
192
193 void QContentPane::mouseReleaseEvent(QMouseEvent * e)
194 {
195         if (synthetic_mouse_event_.timeout.running())
196                 synthetic_mouse_event_.timeout.stop();
197
198         FuncRequest const cmd(LFUN_MOUSE_RELEASE, e->x(), e->y(),
199                               q_button_state(e->button()));
200         wa_->view().view()->workAreaDispatch(cmd);
201 }
202
203
204 void QContentPane::mouseMoveEvent(QMouseEvent * e)
205 {
206         FuncRequest cmd(LFUN_MOUSE_MOTION, e->x(), e->y(),
207                               q_motion_state(e->state()));
208
209         // If we're above or below the work area...
210         if (e->y() <= 20 || e->y() >= height() - 20) {
211                 // Make sure only a synthetic event can cause a page scroll,
212                 // so they come at a steady rate:
213                 if (e->y() <= 20)
214                         // _Force_ a scroll up:
215                         cmd.y = -40;
216                 else
217                         cmd.y = height();
218                 // Store the event, to be handled when the timeout expires.
219                 synthetic_mouse_event_.cmd = cmd;
220
221                 if (synthetic_mouse_event_.timeout.running())
222                         // Discard the event. Note that it _may_ be handled
223                         // when the timeout expires if
224                         // synthetic_mouse_event_.cmd has not been overwritten.
225                         // Ie, when the timeout expires, we handle the
226                         // most recent event but discard all others that
227                         // occurred after the one used to start the timeout
228                         // in the first place.
229                         return;
230                 else {
231                         synthetic_mouse_event_.restart_timeout = true;
232                         synthetic_mouse_event_.timeout.start();
233                         // Fall through to handle this event...
234                 }
235
236         } else if (synthetic_mouse_event_.timeout.running()) {
237                 // Store the event, to be possibly handled when the timeout
238                 // expires.
239                 // Once the timeout has expired, normal control is returned
240                 // to mouseMoveEvent (restart_timeout = false).
241                 // This results in a much smoother 'feel' when moving the
242                 // mouse back into the work area.
243                 synthetic_mouse_event_.cmd = cmd;
244                 synthetic_mouse_event_.restart_timeout = false;
245                 return;
246         }
247
248         // Has anything changed on-screen since the last QMouseEvent
249         // was received?
250         double const scrollbar_value = wa_->scrollbar_->value();
251         if (e->x() != synthetic_mouse_event_.x_old ||
252             e->y() != synthetic_mouse_event_.y_old ||
253             scrollbar_value != synthetic_mouse_event_.scrollbar_value_old) {
254                 // Yes it has. Store the params used to check this.
255                 synthetic_mouse_event_.x_old = e->x();
256                 synthetic_mouse_event_.y_old = e->y();
257                 synthetic_mouse_event_.scrollbar_value_old = scrollbar_value;
258
259                 // ... and dispatch the event to the LyX core.
260                 wa_->view().view()->workAreaDispatch(cmd);
261         }
262 }
263
264
265 void QContentPane::wheelEvent(QWheelEvent * e)
266 {
267         // Wheel rotation by one notch results in a delta() of 120 (see
268         // documentation of QWheelEvent)
269         int const lines = QApplication::wheelScrollLines() * e->delta() / 120;
270         wa_->scrollbar_->setValue(wa_->scrollbar_->value() -
271                                   lines *  wa_->scrollbar_->lineStep());
272 }
273
274
275 void QContentPane::keyPressEvent(QKeyEvent * e)
276 {
277         keyeventQueue_.push(boost::shared_ptr<QKeyEvent>(new QKeyEvent(*e)));
278 }
279
280
281 void QContentPane::keyeventTimeout()
282 {
283         bool handle_autos = true;
284
285         while (!keyeventQueue_.empty()) {
286                 boost::shared_ptr<QKeyEvent> ev = keyeventQueue_.front();
287
288                 // We never handle more than one auto repeated
289                 // char in a list of queued up events.
290                 if (!handle_autos && ev->isAutoRepeat()) {
291                         keyeventQueue_.pop();
292                         continue;
293                 }
294
295         boost::shared_ptr<QLyXKeySym> sym(new QLyXKeySym);
296                 sym->set(ev.get());
297
298                 wa_->processKeySym(sym, q_key_state(ev->state()));
299                 keyeventQueue_.pop();
300
301                 handle_autos = false;
302         }
303
304         // Restart the timer.
305         step_timer_.start(25, true);
306 }
307
308
309 void QContentPane::doubleClickTimeout()
310 {
311         if (!dc_event_.active)
312                 return;
313
314         dc_event_.active = false;
315
316         FuncRequest cmd(LFUN_MOUSE_DOUBLE,
317                 dc_event_.x, dc_event_.y,
318                 q_button_state(dc_event_.state));
319         wa_->view().view()->workAreaDispatch(cmd);
320 }
321
322
323 void QContentPane::mouseDoubleClickEvent(QMouseEvent * e)
324 {
325         dc_event_ = double_click(e);
326
327         // doubleClickInterval() is just too long.
328         QTimer::singleShot(int(QApplication::doubleClickInterval() / 1.5),
329                 this, SLOT(doubleClickTimeout()));
330 }
331
332
333 void QContentPane::resizeEvent(QResizeEvent *)
334 {
335         if (!pixmap_.get()) {
336                 pixmap_.reset(new QPixmap(width(), height()));
337         }
338
339         pixmap_->resize(width(), height());
340         wa_->view().view()->workAreaResize(width(), height());
341 }
342
343
344 void QContentPane::paintEvent(QPaintEvent * e)
345 {
346         BufferView * buffer_view_ = wa_->view().view();
347
348         if (!pixmap_.get()) {
349                 pixmap_.reset(new QPixmap(width(), height()));
350                 buffer_view_->workAreaResize(width(), height());
351                 return;
352         }
353
354         QRect r(e->rect());
355
356         QPainter q(this);
357         q.drawPixmap(QPoint(r.x(), r.y()),
358                 *pixmap_.get(), r);
359
360         buffer_view_->updateScrollbar();
361         ScrollbarParameters const & scroll_ = buffer_view_->scrollbarParameters();
362
363         wa_->scrollbar_->setTracking(false);
364         wa_->setScrollbarParams(scroll_.height, scroll_.position,
365                 scroll_.lineScrollHeight);
366         wa_->scrollbar_->setTracking(true);
367 }
368
369
370 void QContentPane::trackScrollbar(bool track_on)
371 {
372         track_scrollbar_ = track_on;
373 }
374
375 } // namespace frontend
376 } // namespace lyx