diff -r 000000000000 -r 1450b09d0cfd ginebra/chromewidget.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ginebra/chromewidget.cpp Tue May 04 12:39:35 2010 +0300 @@ -0,0 +1,767 @@ +/* +* Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +* All rights reserved. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, version 2.1 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, +* see "http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html/". +* +* Description: +* +*/ + +#include +#include +#include +#include +#include +#include +#include "qwebpage.h" +#include "qwebframe.h" +#include "qwebview.h" +#include "qwebelement.h" +#include "chromewidget.h" +#include "chromerenderer.h" +#include "chromesnippet.h" +#include "chromewidgetjsobject.h" +#include "chromeview.h" +#include "attentionanimator.h" +#include "visibilityanimator.h" +//NB: remove these +#include "animations/fadeanimator.h" +#include "animations/bounceanimator.h" +#include "animations/flyoutanimator.h" + +#include "utilities.h" +#include + +//Temporary include +#include + +#ifdef G_TIMING +#include "gtimer.h" +#endif + +class UpdateBufferEvent : public QEvent { + public: + UpdateBufferEvent() + : QEvent(customType()) { + } + static QEvent::Type customType() { + static int type = QEvent::registerEventType(); + return (QEvent::Type) type; + } +}; + +ChromeWidget::ChromeWidget(ChromeView *parentChromeView, QGraphicsItem *parent, const QString &jsName) + :QObject(), + m_chromePage(0), + m_parentItem(parent), + m_parentChromeView(parentChromeView), + m_state(maximized), + m_buffer(0), + m_painter(0), + m_dirtyTimer(0), + m_jsObject(new ChromeWidgetJSObject(this, this, jsName)) +{ + // Connect signals generated by this object to signals on the javascript object. + safe_connect(this, SIGNAL(loadStarted()), m_jsObject, SIGNAL(loadStarted())); + safe_connect(this, SIGNAL(loadComplete()), m_jsObject, SIGNAL(loadComplete())); + safe_connect(this, SIGNAL(dragStarted()), m_jsObject, SIGNAL(dragStarted())); + safe_connect(this, SIGNAL(dragFinished()), m_jsObject, SIGNAL(dragFinished())); + safe_connect(this, SIGNAL(viewPortResize(QRect)), m_jsObject, SIGNAL(viewPortResize(QRect))); + + //Allocate an instance of webkit to render the chrome + ChromeRenderer *pageView = new ChromeRenderer(parentChromeView->parentWidget()); + safe_connect(pageView, SIGNAL(symbianCarriageReturn()), m_jsObject, SIGNAL(symbianCarriageReturn())); + //QWebView *pageView = new QWebView(parentChromeView->parentWidget()); + + pageView->show(); + m_chromePage = pageView->page(); + + //Render to a transparent background (see WebKit bug 29414) + QPalette palette = m_chromePage->palette(); + palette.setColor(QPalette::Base, Qt::transparent); + m_chromePage->setPalette(palette); + + // No scrolling of the chrome + m_chromePage->mainFrame()->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff); + m_chromePage->mainFrame()->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff); + + // Display the page view offscreen to ensure that it doesn't grab focus from chrome widget + pageView->setGeometry(-1600, 0, 5, 5); + + // Connect QtWebPage signals + safe_connect(chromePage(), SIGNAL(frameCreated(QWebFrame*)), this, SLOT(frameCreated(QWebFrame*))); + safe_connect(chromePage(), SIGNAL(loadStarted()), this, SLOT(onLoadStarted())); + safe_connect(chromePage(), SIGNAL(loadFinished(bool)), this, SLOT(loadFinished(bool))); + safe_connect(chromePage(), SIGNAL(repaintRequested(QRect)), this, SLOT(repaintRequested(QRect))); + + //External links in chrome are delegated to a content view so we + //propagate the QWebPage::linkClicked signal. The idea is that + //chrome does not normally load external content into the chrome + //itself (though chrome can still load content via XHR). Typically, + //the active web view handles this signal. This allows chrome to contain + //links to external content that get loaded into the web view. An + //example would be a news feed that renders feed item titles in a chrome + //pop-up, but which loads the feed content into the main web view. + + chromePage()->setLinkDelegationPolicy(QWebPage::DelegateExternalLinks); + QObject::connect(chromePage(), SIGNAL(linkClicked(const QUrl&)), this, SIGNAL(delegateLink(const QUrl&))); + + installEventFilter(this); +} + +ChromeWidget::~ChromeWidget(){ + delete m_painter; + delete m_jsObject; + delete m_buffer; +} + + +QObject *ChromeWidget::jsObject() { + return static_cast(m_jsObject); +} + +#ifdef Q_OS_SYMBIAN +QPixmap * ChromeWidget::buffer() +#else +QImage * ChromeWidget::buffer() +#endif +{ + return m_buffer; +} + +QPainter * ChromeWidget::painter(){ + return m_painter; +} + +void ChromeWidget::resizeBuffer(){ + //qDebug() << "ChromeWidget::resizeBuffer: " << chromePage()->mainFrame()->contentsSize(); + if(m_painter) { + m_painter->end(); + delete m_painter; + m_painter = 0; + } + if(m_buffer) + delete m_buffer; +#ifdef Q_OS_SYMBIAN + m_buffer = new QPixmap(chromePage()->mainFrame()->contentsSize()); +#else + m_buffer = new QImage(chromePage()->mainFrame()->contentsSize(), QImage::Format_ARGB32_Premultiplied); +#endif + m_buffer->fill(Qt::transparent); + m_painter = new QPainter(m_buffer); +} + +void ChromeWidget::setChromeUrl(QString url) +{ + //qDebug() << "ChromeWidget::setChromeUrl: " << url; + if(chromePage() && chromePage()->mainFrame()){ +#ifdef G_TIMING + GTimer * t = new GTimer(); + t->start("ChromeWidget::setChromeUrl()"); +#endif + chromePage()->mainFrame()->load(QUrl(url)); +#ifdef G_TIMING + t->stop(); + t->save(); + delete t; +#endif + } +} + +void ChromeWidget::setGeometry(const QRect &rect) +{ + m_chromePage->setViewportSize(QSize(rect.size().width(), 1000)); + resizeBuffer(); + updateChildGeometries(); +} + + +void ChromeWidget::toggleVisibility(const QString & elementId) +{ + ChromeSnippet * snippet = getSnippet(elementId); + if(snippet) + snippet->toggleVisibility(); +} + +void ChromeWidget::setLocation(const QString& id, int x, int y) +{ + ChromeSnippet * snippet = getSnippet(id); + if(snippet) + snippet->setPos(x,y); +} + +void ChromeWidget::setAnchor(const QString& id, const QString& anchor){ + ChromeSnippet * snippet = getSnippet(id); + if(snippet) + snippet->setAnchor(anchor); +} + +void ChromeWidget::show(const QString & elementId, int x, int y) +{ + ChromeSnippet * snippet = getSnippet(elementId); + + if(snippet){ + snippet->setPos(x,y); + snippet->show(true); + } +} + +void ChromeWidget::show(const QString & elementId) +{ + ChromeSnippet * snippet = getSnippet(elementId); + + if(snippet){ + //snippet->show(true); + snippet->show(false); + } +} + + +void ChromeWidget::hide(const QString & elementId) +{ + ChromeSnippet * snippet = getSnippet(elementId); + if(snippet) + snippet->hide(true); +} + +void ChromeWidget::toggleAttention(const QString & elementId){ + ChromeSnippet * snippet = getSnippet(elementId); + if(snippet) { + //qDebug() << "ChromeWidget::toggleAttention " << elementId; + snippet->toggleAttention(); + } +} + +void ChromeWidget::setVisibilityAnimator(const QString& elementId, const QString & animatorName){ + ChromeSnippet * snippet = getSnippet(elementId); + if(snippet) { + VisibilityAnimator * animator = VisibilityAnimator::create(animatorName, snippet); + snippet->setVisibilityAnimator(animator); // NB: Move this to visibility animator implementation + } +} + +void ChromeWidget::setAttentionAnimator(const QString& elementId, const QString & animatorName){ +ChromeSnippet * snippet = getSnippet(elementId); + if(snippet) { + AttentionAnimator * animator = AttentionAnimator::create(animatorName, snippet); + snippet->setAttentionAnimator(animator); // NB: Move this to visibility animator implementation + } +} + +//NB: Factor out snippet cleanup and use in destructor too + +void ChromeWidget::onLoadStarted() // slot +{ + qDebug() << "ChromeWidget::onLoadStarted"; +#ifdef G_TIMING + GTimer * t = new GTimer(); + t->start("ChromeWidget::loadStarted"); +#endif + //First zero out all of the non-root snippets. These + //will be deleted when the root snippets are deleted. + QMapIterator i(m_snippetMap); + while(i.hasNext()){ + i.next(); + if(i.value()->parentItem() != m_parentItem){ + m_snippetMap[i.key()] = 0; + } + } + //Now delete the root snippets. + foreach(ChromeSnippet *snippet, m_snippetMap){ + if(snippet){ + //Remove about-to-be-deleted snippet from parent scene + m_parentChromeView->getScene()->removeItem(snippet); + delete snippet; + } + } + m_snippetMap.clear(); +// m_topSnippet = 0; +// m_bottomSnippet = 0; + //m_popSnippet = 0; + //Does anybody care about this signal? + emit loadStarted(); +#ifdef G_TIMING + t->stop(); + t->save(); + delete t; +#endif +} + + +QString ChromeWidget::getDisplayMode() +{ + return m_parentChromeView->getDisplayMode(); + +} + + +void ChromeWidget::frameCreated(QWebFrame* frame){ + Q_UNUSED(frame) + //qDebug() << "===>ChromeWidget::frameCreated"; +} + +void ChromeWidget::loadFinished(bool ok) // slot +{ +#ifdef G_TIMING + GTimer * t = new GTimer(); + t->start("ChromeWidget::loadFinished"); +#endif + qDebug() << "ChromeWidget::loadFinished"; + if(!ok) + { + qDebug() << "ChromeWidget::loadFinished: error"; + return; + } + getInitialChrome(); + resizeBuffer(); + updateChildGeometries(); + emit loadComplete(); +#ifdef G_TIMING + t->stop(); + t->save(); + delete t; +#endif +} + +void ChromeWidget::getInitialChrome(){ + + QWebElement doc = chromePage()->mainFrame()->documentElement(); +#if QT_VERSION < 0x040600 + QList initialSnippets = doc.findAll(".GinebraSnippet"); +#else + QList initialSnippets = doc.findAll(".GinebraSnippet").toList(); +#endif + foreach(QWebElement element, initialSnippets) { + ChromeSnippet* s = getSnippet(element.attribute("id")); + if((element.attribute("data-GinebraVisible","false"))=="true"){ + s->show(false); + } + else { + s->hide(); + } + } +} + +ChromeSnippet *ChromeWidget::getSnippet(const QString &docElementId, QGraphicsItem *parent) { + + ChromeSnippet *result = m_snippetMap.value(docElementId); + if(!result){ + QWebElement doc = chromePage()->mainFrame()->documentElement(); + QWebElement element = doc.findFirst("#" + docElementId); + QRect rect = getDocElementRect(docElementId); + if(!rect.isNull()){ + QGraphicsItem * p = (parent)?parent:m_parentItem; + + // Create the snippet, pass the ChromeWidget's javascript object in so that it can + // be used as the parent of the snippet's javascript object. + result = new ChromeSnippet(p, this, jsObject(), docElementId); + + // Make sure snippets are shown above the content view. + result->setZValue(3); + + //result->setAnchor("AnchorCenter"); + //qDebug() << "Creating snippet: " << docElementId << ":" << (int) result; + // Set up connections to freeze the main content page while snippets are being dragged + // to improve performance on complex pages. + safe_connect(result->getJSObject(), SIGNAL(dragStarted()), this, SIGNAL(dragStarted())); + safe_connect(result->getJSObject(), SIGNAL(dragFinished()), this, SIGNAL(dragFinished())); + //Note that the following can be inefficient if several snippets are + //made visible/invisible at once, which will result is successive + //updates to the viewport. Optimize this to coalesce updates. + safe_connect(result->getJSObject(), SIGNAL(onHide()), this, SLOT(updateViewPort())); + safe_connect(result->getJSObject(), SIGNAL(onShow()), this, SLOT(updateViewPort())); + //qDebug() << "Snippet child count: " << p->childItems().size() << " parent=" << ((QGraphicsWidget *)p)->objectName(); + //qDebug() << "ChromeWidget::getSnippet: " << docElementId << " " << rect; + + result->setOwnerArea(rect); + //Snippet size is determined by owner area. + result->resize(rect.size()); + //Set auto-layout attributes + result->setAnchor(element.attribute("data-GinebraAnchor", "AnchorNone")); + result->setHidesContent( element.attribute("data-GinebraHidesContent", "false") == "true" ); + result->setAnchorOffset( element.attribute("data-GinebraAnchorOffset", "0").toInt() ); //toInt() returns 0 for malformed string + m_snippetMap[docElementId] = result; + //NB: not very efficient + QList chromeButtons = getChildIdsByClassName(docElementId, "GinebraButtonSnippet").toList(); + //qDebug() << "Chrome row size: " << chromeButtons.size(); + for(int i = 0; i < chromeButtons.size();i++) { + qDebug() << "Chrome row button: " << chromeButtons[i].toString(); + getSnippet(chromeButtons[i].toString(),result); + } + + } + else{ + //qDebug() << "ChromeWidget::getSnippet: snippet not found, id=" << docElementId; + return 0; + } + }else{ + //qDebug() << "Found existing snippet: " << docElementId; + } + + return result; +} + +/* Do a re-layout of the chrome. This gets snippet geometries, sets positions + * and calculates the viewport size. This gets called when: + * - New chrome is loaded + * - The chrome is resized + * This doesn't get called when chrome snippet visibility changes + * or snippets get moved so that animations don't invoke multiple + * relayouts. This means that visiblity changes need to explicitly + * invoke a viewport size calculation if they want to resize the + * viewport. + */ + +void ChromeWidget::updateChildGeometries() +{ + QRect viewRect(QPoint(0,0), m_parentChromeView->geometry().size()); + //qDebug() << "ChromeWidget::updateChildGeometries: viewRect=" << viewRect; + //m_chromePage->setViewportSize(viewRect.size()); + + updateOwnerAreas(); + + //NB: It would be more efficient to calculate the viewport as snippet geometry is set + //though this ought to be done without duplicating code between here and updateViewport() + + foreach(ChromeSnippet *snippet, m_snippetMap) { + qreal sHeight = snippet->ownerArea().height(); + if(snippet->anchor()=="AnchorTop"){ + snippet->setPos(0, snippet->anchorOffset()); + snippet->resize(viewRect.width(), sHeight); + } + else if(snippet->anchor()=="AnchorBottom"){ + //NB: Why do we need to subtract 2 from y coord here??? + //snippet->setPos(0, viewRect.height() - sHeight - snippet->anchorOffset() -2); + snippet->setPos(0, viewRect.height() - sHeight - snippet->anchorOffset()); + snippet->resize(viewRect.width(), sHeight); + } + else if(snippet->anchor()=="AnchorCenter"){ + qreal sWidth = snippet->ownerArea().width(); + snippet->setPos((viewRect.width()-sWidth)/2,(viewRect.height()-sHeight)/2); + snippet->resize(sWidth, sHeight); + } + else if(snippet->anchor()=="AnchorFullScreen"){ + snippet->setRect(0,0,viewRect.width(), viewRect.height()); + } + snippet->updateChildGeometries(); + + } + + updateViewPort(); + + repaintRequested(viewRect); //Do intial repaint of the whole chrome after snippets are inited +} + +//Updates the current viewport size to the area not covered by visible top and bottom chrome. + +void ChromeWidget::updateViewPort() { + QRect viewPort(QPoint(0,0), m_parentChromeView->geometry().size()); + + //NB: Note that this algorithm assumes that anchor offsets do NOT + //shrink the viewport. I.e., if you have an offset snippet it is + //assumed either that it hides content (HidesContent attribute is set) + //or that it is being stacked on another anchored snippet. An offset snippet + //that is not being stacked on another snippet and that does not have content hiding + //set (HidesContent attribute) will typically show on top of the content window + //with the content window reduced by the size of the snippet. + int viewPortY = 0; + foreach(ChromeSnippet *snippet, m_snippetMap) { + if(!snippet->hidesContent()){ + if((snippet->anchor()=="AnchorTop") && snippet->isVisible() && !snippet->isHiding()){ + int snippetY = snippet->pos().y() + snippet->ownerArea().height(); + if (snippetY > viewPortY) { + viewPortY = snippetY; + } + } + else if((snippet->anchor()=="AnchorBottom") && snippet->isVisible() && !snippet->isHiding()){ + viewPort.adjust(0, 0, 0, (int)-snippet->ownerArea().height()); + } + } + } + viewPort.adjust(0, viewPortY, 0, 0); + emit viewPortResize(viewPort); +} + +//Explicitly reset the viewport to a specified rectangle + +void ChromeWidget::setViewPort(QRect viewPort){ + emit viewPortResize(viewPort); +} + +void ChromeWidget::networkRequestFinished(QNetworkReply *reply){ // slot + if(reply->error() != QNetworkReply::NoError) { + //qDebug() << "ChromeWidget::networkRequestFinished: " << reply->errorString(); + } +} + +// Called when some part of the chrome page needs repainting. Uses a custom event to delay calling mainFrame->render() +// since in some cases render() can crash -- apparently when it tries to paint an element that has +// been deleted (by javascript etc.). Coalesces multiple calls to repaintRequested() into one call to +// paintDirtyRegion(). +void ChromeWidget::repaintRequested(QRect dirtyRect){ // slot + //qDebug() << "ChromeWidget::repaintRequested: " << dirtyRect; + +#ifdef G_TIMING + GTimer * t = new GTimer(); + t->start("ChromeWidget::repaintRequested"); +#endif + +#ifdef Q_OS_SYMBIANXX // turn off the hack for now, remove eventually + // Hack to get around painting issue in text fields. Backspacing doesn't appear to generate + // repaint requests though the blinking caret does. Since the caret is very narrow this leaves + // behind artifacts of the character that was deleted. + dirtyRect.setRight(dirtyRect.right() + 20); + + //NB:Delayed repaints don't get invoked on NSP, at least on emulator + //so paint immediately. Note that delayed repaints are a work-around + //for JS/DOM issues in WebKit, so this needs to be revisited. + m_dirtyRegion = dirtyRect; + paintDirtyRegion(); +#else + if(m_dirtyRegion.isEmpty()) { + m_dirtyRegion += dirtyRect; + QCoreApplication::postEvent(this, new UpdateBufferEvent); + } + else + m_dirtyRegion += dirtyRect; +#endif +#ifdef G_TIMING + t->stop(); + t->save(); + delete t; +#endif +} + +void ChromeWidget::paintDirtyRegion() { + //qDebug() << "ChromeWidget::paintDirtyRegion" << m_dirtyRegion; + + if(m_dirtyRegion.isEmpty()) + return; + if(m_buffer){ + m_painter->save(); //NB: would it be more efficient just to create a new painter on the stack? + //Must set clip rect because frame may render background(?) outside dirty rect + m_painter->setClipRegion(m_dirtyRegion); + if(chromePage() && chromePage()->mainFrame()) + chromePage()->mainFrame()->render(m_painter, m_dirtyRegion); + m_painter->restore(); + } + + foreach(ChromeSnippet *snippet, m_snippetMap) { + if((snippet->parentItem() == m_parentItem) && snippet->isVisible() && m_dirtyRegion.intersects(snippet->ownerArea().toRect())) { + // qDebug() << "Dirty rect intersects: " << snippet->docElementId() << ": " << snippet->ownerArea().toRect(); + snippet->update(); + } + } + + // Clear dirty region. + m_dirtyRegion = QRegion(); +} + + +// Update owner areas of all snippets to allow for changes in chrome page geometry. +void ChromeWidget::updateOwnerAreas() { + foreach(ChromeSnippet *snippet, m_snippetMap) { + snippet->setOwnerArea(getDocElementRect(snippet->docElementId())); + } +} + +//NB: The following methods should also be implementable, and possibly +//more efficient, via the C++ DOM API + +void ChromeWidget::debugAlert(const QString &msg){ + chromePage()->mainFrame()->evaluateJavaScript("alert('" + msg + "')"); +} + +QVariant ChromeWidget::getDocElement(const QString &id) { + return chromePage()->mainFrame()->evaluateJavaScript("document.getElementById('" + id + "')"); +} + +QVariant ChromeWidget::getDocIdsByName(const QString &name){ + + QString js ( + "var elements = document.getElementsByName('" + name + "');" + "var result = new Array();" + "for(i = 0 ; i< elements.length; i++){" + " result[i]=elements[i].id;" + "}" + "result;" + ); + return chromePage()->mainFrame()->evaluateJavaScript(js); +} + +QVariant ChromeWidget::getDocIdsByClassName(const QString &name){ + + QString js ( + "var elements = document.getElementsByClassName('" + name + "');" + "var result = new Array();" + "for(i = 0 ; i< elements.length; i++){" + " result[i]=elements[i].id;" + "}" + "result;" + ); + return chromePage()->mainFrame()->evaluateJavaScript(js); +} + +QVariant ChromeWidget::getChildIdsByClassName(const QString &parentId, const QString &name){ + + QString js ( + "var elements = document.getElementsByClassName('" + name + "');" + "var result = new Array();" + "for(i = 0 ; i< elements.length; i++){" + "if(elements[i].parentNode.id == '" + parentId +"'){" + " result[i]=elements[i].id;" + "}" + "}" + "result;" + ); + return chromePage()->mainFrame()->evaluateJavaScript(js); + +} + +QSize ChromeWidget::getDocElementSize(const QString &id) { + QSize result; + QVariant jObj = getDocElement(id); + if(jObj.isValid()) { + QMap jMap = jObj.toMap(); + //qDebug() << "Tagname: " << (jMap["tagName"].toString()); + result.setWidth(jMap["clientWidth"].toInt()); + result.setHeight(jMap["clientHeight"].toInt()); + } + else { + qDebug() << "ChromeWidget::getDocElementSize: element not found. " << id; + } + return result; +} + +QString ChromeWidget::getDocElementAttribute(const QString &id, const QString &attribute) { + QString result; + QVariant jObj = getDocElement(id); + if(jObj.isValid()) { + QMap jMap = jObj.toMap(); + //qDebug() << "Tagname: " << (jMap["tagName"].toString()); + result = jMap[attribute].toString(); + } + else { + qDebug() << "ChromeWidget::getDocElementSize: element not found. " << id; + } + return result; +} + +QRect ChromeWidget::getDocElementRect(const QString &id) { + QString js("var obj = document.getElementById('" + id + "');" + "var width = obj.clientWidth;" + "var height = obj.clientHeight;" + "var curleft = curtop = 0;" + "do {" + " curleft += obj.offsetLeft;" + " curtop += obj.offsetTop;" + "} while (obj = obj.offsetParent);" + "[curleft, curtop, width, height]"); + QVariant jObj = chromePage()->mainFrame()->evaluateJavaScript(js); + if(jObj.isValid()) { + return QRect(jObj.toList()[0].toInt(), jObj.toList()[1].toInt(), jObj.toList()[2].toInt(), jObj.toList()[3].toInt()); + } + else { + qDebug() << "ChromeWidget::getDocElementRect: element not found. " << id; + return QRect(); + } +} + +// Private. This class shadows the Qt class QComboBoxPrivateContainer to provide access its +// the 'combo' pointer in eventFilter(). +class xQComboBoxPrivateContainer : public QFrame +{ + public: + int spacing() const; + QTimer blockMouseReleaseTimer; + QBasicTimer adjustSizeTimer; + QPoint initialClickPosition; + QComboBox *combo; + QAbstractItemView *view; + void *top; + void *bottom; +}; + +bool ChromeWidget::eventFilter(QObject *object, QEvent *event) +{ + // Shameless hack here. We need to intercept the creation of combobox drop-downs + // in the chrome and move them into their correct positions since the system thinks they belong + // off-screen over where the chrome page is actually rendered. Since drop-downs are grandchildren + // of the ChromeRenderer we start by watching for child added events, when one is created we + // watch it also for child added events too, thereby watching grandchild events. When we + // see a QComboBoxPrivateContainer (the drop-down list) being moved we move it instead into + // position just under the combobox. + + //qDebug() << "ChromeWidget::eventFilter: " << event->type(); + + switch ((int)event->type()) { + case QEvent::ChildAdded: + case QEvent::ChildPolished: + { + QChildEvent *childEvt = static_cast(event); + //qDebug() << " watching " << childEvt->child(); + childEvt->child()->installEventFilter(this); + break; + } + case QEvent::Move: + { + //QMoveEvent *evt = static_cast(event); + //qDebug() << " oldpos " << evt->oldPos() << " pos " << evt->pos(); + if(object->inherits("QComboBoxPrivateContainer")) { + xQComboBoxPrivateContainer *cbpc = static_cast(object); + QComboBox *combo = cbpc->combo; + QRect comboRect = combo->geometry(); + QPoint comboPos = comboRect.topLeft(); + ChromeSnippet *snippet = getSnippet(comboPos); + if(snippet) { + QPoint relativePos = comboPos - snippet->ownerArea().topLeft().toPoint(); + static_cast(object)->move(m_parentChromeView->mapToGlobal(QPoint(0,0)) + + snippet->rect().topLeft().toPoint() + + relativePos + + QPoint(0, comboRect.height())); + } + } + break; + } + default: + { + if(event->type() == UpdateBufferEvent::customType()) { + if(object == this) { + //qDebug() << "ChromeWidget::eventFilter: UpdateBufferEvent " << (void*)object << event; + paintDirtyRegion(); + } + } + break; + } + } + + return QObject::eventFilter(object, event); +} + +ChromeSnippet *ChromeWidget::getSnippet(QPoint pos) const { + foreach(ChromeSnippet *snippet, m_snippetMap) { + if(snippet->ownerArea().contains(pos)) + return snippet; + } + return 0; +} + +void ChromeWidget::dump() { + qDebug() << "ChromeWidget::dump"; + foreach(ChromeSnippet *snippet, m_snippetMap) { + snippet->dump(); + qDebug() << "------"; + } +}