/**************************************************************************** ** ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). ** Contact: Qt Software Information (qt-info@nokia.com) ** ** This file is part of the WebClient project on Trolltech Labs. ** ** This file may be used under the terms of the GNU General Public ** License version 2.0 or 3.0 as published by the Free Software Foundation ** and appearing in the file LICENSE.GPL included in the packaging of ** this file.  Please review the following information to ensure GNU ** General Public Licensing requirements will be met: ** http://www.fsf.org/licensing/licenses/info/GPLv2.html and ** http://www.gnu.org/copyleft/gpl.html. ** ** If you are unsure which license is appropriate for your use, please ** contact the sales department at qt-sales@nokia.com. ** ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. ** ****************************************************************************/ #include #include "widgeteventhandler.h" #include "webclient.h" bool takeEvents = true; class QLineEditAccess : public QLineEdit { public: void emitTextEdited(const QString &text) { QLineEdit::textChanged(text); QLineEdit::textEdited(text); } }; class QTextEditAccess : public QTextEdit { public: void emitTextEdited(const QString &) { QTextEdit::textChanged(); } }; void setTextOnWidget(QWidget *widget, const QString &text) { if (QLineEdit * lineEdit = qobject_cast(widget)) { lineEdit->setText(text); reinterpret_cast(lineEdit)->emitTextEdited(text); } else if (QTextEdit * textEdit = qobject_cast(widget)) { textEdit->setText(text); reinterpret_cast(textEdit)->emitTextEdited(text); } } QWidget * findEventTarget(QWidget *root, QPoint pos) { QWidget *current = root; QWidget *next = current->childAt(pos); while (next) { current = next; next = current->childAt(pos); } return current; } WidgetEventHandler::WidgetEventHandler(QObject *parent, Server *server) : QObject(parent), graphicsWidget(false), grabbing(false), server(server) { focusWidget = 0; } void WidgetEventHandler::setRootWidget(QWidget *root) { DEBUG << "set root widget" << root; rootWidget = root; recursivelyInstallEventHandler(root); } bool WidgetEventHandler::shouldProcessChildWidgets(QWidget *widget) { if (qobject_cast(widget)) return false; return true; } void WidgetEventHandler::recursivelyInstallEventHandler(QWidget *widget) { if (server->shouldSkipUpdate(widget->metaObject()->className())) { //qDebug() << "skip plain widget" << widget; } else { widget->installEventFilter(this); } //qDebug << "event filter on" << widget->metaObject()->className() << widget->objectName() << int(widget); if (QLineEdit *lineEdit = qobject_cast(widget)) { connect(lineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(textChange())); } else if (QTextEdit *textEdit = qobject_cast(widget)) { connect(textEdit, SIGNAL(textChanged()), this, SLOT(textChange())); } else if (QAbstractScrollArea *scrollArea = qobject_cast(widget)) { recursivelyInstallEventHandler(scrollArea->viewport()); } if (shouldProcessChildWidgets(widget) == false) return; foreach (QObject *child, widget->children()) { if (QWidget *childWidget = qobject_cast(child)) recursivelyInstallEventHandler(childWidget); } } void WidgetEventHandler::setSession(Session *session) { events.setSession(session); } Session *WidgetEventHandler::session() { return events.m_session; } void WidgetEventHandler::addPendingUpdate(QWidget* widget, const QRect &rect) { if (pendingUpdates.isEmpty()) QTimer::singleShot(0, this, SLOT(updatePendingWidgets())); pendingUpdates.insert(widget, rect); } bool WidgetEventHandler::eventFilter(QObject *object, QEvent *event) { QWidget *widget = qobject_cast(object); extern QWidget *sharedRoot; if (widget == sharedRoot) return true; if (event->type() == QEvent::Paint) { if (server->shouldSkipUpdate(widget->metaObject()->className())) { // qDebug() << "skip plain widget" << widget; return true; } if (QLabel *label = qobject_cast(widget)) { events.addEvent(idForWidget(widget), EventEntry::TextUpdate); return true; } else if (QTextEdit *textEdit = qobject_cast(widget)) { events.addEvent(idForWidget(widget), EventEntry::TextUpdate); return true; } else if (QPushButton *textEdit = qobject_cast(widget)) { events.addEvent(idForWidget(widget), EventEntry::TextUpdate); return true; } else if (!grabbing) { DEBUG << " add paint update" << object; addPendingUpdate(widget, static_cast(event)->rect()); } } if (event->type() == QEvent::Show) { DEBUG << "show" << object; // Add immediate show update if we have an image to serve. if (events.images.contains(idForWidget(widget))) { addShowEvent(widget); } // Add pending paint update addPendingUpdate(widget, widget->rect()); recursivelyAddShow(widget); } if (event->type() == QEvent::Hide) { DEBUG << "hide" << object; events.addEvent(idForWidget(widget), EventEntry::Hide); recursivelyAddHide(widget); } if (event->type() == QEvent::Move) { QRect geometry = globalGeometry(widget); DEBUG << "move geometry" << object->metaObject()->className() << geometry; if (disableUpdates.contains(sender()) == false) events.addGeometryEvent(idForWidget(widget), geometry); } if (event->type() == QEvent::Resize) { QRect geometry = globalGeometry(widget); DEBUG << "resize geometry" << geometry; events.addGeometryEvent(idForWidget(widget), geometry); } if (event->type() == QEvent::ParentChange) { DEBUG << "parentChange" << "parent"<< widget->parentWidget() << "child" << widget; events.addParentChangeEvent(idForWidget(widget)); } return false; } void WidgetEventHandler::updatePendingWidgets() { const QHash::const_iterator end = pendingUpdates.end(); QHash::const_iterator it = pendingUpdates.begin(); while(it != end) { widgetPaint(it.key(), it.value()); ++it; } pendingUpdates.clear(); } void WidgetEventHandler::handleRequest(HttpRequest *request, HttpResponse *response) { const QByteArray path = request->path(); bool handled = false; DEBUG << "request" << path; if (path.startsWith("/mousepress") || path.startsWith("/mouserelease") || path.startsWith("/mousedoubleclick")) { // DEBUG << "handle mouse press"; handleMousePress(path); handled = true; } else if (path.startsWith("/keypress") || path.startsWith("/keyrelease")) { DEBUG << "handle key press"; handleKeyPress(path); handled = true; } else if (path.startsWith("/json")) { handled = handleJsonMessage(path); } if (!handled) { if (path.startsWith("/content")) { events.reset(); // refresh everything recursivelyAddUpdate(rootWidget); } events.handleRequest(request, response); } } // Keep track of the current live widgets, to avoid dereferencing // stale widget pointers when clients sends updated for a certain // widget id. This also protects against malicious clients that // sends fake ids. quintptr WidgetEventHandler::idForWidget(QWidget *widget) { quintptr id = reinterpret_cast(widget); if (liveWidgets.contains(id) == false) { liveWidgets.insert(id); connect(widget, SIGNAL(destroyed()), SLOT(widgetDeleted())); } return id; } QWidget *WidgetEventHandler::widgetForId(quintptr id) { if (liveWidgets.contains(id)) return reinterpret_cast(id); return 0; } void WidgetEventHandler::widgetDeleted() { liveWidgets.remove(reinterpret_cast(sender())); } bool WidgetEventHandler::handleJsonMessage(const QByteArray &message) { QByteArray jsonText = QUrl::fromPercentEncoding(message.mid(5)).toUtf8(); // remove "/json", decode. DEBUG << "json request" << jsonText; json_object* request = json_tokener_parse(jsonText.data()); json_object* type = json_object_object_get(request, "type"); QByteArray typeText = json_object_get_string(type); DEBUG << typeText; if (typeText == "textupdate") { handleTextUpdate(request); json_object_put(request); //free return true; } else if (typeText == "positionupdate") { handlePositionUpdate(request); json_object_put(request); //free return true; } json_object_put(request); //free return false; } void WidgetEventHandler::handleMousePress(const QByteArray &message) { QList tokens = message.split('-'); QPoint p(tokens.at(1).toInt(), tokens.at(2).toInt()); // ### assumes well-formed string QWidget *target = findEventTarget(rootWidget, p); QPoint local = target->mapFrom(rootWidget, p); if (message.startsWith("/mousepress")) { QMouseEvent press(QEvent::MouseButtonPress, local , Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QApplication::sendEvent(target, &press); } else if (message.startsWith("/mouserelease")) { QMouseEvent release(QEvent::MouseButtonRelease, local , Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QApplication::sendEvent(target, &release); focusWidget = target; } else if (message.startsWith("/mousedoubleclick")) { QMouseEvent press(QEvent::MouseButtonDblClick, local , Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QApplication::sendEvent(target, &press); } } void WidgetEventHandler::handleKeyPress(const QByteArray &message) { if (!focusWidget) return; QList tokens = message.split('-'); int code = tokens.at(1).toInt(); // ### QChar c(code); if (code == 8) { // DEBUG << "backspace"; QKeyEvent press(QEvent::KeyPress, Qt::Key_Backspace, Qt::NoModifier, QString()); QKeyEvent release(QEvent::KeyRelease, Qt::Key_Backspace, Qt::NoModifier, QString()); QApplication::sendEvent(focusWidget, &press); QApplication::sendEvent(focusWidget, &release); } else { QKeyEvent press(QEvent::KeyPress, code, Qt::NoModifier, c); QKeyEvent release(QEvent::KeyRelease, code, Qt::NoModifier, c); QApplication::sendEvent(focusWidget, &press); QApplication::sendEvent(focusWidget, &release); } // DEBUG << "got key press" << c; qApp->processEvents(); } void WidgetEventHandler::handleTextUpdate(json_object* request) { json_object* idObject = json_object_object_get(request, "id"); const int id = json_object_get_int(idObject); json_object* tmp = json_object_object_get(request, "text"); QByteArray text = json_object_get_string(tmp); QWidget *widget = widgetForId(id); if (!widget) return; disableUpdates.insert(widget); if (text == "enter!") { // FIXME! :) QKeyEvent keyEvent(QEvent::KeyPress, Qt::Key_Return, Qt::NoModifier); qApp->sendEvent(widget, &keyEvent); } else { setTextOnWidget(widget, text); } disableUpdates.remove(widget); } void WidgetEventHandler::handlePositionUpdate(json_object *request) { const int id = json_object_get_int(json_object_object_get(request, "id")); const int x = json_object_get_int(json_object_object_get(request, "x")); const int y = json_object_get_int(json_object_object_get(request, "y")); QWidget *widget = widgetForId(id); if (!widget) return; disableUpdates.insert(widget); widget->move(x, y); disableUpdates.remove(widget); // DEBUG() << "position update" << id << x << y; } void WidgetEventHandler::widgetPaint(QWidget *widget, const QRect &updateRect) { if (server->shouldSkipUpdate(widget->metaObject()->className())) { return ; } // Skip the painting step for static widgets that already has been painted, // but send the update event to the client. if (server->testHint(widget, WebClient::StaticWidget)) { if (events.staticCompressedImages.contains(idForWidget(widget))) { events.addStaticUpdateEvent(idForWidget(widget)); return; } } DEBUG << "paint" << widget << widget->rect() << "update" << updateRect; QImage image(widget->size(), QImage::Format_ARGB32_Premultiplied); DEBUG << "widget->size" << widget->size(); image.fill(QColor(0,0,0,0).rgba()); // fill with transparent pixels grabbing = true; // prevent recusion //DEBUG << "render"; // grab widget only, no background or children widget->render(&image, QPoint(0,0), QRegion(QRect(QPoint(0,0), widget->size())), QWidget::RenderFlags(0)); //DEBUG << "render done"; grabbing = false; quintptr id = idForWidget(widget); // The visual representation of a static widget does not change. Store the images // for static widgets in png compressed form. Also store the hash of the // image, and send this as the identity and eTag to the client. This way // the client can cache the image, even across sessions when the widget // pointer (id) changes value. if (server->testHint(widget, WebClient::StaticWidget)){ if (events.staticCompressedImages.contains(id) == false) { QByteArray compressedImage = EventQueue::pngCompress(image); uint hash = qHash(compressedImage); events.addStaticUpdateEvent(id, hash, compressedImage); } } else { // DEBUG << "update" << widget << (int)widget; events.addUpdateEvent(idForWidget(widget), image, updateRect); } // DEBUG << "geometry" << widget << (int)widget << globalGeometry(widget); events.addGeometryEvent(idForWidget(widget), globalGeometry(widget)); } QRect WidgetEventHandler::globalGeometry(QWidget *widget) { // QRect gemetry = widget->rect(); QRect geometry; if (widget->isWindow()) geometry = QRect(QPoint(0,0), widget->size()); else geometry = QRect(widget->mapToParent(QPoint(0,0)), widget->size()); // DEBUG << "geometry for" << widget << geometry; return geometry; } void WidgetEventHandler::recursivelyAddHide(QWidget *widget) { events.addEvent((int)widget, EventEntry::Hide); if (shouldProcessChildWidgets(widget) == false) return; foreach (QObject *child, widget->children()) { if (QWidget *childWidget = qobject_cast(child)) recursivelyAddHide(childWidget); } } void WidgetEventHandler::recursivelyAddShow(QWidget *root) { if (shouldProcessChildWidgets(root) == false) return; foreach (QObject *child, root->children()) { if (QWidget *childWidget = qobject_cast(child)) { if (childWidget->isVisible() == false) continue; if (events.images.contains((int)childWidget)) { addShowEvent(childWidget); } // Add pending paint update addPendingUpdate(childWidget, childWidget->rect()); } } } void WidgetEventHandler::recursivelyAddUpdate(QWidget *widget) { if (widget->isVisible() == false) return; addShowEvent(widget); events.addGeometryEvent((int)widget, globalGeometry(widget)); events.addParentChangeEvent((int) widget); if (QLineEdit *lineEdit = qobject_cast(widget)) { events.addEvent((int)widget, EventEntry::TextUpdate); } else if (QTextEdit *textEdit = qobject_cast(widget)) { events.addEvent((int)widget, EventEntry::TextUpdate); } else if (QPushButton *pusb = qobject_cast(widget)) { events.addEvent((int)widget, EventEntry::TextUpdate); } else if (QLabel *label = qobject_cast(widget)) { events.addEvent((int)widget, EventEntry::TextUpdate); } // Add pending paint update addPendingUpdate(widget, widget->rect()); if (shouldProcessChildWidgets(widget) == false) return; foreach (QObject *child, widget->children()) { if (QWidget *childWidget = qobject_cast(child)) recursivelyAddUpdate(childWidget); } } void WidgetEventHandler::addShowEvent(QWidget *widget) { DEBUG << "add show event" << widget; events.addEvent((int)widget, EventEntry::Show); } void WidgetEventHandler::textChange() { if (disableUpdates.contains(sender()) == false) events.addEvent((int)sender(), EventEntry::TextUpdate); }