3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
7 * \author Abdelrazak Younes
9 * Full author contact details are available in file CREDITS.
14 #include "QWorkArea.h"
15 #include "QLPainter.h"
16 #include "QLyXKeySym.h"
18 #include "lcolorcache.h"
19 #include "qt_helpers.h"
22 #include "funcrequest.h"
24 #include "support/os.h"
26 #include <QApplication>
29 #include <QMainWindow>
31 #include <QDragEnterEvent>
36 #include <boost/bind.hpp>
43 #include <Carbon/Carbon.h>
47 #include <support/lstrings.h>
49 using lyx::support::subst;
54 namespace os = lyx::support::os;
57 QWorkArea const * wa_ptr = 0;
59 /// return the LyX key state from Qt's
60 key_modifier::state q_key_state(Qt::ButtonState state)
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)
68 k |= key_modifier::alt;
73 /// return the LyX mouse button state from Qt's
74 mouse_button::state q_button_state(Qt::ButtonState button)
76 mouse_button::state b = mouse_button::none;
79 b = mouse_button::button1;
82 b = mouse_button::button2;
85 b = mouse_button::button3;
94 /// return the LyX mouse button state from Qt's
95 mouse_button::state q_motion_state(Qt::ButtonState state)
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;
109 // This is a 'heartbeat' generating synthetic mouse move events when the
110 // cursor is at the top or bottom edge of the viewport. One scroll per 0.2 s
111 SyntheticMouseEvent::SyntheticMouseEvent()
112 : timeout(200), restart_timeout(true),
113 x_old(-1), y_old(-1), scrollbar_value_old(-1.0)
118 Painter & QWorkArea::getPainter() { return painter_; }
120 // QLPainter & QWorkArea::getQLPainter() { return painter_; }
122 /// get the content pane widget
123 QWidget * QWorkArea::getContent() const { return viewport(); }
125 QWorkArea::QWorkArea(LyXView &, int w, int h)
126 : WorkArea(), QAbstractScrollArea(qApp->mainWidget()), painter_(this)
128 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
129 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
131 (static_cast<QMainWindow*>(qApp->mainWidget()))->setCentralWidget(this);
133 setAcceptDrops(true);
135 setMinimumSize(100, 70);
137 // setBackgroundRole(lcolorcache.get(LColor::background));
138 // viewport()->setBackgroundRole(QPalette::Window);
139 viewport()->setAutoFillBackground(false);
140 viewport()->setAttribute(Qt::WA_OpaquePaintEvent);
142 viewport()->setFocusPolicy(Qt::WheelFocus);
143 viewport()->setFocus();
144 // viewport()->grabKeyboard();
145 setFocusPolicy(Qt::WheelFocus);
146 setFocusProxy(viewport());
147 viewport()->setCursor(Qt::IBeamCursor);
152 scrolled_with_mouse_=false;
153 scrolled_with_keyboard_ = false;
155 synthetic_mouse_event_.timeout.timeout.connect(
156 boost::bind(&QWorkArea::generateSyntheticMouseEvent,
159 if ( !QObject::connect(&step_timer_, SIGNAL(timeout()),
160 this, SLOT(keyeventTimeout())) )
161 lyxerr[Debug::GUI] << "ERROR: keyeventTimeout cannot connect!" << endl;
164 if ( !QObject::connect(verticalScrollBar(), SIGNAL(actionTriggered(int)),
165 this, SLOT(adjustViewWithScrollBar(int))) )
166 lyxerr[Debug::GUI] << "ERROR: adjustViewWithScrollBar cannot connect!" << endl;
168 #if USE_INPUT_METHODS
169 // to make qt-immodule work
170 setInputMethodEnabled(true);
173 // doubleClickInterval() is 400 ms on X11 witch is just too long.
174 // On Windows and Mac OS X, the operating system's value is used.
175 // On Microsoft Windows, calling this function sets the double
176 // click interval for all applications. So we don't!
177 QApplication::setDoubleClickInterval(300);
180 // Start the timer, one-shot.
181 step_timer_.start(50, true);
183 //viewport()->resize(w, h);
184 pixmap_.reset(new QPixmap(viewport()->width(), viewport()->height()));
186 this->workAreaResize();
188 lyxerr[Debug::GUI] << BOOST_CURRENT_FUNCTION
189 << "\n Area width\t" << width()
190 << "\n Area height\t" << height()
191 << "\n viewport width\t" << viewport()->width()
192 << "\n viewport height\t" << viewport()->height()
195 // this->QWidget::resize(w,h);
202 QWorkArea::~QWorkArea()
206 void QWorkArea::setScrollbarParams(int h, int scroll_pos, int scroll_line_step)
209 if (scrolled_with_mouse_)
211 scrolled_with_mouse_=false;
216 //if (scroll_pos_ == scroll_pos)
218 // verticalScrollBar()->triggerAction(QAbstractSlider::SliderMove);
219 int scroll_pos_ = scroll_pos;
221 int scroll_line_step_ = scroll_line_step;
222 int scroll_page_step_ = viewport()->height();
224 // do what cursor movement does (some grey)
226 int scroll_max_ = std::max(0, h - height());
228 //scrolled_with_keyboard_=true;
230 verticalScrollBar()->setRange(0, scroll_max_);
231 verticalScrollBar()->setSliderPosition(scroll_pos_);
232 verticalScrollBar()->setLineStep(scroll_line_step_);
233 verticalScrollBar()->setPageStep(scroll_page_step_);
236 void QWorkArea::adjustViewWithScrollBar(int action)
238 lyxerr[Debug::GUI] << BOOST_CURRENT_FUNCTION
239 << " verticalScrollBar val=" << verticalScrollBar()->value()
240 << " verticalScrollBar pos=" << verticalScrollBar()->sliderPosition()
241 << " min=" << verticalScrollBar()->minimum()
242 << " max=" << verticalScrollBar()->maximum()
243 << " pagestep=" << verticalScrollBar()->pageStep()
244 << " linestep=" << verticalScrollBar()->lineStep()
247 this->scrollDocView(verticalScrollBar()->sliderPosition());
249 // scrolled_with_mouse_ = true;
253 bool lyxX11EventFilter(XEvent * xev)
256 case SelectionRequest:
257 lyxerr[Debug::GUI] << "X requested selection." << endl;
259 wa_ptr->selectionRequested();
262 lyxerr[Debug::GUI] << "Lost selection." << endl;
264 wa_ptr->selectionLost();
273 OSErr checkAppleEventForMissingParams(const AppleEvent& theAppleEvent)
275 DescType returnedType;
277 OSErr err = AEGetAttributePtr(&theAppleEvent, keyMissedKeywordAttr,
278 typeWildCard, &returnedType, nil, 0,
281 case errAEDescNotFound:
284 return errAEEventNotHandled;
291 pascal OSErr handleOpenDocuments(const AppleEvent* inEvent,
292 AppleEvent* /*reply*/, long /*refCon*/)
295 AEDescList documentList;
296 OSErr err = AEGetParamDesc(inEvent, keyDirectObject, typeAEList,
301 err = checkAppleEventForMissingParams(*inEvent);
304 err = AECountItems(&documentList, &documentCount);
305 for (long documentIndex = 1;
306 err == noErr && documentIndex <= documentCount;
308 DescType returnedType;
313 err = AESizeOfNthItem(&documentList, documentIndex,
314 &returnedType, &actualSize);
316 err = AEGetNthPtr(&documentList, documentIndex,
318 &returnedType, (Ptr)&ref,
319 sizeof(FSRef), &actualSize);
321 FSRefMakePath(&ref, (UInt8*)qstr_buf,
323 s_arg=QString::fromUtf8(qstr_buf);
325 FuncRequest(LFUN_FILE_OPEN,
332 AEDisposeDesc(&documentList);
337 void QWorkArea::haveSelection(bool own) const
341 if (!QApplication::clipboard()->supportsSelection())
345 QApplication::clipboard()->setText(QString(), QClipboard::Selection);
347 // We don't need to do anything if own = false, as this case is
352 string const QWorkArea::getClipboard() const
354 QString str = QApplication::clipboard()->text(QClipboard::Selection);
355 lyxerr[Debug::ACTION] << "getClipboard: " << (const char*) str << endl;
359 // The MAC clipboard uses \r for lineendings, and we use \n
360 return subst(fromqstr(str), '\r', '\n');
362 return fromqstr(str);
367 void QWorkArea::putClipboard(string const & str) const
370 // The MAC clipboard uses \r for lineendings, and we use \n
371 QApplication::clipboard()->setText(toqstr(subst(str, '\n', '\r')),
372 QClipboard::Selection);
374 QApplication::clipboard()->setText(toqstr(str), QClipboard::Selection);
376 lyxerr[Debug::ACTION] << "putClipboard: " << str << endl;
380 void QWorkArea::dragEnterEvent(QDragEnterEvent * event)
382 event->accept(Q3UriDrag::canDecode(event));
384 /// \todo Ask lyx-devel is this is enough:
385 /// if (event->mimeData()->hasFormat("text/plain"))
386 /// event->acceptProposedAction();
391 void QWorkArea::dropEvent(QDropEvent* event)
395 if (Q3UriDrag::decodeLocalFiles(event, files)) {
396 lyxerr[Debug::GUI] << "QWorkArea::dropEvent: got URIs!"
398 for (QStringList::Iterator i = files.begin();
399 i!=files.end(); ++i) {
400 string const file = os::internal_path(fromqstr(*i));
401 dispatch(FuncRequest(LFUN_FILE_OPEN, file));
406 void QWorkArea::scrollContentsBy(int dx, int dy)
408 lyxerr[Debug::GUI] << BOOST_CURRENT_FUNCTION
409 << " scroll by dx=" << dx
411 << " verticalScrollBar val=" << verticalScrollBar()->value()
412 << " min=" << verticalScrollBar()->minimum()
413 << " max=" << verticalScrollBar()->maximum()
414 << " pagestep=" << verticalScrollBar()->pageStep()
415 << " linestep=" << verticalScrollBar()->lineStep()
418 if (scrolled_with_keyboard_)
420 scrolled_with_keyboard_=false;
424 this->scrollDocView(verticalScrollBar()->value());
425 scrolled_with_mouse_ = true;
429 #if USE_INPUT_METHODS
430 // to make qt-immodule work
432 void QWorkArea::inputMethodEvent(QInputMethodEvent * e)
434 QString const text = e->text();
435 if (!text.isEmpty()) {
437 // needed to make math superscript work on some systems
438 // ideally, such special coding should not be necessary
440 key = Qt::Key_AsciiCircum;
441 QKeyEvent ev(QEvent::KeyPress, key, *text.ascii(), 0, text);
448 void QWorkArea::mousePressEvent(QMouseEvent * e)
450 if (dc_event_.active && dc_event_ == *e) {
451 dc_event_.active = false;
452 FuncRequest cmd(LFUN_MOUSE_TRIPLE,
453 dc_event_.x, dc_event_.y,
454 q_button_state(dc_event_.state));
459 FuncRequest const cmd(LFUN_MOUSE_PRESS, e->x(), e->y(),
460 q_button_state(e->button()));
465 void QWorkArea::mouseReleaseEvent(QMouseEvent * e)
467 if (synthetic_mouse_event_.timeout.running())
468 synthetic_mouse_event_.timeout.stop();
470 FuncRequest const cmd(LFUN_MOUSE_RELEASE, e->x(), e->y(),
471 q_button_state(e->button()));
476 void QWorkArea::mouseMoveEvent(QMouseEvent * e)
478 FuncRequest cmd(LFUN_MOUSE_MOTION, e->x(), e->y(),
479 q_motion_state(e->state()));
481 // If we're above or below the work area...
482 if (e->y() <= 20 || e->y() >= viewport()->QWidget::height() - 20) {
483 // Make sure only a synthetic event can cause a page scroll,
484 // so they come at a steady rate:
486 // _Force_ a scroll up:
489 cmd.y = viewport()->QWidget::height();
490 // Store the event, to be handled when the timeout expires.
491 synthetic_mouse_event_.cmd = cmd;
493 if (synthetic_mouse_event_.timeout.running())
494 // Discard the event. Note that it _may_ be handled
495 // when the timeout expires if
496 // synthetic_mouse_event_.cmd has not been overwritten.
497 // Ie, when the timeout expires, we handle the
498 // most recent event but discard all others that
499 // occurred after the one used to start the timeout
500 // in the first place.
503 synthetic_mouse_event_.restart_timeout = true;
504 synthetic_mouse_event_.timeout.start();
505 // Fall through to handle this event...
508 } else if (synthetic_mouse_event_.timeout.running()) {
509 // Store the event, to be possibly handled when the timeout
511 // Once the timeout has expired, normal control is returned
512 // to mouseMoveEvent (restart_timeout = false).
513 // This results in a much smoother 'feel' when moving the
514 // mouse back into the work area.
515 synthetic_mouse_event_.cmd = cmd;
516 synthetic_mouse_event_.restart_timeout = false;
520 // Has anything changed on-screen since the last QMouseEvent
522 double const scrollbar_value = verticalScrollBar()->value();
523 if (e->x() != synthetic_mouse_event_.x_old ||
524 e->y() != synthetic_mouse_event_.y_old ||
525 scrollbar_value != synthetic_mouse_event_.scrollbar_value_old) {
526 // Yes it has. Store the params used to check this.
527 synthetic_mouse_event_.x_old = e->x();
528 synthetic_mouse_event_.y_old = e->y();
529 synthetic_mouse_event_.scrollbar_value_old = scrollbar_value;
531 // ... and dispatch the event to the LyX core.
537 void QWorkArea::wheelEvent(QWheelEvent * e)
539 // verticalScrollBar()->setValue(verticalScrollBar()->value() - e->delta());
543 void QWorkArea::keyPressEvent(QKeyEvent * e)
545 lyxerr[Debug::KEY] << BOOST_CURRENT_FUNCTION
546 << " count=" << e->count()
547 << " text=" << (const char *) e->text()
548 << " isAutoRepeat=" << e->isAutoRepeat()
549 << " key=" << e->key()
552 //viewport()->grabKeyboard();
553 // keyeventQueue_.push(boost::shared_ptr<QKeyEvent>(new QKeyEvent(*e)));
555 boost::shared_ptr<QLyXKeySym> sym(new QLyXKeySym);
557 this->workAreaKeyPress(sym, q_key_state(e->state()));
561 void QWorkArea::generateSyntheticMouseEvent()
563 // Set things off to generate the _next_ 'pseudo' event.
564 if (synthetic_mouse_event_.restart_timeout)
565 synthetic_mouse_event_.timeout.start();
567 // Has anything changed on-screen since the last timeout signal
569 double const scrollbar_value = verticalScrollBar()->value();
570 if (scrollbar_value != synthetic_mouse_event_.scrollbar_value_old) {
571 // Yes it has. Store the params used to check this.
572 synthetic_mouse_event_.scrollbar_value_old = scrollbar_value;
574 // ... and dispatch the event to the LyX core.
575 this->dispatch(synthetic_mouse_event_.cmd);
580 void QWorkArea::keyeventTimeout()
582 bool handle_autos = true;
584 while (!keyeventQueue_.empty()) {
585 boost::shared_ptr<QKeyEvent> ev = keyeventQueue_.front();
587 // We never handle more than one auto repeated
588 // char in a list of queued up events.
589 if (!handle_autos && ev->isAutoRepeat()) {
590 keyeventQueue_.pop();
594 boost::shared_ptr<QLyXKeySym> sym(new QLyXKeySym);
597 lyxerr[Debug::GUI] << BOOST_CURRENT_FUNCTION
598 << " count=" << ev->count()
599 << " text=" << (const char *) ev->text()
600 << " isAutoRepeat=" << ev->isAutoRepeat()
601 << " key=" << ev->key()
604 this->workAreaKeyPress(sym, q_key_state(ev->state()));
605 keyeventQueue_.pop();
607 handle_autos = false;
610 // Restart the timer.
611 step_timer_.start(25, true);
615 void QWorkArea::mouseDoubleClickEvent(QMouseEvent * e)
617 dc_event_ = double_click(e);
619 if (!dc_event_.active)
622 dc_event_.active = false;
624 FuncRequest cmd(LFUN_MOUSE_DOUBLE,
625 dc_event_.x, dc_event_.y,
626 q_button_state(dc_event_.state));
631 void QWorkArea::resizeEvent(QResizeEvent * resizeEvent)
633 pixmap_.reset(new QPixmap(viewport()->width(), viewport()->height()));
635 scrolled_with_mouse_=false;
636 scrolled_with_keyboard_=false;
638 this->workAreaResize();
640 lyxerr[Debug::GUI] << BOOST_CURRENT_FUNCTION
641 << "\n QWidget width\t" << viewport()->width()
642 << "\n QWidget height\t" << viewport()->height()
643 << "\n QResizeEvent rect left\t" << rect().left()
644 << "\n QResizeEvent rect right\t" << rect().right()
648 void QWorkArea::paintEvent(QPaintEvent * e)
651 lyxerr[Debug::GUI] << BOOST_CURRENT_FUNCTION
652 << "\n QWidget width\t" << viewport()->width()
653 << "\n QWidget height\t" << viewport()->height()
654 << "\n QPaintEvent x\t" << e->rect().x()
655 << "\n QPaintEvent y\t" << e->rect().x()
656 << "\n QPaintEvent w\t" << e->rect().width()
657 << "\n QPaintEvent h\t" << e->rect().height()
660 QPainter q(viewport());
661 q.drawPixmap(e->rect(), *pixmap_.get(), e->rect());
663 // q.drawPixmap(QPoint(r.x(), r.y()),
664 // *pixmap_.get(), r);