]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiWorkArea.C
8e87582262b7ba62f759052d2208e361a0e01a48
[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 "GuiApplication.h"
17 #include "QLPainter.h"
18 #include "QLyXKeySym.h"
19 #include "qt_helpers.h"
20
21 #include "gettext.h"
22 #include "LyXView.h"
23
24 #include "BufferView.h"
25 #include "rowpainter.h"
26 #include "debug.h"
27 #include "funcrequest.h"
28 #include "LColor.h"
29 #include "version.h"
30 #include "lyxrc.h"
31
32 #include "support/filetools.h" // LibFileSearch
33 #include "support/os.h"
34 #include "support/convert.h"
35
36 #include "graphics/GraphicsImage.h"
37 #include "graphics/GraphicsLoader.h"
38
39 #include <QLayout>
40 #include <QMainWindow>
41 #include <QMimeData>
42 #include <QUrl>
43 #include <QDragEnterEvent>
44 #include <QPainter>
45 #include <QScrollBar>
46
47 #include <boost/bind.hpp>
48 #include <boost/current_function.hpp>
49
50 #ifdef Q_WS_WIN
51 int const CursorWidth = 2;
52 #else
53 int const CursorWidth = 1;
54 #endif
55
56
57 using std::endl;
58 using std::string;
59
60 namespace os = lyx::support::os;
61
62
63 namespace lyx {
64
65 using support::FileName;
66
67 /// return the LyX key state from Qt's
68 static key_modifier::state q_key_state(Qt::KeyboardModifiers state)
69 {
70         key_modifier::state k = key_modifier::none;
71         if (state & Qt::ControlModifier)
72                 k |= key_modifier::ctrl;
73         if (state & Qt::ShiftModifier)
74                 k |= key_modifier::shift;
75         if (state & Qt::AltModifier || state & Qt::MetaModifier)
76                 k |= key_modifier::alt;
77         return k;
78 }
79
80
81 /// return the LyX mouse button state from Qt's
82 static mouse_button::state q_button_state(Qt::MouseButton button)
83 {
84         mouse_button::state b = mouse_button::none;
85         switch (button) {
86                 case Qt::LeftButton:
87                         b = mouse_button::button1;
88                         break;
89                 case Qt::MidButton:
90                         b = mouse_button::button2;
91                         break;
92                 case Qt::RightButton:
93                         b = mouse_button::button3;
94                         break;
95                 default:
96                         break;
97         }
98         return b;
99 }
100
101
102 /// return the LyX mouse button state from Qt's
103 mouse_button::state q_motion_state(Qt::MouseButtons state)
104 {
105         mouse_button::state b = mouse_button::none;
106         if (state & Qt::LeftButton)
107                 b |= mouse_button::button1;
108         if (state & Qt::MidButton)
109                 b |= mouse_button::button2;
110         if (state & Qt::RightButton)
111                 b |= mouse_button::button3;
112         return b;
113 }
114
115
116 namespace frontend {
117
118 class CursorWidget {
119 public:
120         CursorWidget() {}
121
122         void draw(QPainter & painter)
123         {
124                 // FIXME: do something depending on the cursor shape.
125                 if (show_ && rect_.isValid())
126                         painter.fillRect(rect_, color_);
127         }
128
129         void update(int x, int y, int h, CursorShape shape)
130         {
131                 color_ = guiApp->colorCache().get(LColor::cursor);
132                 rect_ = QRect(x, y, CursorWidth, h);
133                 shape_ = shape;
134         }
135
136         void show(bool set_show = true) { show_ = set_show; }
137         void hide() { show_ = false; }
138
139         QRect const & rect() { return rect_; }
140
141 private:
142         ///
143         CursorShape shape_;
144         ///
145         bool show_;
146         ///
147         QColor color_;
148         ///
149         QRect rect_;
150 };
151
152
153 // This is a 'heartbeat' generating synthetic mouse move events when the
154 // cursor is at the top or bottom edge of the viewport. One scroll per 0.2 s
155 SyntheticMouseEvent::SyntheticMouseEvent()
156         : timeout(200), restart_timeout(true),
157           x_old(-1), y_old(-1), scrollbar_value_old(-1.0)
158 {}
159
160
161 GuiWorkArea::GuiWorkArea(int w, int h, int id, LyXView & lyx_view)
162         : WorkArea(id, lyx_view)
163 {
164         cursor_ = new frontend::CursorWidget();
165         cursor_->hide();
166
167         setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
168         setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
169         setAcceptDrops(true);
170         setMouseTracking(true);
171         setMinimumSize(100, 70);
172
173         viewport()->setAutoFillBackground(false);
174         viewport()->setAttribute(Qt::WA_OpaquePaintEvent);
175         setFocusPolicy(Qt::WheelFocus);
176
177         viewport()->setCursor(Qt::IBeamCursor);
178
179         resize(w, h);
180
181         synthetic_mouse_event_.timeout.timeout.connect(
182                 boost::bind(&GuiWorkArea::generateSyntheticMouseEvent,
183                             this));
184
185         // Initialize the vertical Scroll Bar
186         QObject::connect(verticalScrollBar(), SIGNAL(actionTriggered(int)),
187                 this, SLOT(adjustViewWithScrollBar(int)));
188
189         // PageStep only depends on the viewport height.
190         verticalScrollBar()->setPageStep(viewport()->height());
191
192         lyxerr[Debug::GUI] << BOOST_CURRENT_FUNCTION
193                 << "\n Area width\t" << width()
194                 << "\n Area height\t" << height()
195                 << "\n viewport width\t" << viewport()->width()
196                 << "\n viewport height\t" << viewport()->height()
197                 << endl;
198
199         // Enables input methods for asian languages.
200         // Must be set when creating custom text editing widgets.
201         setAttribute(Qt::WA_InputMethodEnabled, true);
202 }
203
204
205 void GuiWorkArea::setScrollbarParams(int h, int scroll_pos, int scroll_line_step)
206 {
207         if (verticalScrollBarPolicy() != Qt::ScrollBarAlwaysOn)
208                 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
209
210         verticalScrollBar()->setTracking(false);
211
212         // do what cursor movement does (some grey)
213         h += height() / 4;
214         int scroll_max_ = std::max(0, h - height());
215
216         verticalScrollBar()->setRange(0, scroll_max_);
217         verticalScrollBar()->setSliderPosition(scroll_pos);
218         verticalScrollBar()->setSingleStep(scroll_line_step);
219         verticalScrollBar()->setValue(scroll_pos);
220
221         verticalScrollBar()->setTracking(true);
222 }
223
224
225 void GuiWorkArea::adjustViewWithScrollBar(int)
226 {
227         scrollBufferView(verticalScrollBar()->sliderPosition());
228 }
229
230
231 void GuiWorkArea::dragEnterEvent(QDragEnterEvent * event)
232 {
233         if (event->mimeData()->hasUrls())
234                 event->accept();
235         /// \todo Ask lyx-devel is this is enough:
236         /// if (event->mimeData()->hasFormat("text/plain"))
237         ///     event->acceptProposedAction();
238 }
239
240
241 void GuiWorkArea::dropEvent(QDropEvent* event)
242 {
243         QList<QUrl> files = event->mimeData()->urls();
244         if (files.isEmpty())
245                 return;
246
247         lyxerr[Debug::GUI] << "GuiWorkArea::dropEvent: got URIs!" << endl;
248         for (int i = 0; i!=files.size(); ++i) {
249                 string const file = os::internal_path(fromqstr(files.at(i).toLocalFile()));
250                 if (!file.empty())
251                         dispatch(FuncRequest(LFUN_FILE_OPEN, file));
252         }
253 }
254
255
256 void GuiWorkArea::focusInEvent(QFocusEvent * /*event*/)
257 {
258         // No need to do anything if we didn't change views...
259         if (theApp() == 0 || &lyx_view_ == theApp()->currentView())
260                 return;
261
262         theApp()->setCurrentView(lyx_view_);
263
264         // FIXME: it would be better to send a signal "newBuffer()"
265         // in BufferList that could be connected to the different tabbars.
266         lyx_view_.updateTab();
267
268         startBlinkingCursor();
269
270         //FIXME: Use case: Two windows share the same buffer.
271         // The first window is resize. This modify the inner Buffer
272         // structure because Paragraph has a notion of line break and
273         // thus line width (this is very bad!).
274         // When switching to the other window which does not have the
275         // same size, LyX crashes because the line break is not adapted
276         // the this BufferView width.
277         // The following line fix the crash by resizing the BufferView 
278         // on a focusInEvent(). That is not a good fix but it is a fix
279         // nevertheless. The bad side effect is that when the two
280         // BufferViews show the same portion of the Buffer, the second 
281         // BufferView will show the same line breaks as the first one;
282         // even though those line breaks are not adapted to the second
283         // BufferView width... such is life!
284         resizeBufferView();
285 }
286
287
288 void GuiWorkArea::focusOutEvent(QFocusEvent * /*event*/)
289 {
290         // No need to do anything if we didn't change views...
291         if (&lyx_view_ == theApp()->currentView())
292                 return;
293
294         stopBlinkingCursor();
295 }
296
297
298 void GuiWorkArea::mousePressEvent(QMouseEvent * e)
299 {
300         if (dc_event_.active && dc_event_ == *e) {
301                 dc_event_.active = false;
302                 FuncRequest cmd(LFUN_MOUSE_TRIPLE,
303                         dc_event_.x, dc_event_.y,
304                         q_button_state(dc_event_.state));
305                 dispatch(cmd);
306                 return;
307         }
308
309         FuncRequest const cmd(LFUN_MOUSE_PRESS, e->x(), e->y(),
310                               q_button_state(e->button()));
311         dispatch(cmd);
312 }
313
314
315 void GuiWorkArea::mouseReleaseEvent(QMouseEvent * e)
316 {
317         if (synthetic_mouse_event_.timeout.running())
318                 synthetic_mouse_event_.timeout.stop();
319
320         FuncRequest const cmd(LFUN_MOUSE_RELEASE, e->x(), e->y(),
321                               q_button_state(e->button()));
322         dispatch(cmd);
323 }
324
325
326 void GuiWorkArea::mouseMoveEvent(QMouseEvent * e)
327 {
328         FuncRequest cmd(LFUN_MOUSE_MOTION, e->x(), e->y(),
329                               q_motion_state(e->buttons()));
330
331         // If we're above or below the work area...
332         if (e->y() <= 20 || e->y() >= viewport()->height() - 20) {
333                 // Make sure only a synthetic event can cause a page scroll,
334                 // so they come at a steady rate:
335                 if (e->y() <= 20)
336                         // _Force_ a scroll up:
337                         cmd.y = -40;
338                 else
339                         cmd.y = viewport()->height();
340                 // Store the event, to be handled when the timeout expires.
341                 synthetic_mouse_event_.cmd = cmd;
342
343                 if (synthetic_mouse_event_.timeout.running())
344                         // Discard the event. Note that it _may_ be handled
345                         // when the timeout expires if
346                         // synthetic_mouse_event_.cmd has not been overwritten.
347                         // Ie, when the timeout expires, we handle the
348                         // most recent event but discard all others that
349                         // occurred after the one used to start the timeout
350                         // in the first place.
351                         return;
352                 else {
353                         synthetic_mouse_event_.restart_timeout = true;
354                         synthetic_mouse_event_.timeout.start();
355                         // Fall through to handle this event...
356                 }
357
358         } else if (synthetic_mouse_event_.timeout.running()) {
359                 // Store the event, to be possibly handled when the timeout
360                 // expires.
361                 // Once the timeout has expired, normal control is returned
362                 // to mouseMoveEvent (restart_timeout = false).
363                 // This results in a much smoother 'feel' when moving the
364                 // mouse back into the work area.
365                 synthetic_mouse_event_.cmd = cmd;
366                 synthetic_mouse_event_.restart_timeout = false;
367                 return;
368         }
369
370         // Has anything changed on-screen since the last QMouseEvent
371         // was received?
372         double const scrollbar_value = verticalScrollBar()->value();
373         if (e->x() != synthetic_mouse_event_.x_old ||
374             e->y() != synthetic_mouse_event_.y_old ||
375             scrollbar_value != synthetic_mouse_event_.scrollbar_value_old) {
376                 // Yes it has. Store the params used to check this.
377                 synthetic_mouse_event_.x_old = e->x();
378                 synthetic_mouse_event_.y_old = e->y();
379                 synthetic_mouse_event_.scrollbar_value_old = scrollbar_value;
380
381                 // ... and dispatch the event to the LyX core.
382                 dispatch(cmd);
383         }
384 }
385
386
387 void GuiWorkArea::wheelEvent(QWheelEvent * e)
388 {
389         // Wheel rotation by one notch results in a delta() of 120 (see
390         // documentation of QWheelEvent)
391         int const lines = qApp->wheelScrollLines() * e->delta() / 120;
392         verticalScrollBar()->setValue(verticalScrollBar()->value() -
393                         lines *  verticalScrollBar()->singleStep());
394         adjustViewWithScrollBar();
395 }
396
397
398 void GuiWorkArea::generateSyntheticMouseEvent()
399 {
400 // Set things off to generate the _next_ 'pseudo' event.
401         if (synthetic_mouse_event_.restart_timeout)
402                 synthetic_mouse_event_.timeout.start();
403
404         // Has anything changed on-screen since the last timeout signal
405         // was received?
406         double const scrollbar_value = verticalScrollBar()->value();
407         if (scrollbar_value != synthetic_mouse_event_.scrollbar_value_old) {
408                 // Yes it has. Store the params used to check this.
409                 synthetic_mouse_event_.scrollbar_value_old = scrollbar_value;
410
411                 // ... and dispatch the event to the LyX core.
412                 dispatch(synthetic_mouse_event_.cmd);
413         }
414 }
415
416
417 void GuiWorkArea::keyPressEvent(QKeyEvent * e)
418 {
419         lyxerr[Debug::KEY] << BOOST_CURRENT_FUNCTION
420                 << " count=" << e->count()
421                 << " text=" << fromqstr(e->text())
422                 << " isAutoRepeat=" << e->isAutoRepeat()
423                 << " key=" << e->key()
424                 << endl;
425
426         boost::shared_ptr<QLyXKeySym> sym(new QLyXKeySym);
427         sym->set(e);
428         processKeySym(sym, q_key_state(e->modifiers()));
429 }
430
431
432 void GuiWorkArea::mouseDoubleClickEvent(QMouseEvent * e)
433 {
434         dc_event_ = double_click(e);
435
436         if (!dc_event_.active)
437                 return;
438
439         dc_event_.active = false;
440
441         FuncRequest cmd(LFUN_MOUSE_DOUBLE,
442                 dc_event_.x, dc_event_.y,
443                 q_button_state(dc_event_.state));
444         dispatch(cmd);
445 }
446
447
448 void GuiWorkArea::resizeEvent(QResizeEvent * ev)
449 {
450         stopBlinkingCursor();
451         screen_ = QPixmap(ev->size().width(), ev->size().height());
452         verticalScrollBar()->setPageStep(viewport()->height());
453         QAbstractScrollArea::resizeEvent(ev);
454         resizeBufferView();
455         startBlinkingCursor();
456 }
457
458
459 void GuiWorkArea::update(int x, int y, int w, int h)
460 {
461         viewport()->repaint(x, y, w, h);
462 }
463
464
465 void GuiWorkArea::doGreyOut(QLPainter & pain)
466 {
467         setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
468
469         pain.fillRectangle(0, 0, width(), height(),
470                 LColor::bottomarea);
471
472         //if (!lyxrc.show_banner)
473         //      return;
474         lyxerr[Debug::GUI] << "show banner: " << lyxrc.show_banner << endl;
475         /// The text to be written on top of the pixmap
476         QString const text = lyx_version ? QString(lyx_version) : qt_("unknown version");
477         FileName const file = support::libFileSearch("images", "banner", "ppm");
478         if (file.empty())
479                 return;
480
481         QPixmap pm(toqstr(file.absFilename()));
482         if (!pm) {
483                 lyxerr << "could not load splash screen: '" << file << "'" << endl;
484                 return;
485         }
486
487         QFont font;
488         // The font used to display the version info
489         font.setStyleHint(QFont::SansSerif);
490         font.setWeight(QFont::Bold);
491         font.setPointSize(convert<int>(lyxrc.font_sizes[LyXFont::SIZE_LARGE]));
492
493         int const w = pm.width();
494         int const h = pm.height();
495
496         int x = (width() - w) / 2;
497         int y = (height() - h) / 2;
498
499         pain.drawPixmap(x, y, pm);
500
501         x += 300;
502         y += 265;
503
504         pain.setPen(QColor(255, 255, 0));
505         pain.setFont(font);
506         pain.drawText(x, y, text);
507 }
508
509
510 void GuiWorkArea::paintEvent(QPaintEvent * ev)
511 {
512         QRect const rc = ev->rect(); 
513         lyxerr[Debug::PAINTING] << "paintEvent begin: x: " << rc.x()
514                 << " y: " << rc.y()
515                 << " w: " << rc.width()
516                 << " h: " << rc.height() << endl;
517
518         QPainter pain(viewport());
519         pain.drawPixmap(rc, screen_, rc);
520         cursor_->draw(pain);
521 }
522
523
524 void GuiWorkArea::expose(int x, int y, int w, int h)
525 {
526         QLPainter pain(&screen_);
527
528         if (greyed_out_) {
529                 lyxerr[Debug::GUI] << "splash screen requested" << endl;
530                 doGreyOut(pain);
531                 verticalScrollBar()->hide();
532                 update(0, 0, width(), height());
533                 return;
534         }
535
536         verticalScrollBar()->show();
537         paintText(*buffer_view_, pain);
538         update(x, y, w, h);
539 }
540
541
542 void GuiWorkArea::showCursor(int x, int y, int h, CursorShape shape)
543 {
544         cursor_->update(x, y, h, shape);
545         cursor_->show();
546         viewport()->update(cursor_->rect());
547 }
548
549
550 void GuiWorkArea::removeCursor()
551 {
552         cursor_->hide();
553         //if (!qApp->focusWidget())
554                 viewport()->update(cursor_->rect());
555 }
556
557
558 void GuiWorkArea::inputMethodEvent(QInputMethodEvent * e)
559 {
560         QString const & text = e->commitString();
561         if (!text.isEmpty()) {
562
563                 lyxerr[Debug::KEY] << BOOST_CURRENT_FUNCTION
564                         << " preeditString =" << fromqstr(e->preeditString())
565                         << " commitString  =" << fromqstr(e->commitString())
566                         << endl;
567
568                 int key = 0;
569                 // needed to make math superscript work on some systems
570                 // ideally, such special coding should not be necessary
571                 if (text == "^")
572                         key = Qt::Key_AsciiCircum;
573                 // FIXME: Needs for investigation, this key is not really used,
574                 // the ctor below just check if key is different from 0.
575                 QKeyEvent ev(QEvent::KeyPress, key, Qt::NoModifier, text);
576                 keyPressEvent(&ev);
577         }
578         e->accept();
579 }
580
581 } // namespace frontend
582 } // namespace lyx
583
584 #include "GuiWorkArea_moc.cpp"