/** * \file QContentPane.C * This file is part of LyX, the document processor. * Licence details can be found in the file COPYING. * * \author John Levon * * Full author contact details are available in file CREDITS. */ #include #include "BufferView.h" #include "frontends/LyXView.h" #include "frontends/WorkArea.h" // Qt defines a macro 'signals' that clashes with a boost namespace. // All is well if the namespace is visible first. #include "QWorkArea.h" #include "QContentPane.h" #include "QLyXKeySym.h" #include #include #include namespace { /// return the LyX key state from Qt's key_modifier::state q_key_state(Qt::ButtonState state) { key_modifier::state k = key_modifier::none; if (state & Qt::ControlButton) k |= key_modifier::ctrl; if (state & Qt::ShiftButton) k |= key_modifier::shift; if (state & Qt::AltButton) k |= key_modifier::alt; return k; } /// return the LyX mouse button state from Qt's mouse_button::state q_button_state(Qt::ButtonState button) { mouse_button::state b = mouse_button::none; switch (button) { case Qt::LeftButton: b = mouse_button::button1; break; case Qt::MidButton: b = mouse_button::button2; break; case Qt::RightButton: b = mouse_button::button3; break; default: break; } return b; } /// return the LyX mouse button state from Qt's mouse_button::state q_motion_state(Qt::ButtonState state) { mouse_button::state b = mouse_button::none; if (state & Qt::LeftButton) b |= mouse_button::button1; if (state & Qt::MidButton) b |= mouse_button::button2; if (state & Qt::RightButton) b |= mouse_button::button3; return b; } } // namespace anon namespace lyx { namespace frontend { // This is a 'heartbeat' generating synthetic mouse move events when the // cursor is at the top or bottom edge of the viewport. One scroll per 0.2 s SyntheticMouseEvent::SyntheticMouseEvent() : timeout(200), restart_timeout(true), x_old(-1), y_old(-1), scrollbar_value_old(-1.0) {} QContentPane::QContentPane(QWorkArea * parent) : QWidget(parent, "content_pane", WRepaintNoErase), track_scrollbar_(true), wa_(parent) { synthetic_mouse_event_.timeout.timeout.connect( boost::bind(&QContentPane::generateSyntheticMouseEvent, this)); connect(&step_timer_, SIGNAL(timeout()), SLOT(keyeventTimeout())); setFocusPolicy(QWidget::WheelFocus); setFocus(); setCursor(ibeamCursor); #if USE_INPUT_METHODS // to make qt-immodule work setInputMethodEnabled(true); #endif // stupid moc strikes again connect(wa_->scrollbar_, SIGNAL(valueChanged(int)), this, SLOT(scrollBarChanged(int))); // Start the timer, one-shot. step_timer_.start(50, true); } #if USE_INPUT_METHODS // to make qt-immodule work void QContentPane::imStartEvent(QIMEvent *e) { e->accept(); } void QContentPane::imComposeEvent(QIMEvent *e) { e->accept(); } void QContentPane::imEndEvent(QIMEvent *e) { QString const text = e->text(); if (!text.isEmpty()) { int key = 0; // needed to make math superscript work on some systems // ideally, such special coding should not be necessary if (text == "^") key = Qt::Key_AsciiCircum; QKeyEvent ev(QEvent::KeyPress, key, *text.ascii(), 0, text); keyPressEvent(&ev); } e->accept(); } #endif void QContentPane::generateSyntheticMouseEvent() { // Set things off to generate the _next_ 'pseudo' event. if (synthetic_mouse_event_.restart_timeout) synthetic_mouse_event_.timeout.start(); // Has anything changed on-screen since the last timeout signal // was received? double const scrollbar_value = wa_->scrollbar_->value(); if (scrollbar_value != synthetic_mouse_event_.scrollbar_value_old) { // Yes it has. Store the params used to check this. synthetic_mouse_event_.scrollbar_value_old = scrollbar_value; // ... and dispatch the event to the LyX core. wa_->view().view()->workAreaDispatch(synthetic_mouse_event_.cmd); } } void QContentPane::scrollBarChanged(int val) { if (track_scrollbar_) wa_->view().view()->scrollDocView(val); } void QContentPane::mousePressEvent(QMouseEvent * e) { if (dc_event_.active && dc_event_ == *e) { dc_event_.active = false; FuncRequest cmd(LFUN_MOUSE_TRIPLE, dc_event_.x, dc_event_.y, q_button_state(dc_event_.state)); wa_->view().view()->workAreaDispatch(cmd); return; } FuncRequest const cmd(LFUN_MOUSE_PRESS, e->x(), e->y(), q_button_state(e->button())); wa_->view().view()->workAreaDispatch(cmd); } void QContentPane::mouseReleaseEvent(QMouseEvent * e) { if (synthetic_mouse_event_.timeout.running()) synthetic_mouse_event_.timeout.stop(); FuncRequest const cmd(LFUN_MOUSE_RELEASE, e->x(), e->y(), q_button_state(e->button())); wa_->view().view()->workAreaDispatch(cmd); } void QContentPane::mouseMoveEvent(QMouseEvent * e) { FuncRequest cmd(LFUN_MOUSE_MOTION, e->x(), e->y(), q_motion_state(e->state())); // If we're above or below the work area... if (e->y() <= 20 || e->y() >= height() - 20) { // Make sure only a synthetic event can cause a page scroll, // so they come at a steady rate: if (e->y() <= 20) // _Force_ a scroll up: cmd.y = -40; else cmd.y = height(); // Store the event, to be handled when the timeout expires. synthetic_mouse_event_.cmd = cmd; if (synthetic_mouse_event_.timeout.running()) // Discard the event. Note that it _may_ be handled // when the timeout expires if // synthetic_mouse_event_.cmd has not been overwritten. // Ie, when the timeout expires, we handle the // most recent event but discard all others that // occurred after the one used to start the timeout // in the first place. return; else { synthetic_mouse_event_.restart_timeout = true; synthetic_mouse_event_.timeout.start(); // Fall through to handle this event... } } else if (synthetic_mouse_event_.timeout.running()) { // Store the event, to be possibly handled when the timeout // expires. // Once the timeout has expired, normal control is returned // to mouseMoveEvent (restart_timeout = false). // This results in a much smoother 'feel' when moving the // mouse back into the work area. synthetic_mouse_event_.cmd = cmd; synthetic_mouse_event_.restart_timeout = false; return; } // Has anything changed on-screen since the last QMouseEvent // was received? double const scrollbar_value = wa_->scrollbar_->value(); if (e->x() != synthetic_mouse_event_.x_old || e->y() != synthetic_mouse_event_.y_old || scrollbar_value != synthetic_mouse_event_.scrollbar_value_old) { // Yes it has. Store the params used to check this. synthetic_mouse_event_.x_old = e->x(); synthetic_mouse_event_.y_old = e->y(); synthetic_mouse_event_.scrollbar_value_old = scrollbar_value; // ... and dispatch the event to the LyX core. wa_->view().view()->workAreaDispatch(cmd); } } void QContentPane::wheelEvent(QWheelEvent * e) { // Wheel rotation by one notch results in a delta() of 120 (see // documentation of QWheelEvent) int const lines = QApplication::wheelScrollLines() * e->delta() / 120; wa_->scrollbar_->setValue(wa_->scrollbar_->value() - lines * wa_->scrollbar_->lineStep()); } void QContentPane::keyPressEvent(QKeyEvent * e) { keyeventQueue_.push(boost::shared_ptr(new QKeyEvent(*e))); } void QContentPane::keyeventTimeout() { bool handle_autos = true; while (!keyeventQueue_.empty()) { boost::shared_ptr ev = keyeventQueue_.front(); // We never handle more than one auto repeated // char in a list of queued up events. if (!handle_autos && ev->isAutoRepeat()) { keyeventQueue_.pop(); continue; } boost::shared_ptr sym(new QLyXKeySym); sym->set(ev.get()); wa_->view().workArea()->processKeySym(sym, q_key_state(ev->state())); keyeventQueue_.pop(); handle_autos = false; } // Restart the timer. step_timer_.start(25, true); } void QContentPane::doubleClickTimeout() { if (!dc_event_.active) return; dc_event_.active = false; FuncRequest cmd(LFUN_MOUSE_DOUBLE, dc_event_.x, dc_event_.y, q_button_state(dc_event_.state)); wa_->view().view()->workAreaDispatch(cmd); } void QContentPane::mouseDoubleClickEvent(QMouseEvent * e) { dc_event_ = double_click(e); // doubleClickInterval() is just too long. QTimer::singleShot(int(QApplication::doubleClickInterval() / 1.5), this, SLOT(doubleClickTimeout())); } void QContentPane::resizeEvent(QResizeEvent *) { if (!pixmap_.get()) { pixmap_.reset(new QPixmap(width(), height())); } pixmap_->resize(width(), height()); wa_->view().view()->workAreaResize(width(), height()); } void QContentPane::paintEvent(QPaintEvent * e) { BufferView * buffer_view_ = wa_->view().view(); if (!pixmap_.get()) { pixmap_.reset(new QPixmap(width(), height())); buffer_view_->workAreaResize(width(), height()); return; } QRect r(e->rect()); QPainter q(this); q.drawPixmap(QPoint(r.x(), r.y()), *pixmap_.get(), r); buffer_view_->updateScrollbar(); ScrollbarParameters const & scroll_ = buffer_view_->scrollbarParameters(); wa_->scrollbar_->setTracking(false); wa_->setScrollbarParams(scroll_.height, scroll_.position, scroll_.lineScrollHeight); wa_->scrollbar_->setTracking(true); } void QContentPane::trackScrollbar(bool track_on) { track_scrollbar_ = track_on; } } // namespace frontend } // namespace lyx