ginebra/chromewidget.cpp
changeset 3 0954f5dd2cd0
parent 1 b0dd75e285d2
child 4 d5cdb6bc139d
equal deleted inserted replaced
1:b0dd75e285d2 3:0954f5dd2cd0
     1 /*
       
     2 * Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
       
     3 * All rights reserved.
       
     4 *
       
     5 * This program is free software: you can redistribute it and/or modify
       
     6 * it under the terms of the GNU Lesser General Public License as published by
       
     7 * the Free Software Foundation, version 2.1 of the License.
       
     8 * 
       
     9 * This program is distributed in the hope that it will be useful,
       
    10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       
    12 * GNU Lesser General Public License for more details.
       
    13 *
       
    14 * You should have received a copy of the GNU Lesser General Public License
       
    15 * along with this program.  If not, 
       
    16 * see "http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html/".
       
    17 *
       
    18 * Description:
       
    19 *
       
    20 */
       
    21 
       
    22 #include <QtGui>
       
    23 #include <QNetworkReply>
       
    24 #include <QGraphicsScene>
       
    25 #include <QImage>
       
    26 #include <QTimeLine>
       
    27 #include <QPainterPath>
       
    28 #include "qwebpage.h"
       
    29 #include "qwebframe.h"
       
    30 #include "qwebview.h"
       
    31 #include "qwebelement.h"
       
    32 #include "chromewidget.h"
       
    33 #include "chromerenderer.h"
       
    34 #include "chromesnippet.h"
       
    35 #include "chromewidgetjsobject.h"
       
    36 #include "chromeview.h"
       
    37 #include "attentionanimator.h"
       
    38 #include "visibilityanimator.h"
       
    39 //NB: remove these
       
    40 #include "animations/fadeanimator.h"
       
    41 #include "animations/bounceanimator.h"
       
    42 #include "animations/flyoutanimator.h"
       
    43 
       
    44 #include "utilities.h"
       
    45 #include <assert.h>
       
    46 
       
    47 //Temporary include
       
    48 #include <QDebug>
       
    49 
       
    50 #ifdef G_TIMING
       
    51 #include "gtimer.h"
       
    52 #endif
       
    53 
       
    54 class UpdateBufferEvent : public QEvent {
       
    55   public:
       
    56     UpdateBufferEvent()
       
    57       : QEvent(customType()) {
       
    58     }
       
    59     static QEvent::Type customType() {
       
    60         static int type = QEvent::registerEventType();
       
    61         return (QEvent::Type) type;
       
    62     }
       
    63 };
       
    64 
       
    65 ChromeWidget::ChromeWidget(ChromeView *parentChromeView, QGraphicsItem *parent, const QString &jsName)
       
    66   :QObject(),
       
    67    m_chromePage(0),
       
    68    m_parentItem(parent),
       
    69    m_parentChromeView(parentChromeView),
       
    70    m_state(maximized),
       
    71    m_buffer(0),
       
    72    m_painter(0),
       
    73    m_dirtyTimer(0),
       
    74    m_jsObject(new ChromeWidgetJSObject(this, this, jsName))
       
    75 {
       
    76   // Connect signals generated by this object to signals on the javascript object.
       
    77   safe_connect(this, SIGNAL(loadStarted()), m_jsObject, SIGNAL(loadStarted()));
       
    78   safe_connect(this, SIGNAL(loadComplete()), m_jsObject, SIGNAL(loadComplete()));
       
    79   safe_connect(this, SIGNAL(dragStarted()), m_jsObject, SIGNAL(dragStarted()));
       
    80   safe_connect(this, SIGNAL(dragFinished()), m_jsObject, SIGNAL(dragFinished()));
       
    81   safe_connect(this, SIGNAL(viewPortResize(QRect)), m_jsObject, SIGNAL(viewPortResize(QRect)));
       
    82 
       
    83   //Allocate an instance of webkit to render the chrome
       
    84   ChromeRenderer *pageView = new ChromeRenderer(parentChromeView->parentWidget());
       
    85   safe_connect(pageView, SIGNAL(symbianCarriageReturn()), m_jsObject, SIGNAL(symbianCarriageReturn()));
       
    86   //QWebView *pageView = new QWebView(parentChromeView->parentWidget());
       
    87 
       
    88   pageView->show();
       
    89   m_chromePage = pageView->page();
       
    90 
       
    91   //Render to a transparent background (see WebKit bug 29414)
       
    92   QPalette palette = m_chromePage->palette();
       
    93   palette.setColor(QPalette::Base, Qt::transparent);
       
    94   m_chromePage->setPalette(palette);
       
    95 
       
    96   // No scrolling of the chrome
       
    97   m_chromePage->mainFrame()->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff);
       
    98   m_chromePage->mainFrame()->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff);
       
    99 
       
   100   // Display the page view offscreen to ensure that it doesn't grab focus from chrome widget
       
   101   pageView->setGeometry(-1600, 0, 5, 5);
       
   102 
       
   103   // Connect QtWebPage signals
       
   104   safe_connect(chromePage(), SIGNAL(frameCreated(QWebFrame*)), this, SLOT(frameCreated(QWebFrame*)));
       
   105   safe_connect(chromePage(), SIGNAL(loadStarted()), this, SLOT(onLoadStarted()));
       
   106   safe_connect(chromePage(), SIGNAL(loadFinished(bool)), this, SLOT(loadFinished(bool)));
       
   107   safe_connect(chromePage(), SIGNAL(repaintRequested(QRect)), this, SLOT(repaintRequested(QRect)));
       
   108 
       
   109   //External links in chrome are delegated to a content view so we
       
   110   //propagate the QWebPage::linkClicked signal. The idea is that
       
   111   //chrome does not normally load external content into the chrome
       
   112   //itself (though chrome can still load content via XHR). Typically,
       
   113   //the active  web view handles this signal. This allows chrome to contain
       
   114   //links to external content that get loaded into the web view. An
       
   115   //example would be a news feed that renders feed item titles in a chrome
       
   116   //pop-up, but which loads the feed content into the main web view.
       
   117 
       
   118   chromePage()->setLinkDelegationPolicy(QWebPage::DelegateExternalLinks);
       
   119   QObject::connect(chromePage(), SIGNAL(linkClicked(const QUrl&)), this, SIGNAL(delegateLink(const QUrl&)));
       
   120 
       
   121   installEventFilter(this);
       
   122 }
       
   123 
       
   124 ChromeWidget::~ChromeWidget(){
       
   125   delete m_painter;
       
   126   delete m_jsObject; 
       
   127   delete m_buffer;
       
   128 }
       
   129 
       
   130 
       
   131 QObject *ChromeWidget::jsObject() { 
       
   132   return static_cast<QObject *>(m_jsObject); 
       
   133 }
       
   134 
       
   135 #ifdef Q_OS_SYMBIAN
       
   136 QPixmap * ChromeWidget::buffer()
       
   137 #else
       
   138 QImage * ChromeWidget::buffer()
       
   139 #endif
       
   140 {
       
   141   return m_buffer;
       
   142 }
       
   143 
       
   144 QPainter * ChromeWidget::painter(){
       
   145   return m_painter;
       
   146 }
       
   147 
       
   148 void ChromeWidget::resizeBuffer(){
       
   149   //qDebug() << "ChromeWidget::resizeBuffer: " << chromePage()->mainFrame()->contentsSize();
       
   150   if(m_painter) {
       
   151       m_painter->end();
       
   152       delete m_painter;
       
   153       m_painter = 0;
       
   154   }
       
   155   if(m_buffer) 
       
   156       delete m_buffer;
       
   157 #ifdef Q_OS_SYMBIAN
       
   158   m_buffer = new QPixmap(chromePage()->mainFrame()->contentsSize());
       
   159 #else
       
   160   m_buffer = new QImage(chromePage()->mainFrame()->contentsSize(), QImage::Format_ARGB32_Premultiplied);
       
   161 #endif
       
   162   m_buffer->fill(Qt::transparent);  
       
   163   m_painter = new QPainter(m_buffer);
       
   164 }
       
   165 
       
   166 void ChromeWidget::setChromeUrl(QString url)
       
   167 {
       
   168   //qDebug() << "ChromeWidget::setChromeUrl: " << url;
       
   169   if(chromePage() && chromePage()->mainFrame()){
       
   170 #ifdef G_TIMING
       
   171     GTimer * t = new GTimer();
       
   172     t->start("ChromeWidget::setChromeUrl()");
       
   173 #endif
       
   174     chromePage()->mainFrame()->load(QUrl(url));
       
   175 #ifdef G_TIMING
       
   176    t->stop();
       
   177    t->save();
       
   178    delete t;
       
   179 #endif
       
   180   }
       
   181 }
       
   182 
       
   183 void ChromeWidget::setGeometry(const QRect &rect)
       
   184 {
       
   185   m_chromePage->setViewportSize(QSize(rect.size().width(), 1000));
       
   186   resizeBuffer();
       
   187   updateChildGeometries();
       
   188 }
       
   189 
       
   190 
       
   191 void ChromeWidget::toggleVisibility(const QString & elementId)
       
   192 {
       
   193   ChromeSnippet * snippet = getSnippet(elementId);
       
   194   if(snippet)
       
   195     snippet->toggleVisibility();
       
   196 }
       
   197 
       
   198 void ChromeWidget::setLocation(const QString& id, int x, int y)
       
   199 {
       
   200   ChromeSnippet * snippet = getSnippet(id);
       
   201   if(snippet)
       
   202     snippet->setPos(x,y);
       
   203 }
       
   204 
       
   205 void ChromeWidget::setAnchor(const QString& id, const QString& anchor){
       
   206   ChromeSnippet * snippet = getSnippet(id);
       
   207   if(snippet)
       
   208     snippet->setAnchor(anchor);
       
   209 }
       
   210 
       
   211 void ChromeWidget::show(const QString & elementId, int x, int y)
       
   212 {
       
   213   ChromeSnippet * snippet = getSnippet(elementId);
       
   214 
       
   215   if(snippet){
       
   216     snippet->setPos(x,y);
       
   217     snippet->show(true);
       
   218   }
       
   219 }
       
   220 
       
   221 void ChromeWidget::show(const QString & elementId)
       
   222 {
       
   223   ChromeSnippet * snippet = getSnippet(elementId);
       
   224 
       
   225   if(snippet){
       
   226     //snippet->show(true);
       
   227      snippet->show(false);
       
   228   }
       
   229 }
       
   230 
       
   231 
       
   232 void ChromeWidget::hide(const QString & elementId)
       
   233 {
       
   234   ChromeSnippet * snippet = getSnippet(elementId);
       
   235   if(snippet)
       
   236     snippet->hide(true);
       
   237 }
       
   238 
       
   239 void ChromeWidget::toggleAttention(const QString & elementId){
       
   240   ChromeSnippet * snippet = getSnippet(elementId);
       
   241   if(snippet) {
       
   242     //qDebug() << "ChromeWidget::toggleAttention " << elementId; 
       
   243     snippet->toggleAttention();
       
   244   }
       
   245 }
       
   246 
       
   247 void ChromeWidget::setVisibilityAnimator(const QString& elementId, const QString & animatorName){
       
   248  ChromeSnippet * snippet = getSnippet(elementId);
       
   249   if(snippet) {
       
   250     VisibilityAnimator * animator = VisibilityAnimator::create(animatorName, snippet);
       
   251     snippet->setVisibilityAnimator(animator); // NB: Move this to visibility animator implementation
       
   252   }
       
   253 }
       
   254 
       
   255 void ChromeWidget::setAttentionAnimator(const QString& elementId, const QString & animatorName){
       
   256 ChromeSnippet * snippet = getSnippet(elementId);
       
   257   if(snippet) {
       
   258      AttentionAnimator * animator = AttentionAnimator::create(animatorName, snippet);
       
   259     snippet->setAttentionAnimator(animator); // NB: Move this to visibility animator implementation
       
   260   }
       
   261 }
       
   262 
       
   263 //NB: Factor out snippet cleanup and use in destructor too
       
   264 
       
   265 void ChromeWidget::onLoadStarted()  // slot
       
   266 {
       
   267   qDebug() << "ChromeWidget::onLoadStarted";
       
   268 #ifdef G_TIMING
       
   269   GTimer * t = new GTimer();
       
   270   t->start("ChromeWidget::loadStarted");
       
   271 #endif
       
   272   //First zero out all of the non-root snippets. These
       
   273   //will be deleted when the root snippets are deleted.
       
   274   QMapIterator<QString, ChromeSnippet*> i(m_snippetMap);
       
   275   while(i.hasNext()){
       
   276     i.next();
       
   277     if(i.value()->parentItem() != m_parentItem){
       
   278       m_snippetMap[i.key()] = 0;
       
   279     }
       
   280   }
       
   281   //Now delete the root snippets.
       
   282   foreach(ChromeSnippet *snippet, m_snippetMap){
       
   283     if(snippet){
       
   284       //Remove about-to-be-deleted snippet from parent scene
       
   285       m_parentChromeView->getScene()->removeItem(snippet);
       
   286       delete snippet;
       
   287     }
       
   288   }
       
   289   m_snippetMap.clear();
       
   290 //  m_topSnippet = 0;
       
   291 //  m_bottomSnippet = 0;
       
   292   //m_popSnippet = 0;
       
   293   //Does anybody care about this signal?
       
   294   emit loadStarted();
       
   295 #ifdef G_TIMING
       
   296   t->stop();
       
   297   t->save();
       
   298   delete t;
       
   299 #endif
       
   300 }
       
   301 
       
   302 
       
   303 QString ChromeWidget::getDisplayMode() 
       
   304 {
       
   305 	return m_parentChromeView->getDisplayMode(); 
       
   306 	
       
   307 }
       
   308 	
       
   309 	
       
   310 void ChromeWidget::frameCreated(QWebFrame* frame){
       
   311   Q_UNUSED(frame)
       
   312   //qDebug() << "===>ChromeWidget::frameCreated";
       
   313 }
       
   314 
       
   315 void ChromeWidget::loadFinished(bool ok)  // slot
       
   316 {
       
   317 #ifdef G_TIMING
       
   318   GTimer * t = new GTimer();
       
   319   t->start("ChromeWidget::loadFinished");
       
   320 #endif
       
   321   qDebug() << "ChromeWidget::loadFinished";
       
   322   if(!ok)
       
   323   {
       
   324     qDebug() << "ChromeWidget::loadFinished: error";
       
   325     return;
       
   326   }
       
   327   getInitialChrome();
       
   328   resizeBuffer();
       
   329   updateChildGeometries();
       
   330   emit loadComplete();
       
   331 #ifdef G_TIMING
       
   332   t->stop();
       
   333   t->save();
       
   334   delete t;
       
   335 #endif
       
   336 }
       
   337 
       
   338 void ChromeWidget::getInitialChrome(){
       
   339   
       
   340   QWebElement doc = chromePage()->mainFrame()->documentElement();
       
   341 #if QT_VERSION < 0x040600
       
   342   QList <QWebElement> initialSnippets = doc.findAll(".GinebraSnippet");
       
   343 #else
       
   344   QList <QWebElement> initialSnippets = doc.findAll(".GinebraSnippet").toList();
       
   345 #endif
       
   346   foreach(QWebElement element, initialSnippets) {
       
   347     ChromeSnippet* s = getSnippet(element.attribute("id"));
       
   348     if((element.attribute("data-GinebraVisible","false"))=="true"){
       
   349       s->show(false);
       
   350     }
       
   351     else {
       
   352       s->hide();
       
   353     }
       
   354   }
       
   355 }
       
   356 
       
   357 ChromeSnippet *ChromeWidget::getSnippet(const QString &docElementId, QGraphicsItem *parent) {
       
   358  
       
   359   ChromeSnippet *result = m_snippetMap.value(docElementId);
       
   360   if(!result){
       
   361     QWebElement doc = chromePage()->mainFrame()->documentElement();
       
   362     QWebElement element = doc.findFirst("#" + docElementId);
       
   363     QRect rect = getDocElementRect(docElementId);
       
   364     if(!rect.isNull()){
       
   365       QGraphicsItem * p = (parent)?parent:m_parentItem;
       
   366       
       
   367       // Create the snippet, pass the ChromeWidget's javascript object in so that it can
       
   368       // be used as the parent of the snippet's javascript object.
       
   369       result = new ChromeSnippet(p, this, jsObject(), docElementId);
       
   370       
       
   371       // Make sure snippets are shown above the content view.
       
   372       result->setZValue(3);
       
   373       
       
   374       //result->setAnchor("AnchorCenter");
       
   375       //qDebug() << "Creating snippet: " << docElementId << ":" << (int) result;  
       
   376       // Set up connections to freeze the main content page while snippets are being dragged
       
   377       // to improve performance on complex pages.
       
   378       safe_connect(result->getJSObject(), SIGNAL(dragStarted()), this, SIGNAL(dragStarted()));
       
   379       safe_connect(result->getJSObject(), SIGNAL(dragFinished()), this, SIGNAL(dragFinished()));
       
   380       //Note that the following can be inefficient if several snippets are
       
   381       //made visible/invisible at once, which will result is successive
       
   382       //updates to the viewport. Optimize this to coalesce updates.
       
   383       safe_connect(result->getJSObject(), SIGNAL(onHide()), this, SLOT(updateViewPort()));
       
   384       safe_connect(result->getJSObject(), SIGNAL(onShow()), this, SLOT(updateViewPort()));
       
   385       //qDebug() << "Snippet child count: " << p->childItems().size() << " parent=" << ((QGraphicsWidget *)p)->objectName();
       
   386       //qDebug() << "ChromeWidget::getSnippet: " << docElementId << " " << rect;
       
   387 
       
   388       result->setOwnerArea(rect);
       
   389       //Snippet size is determined by owner area.
       
   390       result->resize(rect.size());
       
   391       //Set auto-layout attributes
       
   392       result->setAnchor(element.attribute("data-GinebraAnchor", "AnchorNone"));
       
   393       result->setHidesContent( element.attribute("data-GinebraHidesContent", "false") == "true" );
       
   394       result->setAnchorOffset( element.attribute("data-GinebraAnchorOffset", "0").toInt() ); //toInt() returns 0 for malformed string
       
   395       m_snippetMap[docElementId] = result;
       
   396       //NB: not very efficient
       
   397       QList <QVariant> chromeButtons = getChildIdsByClassName(docElementId, "GinebraButtonSnippet").toList();
       
   398       //qDebug() << "Chrome row size: " << chromeButtons.size();
       
   399       for(int i = 0; i < chromeButtons.size();i++) {
       
   400         qDebug() << "Chrome row button: " << chromeButtons[i].toString();
       
   401         getSnippet(chromeButtons[i].toString(),result);
       
   402       }
       
   403   
       
   404     }
       
   405     else{
       
   406       //qDebug() << "ChromeWidget::getSnippet: snippet not found, id=" << docElementId;
       
   407       return 0;
       
   408     }
       
   409   }else{
       
   410     //qDebug() << "Found existing snippet: " << docElementId;
       
   411   }
       
   412 
       
   413   return result;
       
   414 }
       
   415 
       
   416 /* Do a re-layout of the chrome. This gets snippet geometries, sets positions
       
   417  * and calculates the viewport size. This gets called when:
       
   418  * - New chrome is loaded
       
   419  * - The chrome is resized
       
   420  * This doesn't get called when chrome snippet visibility changes
       
   421  * or snippets get moved so that animations don't invoke multiple
       
   422  * relayouts. This means that visiblity changes need to explicitly
       
   423  * invoke a viewport size calculation if they want to resize the
       
   424  * viewport.
       
   425  */
       
   426 
       
   427 void ChromeWidget::updateChildGeometries()
       
   428 {
       
   429   QRect viewRect(QPoint(0,0), m_parentChromeView->geometry().size());
       
   430   //qDebug() << "ChromeWidget::updateChildGeometries: viewRect=" << viewRect;
       
   431   //m_chromePage->setViewportSize(viewRect.size());
       
   432 
       
   433   updateOwnerAreas();
       
   434   
       
   435   //NB: It would be more efficient to calculate the viewport as snippet geometry is set
       
   436   //though this ought to be done without duplicating code between here and updateViewport()
       
   437   
       
   438   foreach(ChromeSnippet *snippet, m_snippetMap) {
       
   439     qreal sHeight = snippet->ownerArea().height();
       
   440     if(snippet->anchor()=="AnchorTop"){
       
   441       snippet->setPos(0, snippet->anchorOffset());
       
   442       snippet->resize(viewRect.width(), sHeight);
       
   443     }
       
   444     else if(snippet->anchor()=="AnchorBottom"){
       
   445       //NB: Why do we need to subtract 2 from y coord here???
       
   446       //snippet->setPos(0, viewRect.height() - sHeight - snippet->anchorOffset() -2);
       
   447       snippet->setPos(0, viewRect.height() - sHeight - snippet->anchorOffset());
       
   448       snippet->resize(viewRect.width(), sHeight);
       
   449     }
       
   450     else if(snippet->anchor()=="AnchorCenter"){
       
   451       qreal sWidth = snippet->ownerArea().width();
       
   452       snippet->setPos((viewRect.width()-sWidth)/2,(viewRect.height()-sHeight)/2);
       
   453       snippet->resize(sWidth, sHeight);
       
   454     }
       
   455     else if(snippet->anchor()=="AnchorFullScreen"){
       
   456       snippet->setRect(0,0,viewRect.width(), viewRect.height());
       
   457     }
       
   458     snippet->updateChildGeometries();
       
   459     
       
   460   }
       
   461   
       
   462   updateViewPort();
       
   463 
       
   464   repaintRequested(viewRect); //Do intial repaint of the whole chrome after snippets are inited
       
   465 }
       
   466 
       
   467 //Updates the current viewport size to the area not covered by visible top and bottom chrome.
       
   468 
       
   469 void ChromeWidget::updateViewPort() {
       
   470   QRect viewPort(QPoint(0,0), m_parentChromeView->geometry().size());
       
   471   
       
   472   //NB: Note that this algorithm assumes that anchor offsets do NOT
       
   473   //shrink the viewport. I.e., if you have an offset snippet it is
       
   474   //assumed either that it hides content (HidesContent attribute is set)
       
   475   //or that it is being stacked on another anchored snippet. An offset snippet 
       
   476   //that is not being stacked on another snippet and that does not have content hiding
       
   477   //set (HidesContent attribute) will typically show on top of the content window
       
   478   //with the content window reduced by the size of the snippet.
       
   479   int viewPortY = 0;
       
   480   foreach(ChromeSnippet *snippet, m_snippetMap) {
       
   481     if(!snippet->hidesContent()){
       
   482       if((snippet->anchor()=="AnchorTop") && snippet->isVisible() && !snippet->isHiding()){
       
   483         int snippetY = snippet->pos().y() + snippet->ownerArea().height();
       
   484         if  (snippetY > viewPortY) {
       
   485             viewPortY = snippetY;
       
   486         }
       
   487       }
       
   488       else if((snippet->anchor()=="AnchorBottom") && snippet->isVisible() && !snippet->isHiding()){
       
   489         viewPort.adjust(0, 0, 0, (int)-snippet->ownerArea().height());
       
   490       }
       
   491     }
       
   492   }
       
   493   viewPort.adjust(0, viewPortY, 0, 0);
       
   494   emit viewPortResize(viewPort);
       
   495 }
       
   496 
       
   497 //Explicitly reset the viewport to a specified rectangle
       
   498 
       
   499 void ChromeWidget::setViewPort(QRect viewPort){
       
   500   emit viewPortResize(viewPort);
       
   501 }
       
   502 
       
   503 void ChromeWidget::networkRequestFinished(QNetworkReply *reply){  // slot
       
   504   if(reply->error() != QNetworkReply::NoError) {
       
   505     //qDebug() << "ChromeWidget::networkRequestFinished: " << reply->errorString();
       
   506   }
       
   507 }
       
   508 
       
   509 // Called when some part of the chrome page needs repainting.  Uses a custom event to delay calling mainFrame->render()
       
   510 // since in some cases render() can crash -- apparently when it tries to paint an element that has
       
   511 // been deleted (by javascript etc.).  Coalesces multiple calls to repaintRequested() into one call to
       
   512 // paintDirtyRegion().
       
   513 void ChromeWidget::repaintRequested(QRect dirtyRect){  // slot
       
   514   //qDebug() << "ChromeWidget::repaintRequested: " << dirtyRect;
       
   515 
       
   516 #ifdef G_TIMING
       
   517  GTimer * t = new GTimer();
       
   518  t->start("ChromeWidget::repaintRequested");
       
   519 #endif
       
   520 
       
   521 #ifdef Q_OS_SYMBIANXX // turn off the hack for now, remove eventually
       
   522   // Hack to get around painting issue in text fields.  Backspacing doesn't appear to generate
       
   523   // repaint requests though the blinking caret does.  Since the caret is very narrow this leaves 
       
   524   // behind artifacts of the character that was deleted.
       
   525   dirtyRect.setRight(dirtyRect.right() + 20);
       
   526   
       
   527   //NB:Delayed repaints don't get invoked on NSP, at least on emulator
       
   528   //so paint immediately. Note that delayed repaints are a work-around
       
   529   //for JS/DOM issues in WebKit, so this needs to be revisited.
       
   530   m_dirtyRegion = dirtyRect;
       
   531   paintDirtyRegion();
       
   532 #else
       
   533   if(m_dirtyRegion.isEmpty()) {
       
   534       m_dirtyRegion += dirtyRect;
       
   535       QCoreApplication::postEvent(this, new UpdateBufferEvent);
       
   536   }
       
   537   else
       
   538       m_dirtyRegion += dirtyRect;
       
   539 #endif
       
   540 #ifdef G_TIMING
       
   541   t->stop();
       
   542   t->save();
       
   543   delete t;
       
   544 #endif
       
   545 }
       
   546 
       
   547 void ChromeWidget::paintDirtyRegion() {
       
   548   //qDebug() << "ChromeWidget::paintDirtyRegion" << m_dirtyRegion;
       
   549   
       
   550   if(m_dirtyRegion.isEmpty())
       
   551       return;
       
   552   if(m_buffer){
       
   553     m_painter->save(); //NB: would it be more efficient just to create a new painter on the stack?
       
   554     //Must set clip rect because frame may render background(?) outside dirty rect
       
   555     m_painter->setClipRegion(m_dirtyRegion);
       
   556     if(chromePage() && chromePage()->mainFrame())
       
   557         chromePage()->mainFrame()->render(m_painter, m_dirtyRegion);
       
   558     m_painter->restore();
       
   559   }
       
   560 
       
   561   foreach(ChromeSnippet *snippet, m_snippetMap) {
       
   562     if((snippet->parentItem() == m_parentItem) && snippet->isVisible() && m_dirtyRegion.intersects(snippet->ownerArea().toRect())) {
       
   563       //  qDebug() << "Dirty rect intersects: " << snippet->docElementId() << ": " << snippet->ownerArea().toRect();
       
   564       snippet->update();
       
   565     }
       
   566   }
       
   567 
       
   568   // Clear dirty region.
       
   569   m_dirtyRegion = QRegion();
       
   570 }
       
   571 
       
   572 
       
   573 // Update owner areas of all snippets to allow for changes in chrome page geometry.
       
   574 void ChromeWidget::updateOwnerAreas() {
       
   575   foreach(ChromeSnippet *snippet, m_snippetMap) {
       
   576     snippet->setOwnerArea(getDocElementRect(snippet->docElementId()));
       
   577   }
       
   578 }
       
   579 
       
   580 //NB: The following methods should also be implementable, and possibly
       
   581 //more efficient, via the C++ DOM API
       
   582 
       
   583 void ChromeWidget::debugAlert(const QString &msg){
       
   584   chromePage()->mainFrame()->evaluateJavaScript("alert('" + msg + "')");
       
   585 }
       
   586 
       
   587 QVariant ChromeWidget::getDocElement(const QString &id) {
       
   588   return chromePage()->mainFrame()->evaluateJavaScript("document.getElementById('" + id + "')");
       
   589 }
       
   590 
       
   591 QVariant ChromeWidget::getDocIdsByName(const QString &name){
       
   592 
       
   593   QString js (
       
   594 	      "var elements = document.getElementsByName('" + name + "');"
       
   595               "var result = new Array();"
       
   596               "for(i = 0 ; i< elements.length; i++){"
       
   597               " result[i]=elements[i].id;"
       
   598               "}"
       
   599               "result;"
       
   600 	     );
       
   601   return chromePage()->mainFrame()->evaluateJavaScript(js);
       
   602 }
       
   603 
       
   604 QVariant ChromeWidget::getDocIdsByClassName(const QString &name){
       
   605 
       
   606   QString js (
       
   607               "var elements = document.getElementsByClassName('" + name + "');"
       
   608               "var result = new Array();"
       
   609               "for(i = 0 ; i< elements.length; i++){"
       
   610               " result[i]=elements[i].id;"
       
   611               "}"
       
   612               "result;"
       
   613              );
       
   614   return chromePage()->mainFrame()->evaluateJavaScript(js);
       
   615 }
       
   616 
       
   617 QVariant ChromeWidget::getChildIdsByClassName(const QString &parentId, const QString &name){
       
   618 
       
   619   QString js (
       
   620 	      "var elements = document.getElementsByClassName('" + name + "');"
       
   621               "var result = new Array();"
       
   622               "for(i = 0 ; i< elements.length; i++){"
       
   623               "if(elements[i].parentNode.id == '" + parentId +"'){"
       
   624               " result[i]=elements[i].id;"
       
   625               "}"
       
   626               "}"
       
   627               "result;"
       
   628 	     );
       
   629   return chromePage()->mainFrame()->evaluateJavaScript(js);
       
   630 
       
   631 }
       
   632 
       
   633 QSize ChromeWidget::getDocElementSize(const QString &id) {
       
   634   QSize result;
       
   635   QVariant jObj = getDocElement(id);
       
   636   if(jObj.isValid()) {
       
   637       QMap<QString, QVariant> jMap = jObj.toMap();
       
   638       //qDebug() << "Tagname: " << (jMap["tagName"].toString());
       
   639       result.setWidth(jMap["clientWidth"].toInt());
       
   640       result.setHeight(jMap["clientHeight"].toInt());
       
   641   }
       
   642   else {
       
   643     qDebug() << "ChromeWidget::getDocElementSize: element not found. " << id;
       
   644   }
       
   645   return result;
       
   646 }
       
   647 
       
   648 QString ChromeWidget::getDocElementAttribute(const QString &id, const QString &attribute) {
       
   649   QString result;
       
   650   QVariant jObj = getDocElement(id);
       
   651   if(jObj.isValid()) {
       
   652       QMap<QString, QVariant> jMap = jObj.toMap();
       
   653       //qDebug() << "Tagname: " << (jMap["tagName"].toString());
       
   654       result = jMap[attribute].toString();
       
   655   }
       
   656   else {
       
   657     qDebug() << "ChromeWidget::getDocElementSize: element not found. " << id;
       
   658   }
       
   659   return result;
       
   660 }
       
   661 
       
   662 QRect ChromeWidget::getDocElementRect(const QString &id) {
       
   663   QString js("var obj = document.getElementById('" + id + "');"
       
   664              "var width = obj.clientWidth;"
       
   665              "var height = obj.clientHeight;"
       
   666              "var curleft = curtop = 0;"
       
   667              "do {"
       
   668              "  curleft += obj.offsetLeft;"
       
   669              "  curtop += obj.offsetTop;"
       
   670              "} while (obj = obj.offsetParent);"
       
   671              "[curleft, curtop, width, height]");
       
   672   QVariant jObj = chromePage()->mainFrame()->evaluateJavaScript(js);
       
   673   if(jObj.isValid()) {
       
   674     return QRect(jObj.toList()[0].toInt(), jObj.toList()[1].toInt(), jObj.toList()[2].toInt(), jObj.toList()[3].toInt());
       
   675   }
       
   676   else {
       
   677     qDebug() << "ChromeWidget::getDocElementRect: element not found. " << id;
       
   678     return QRect();
       
   679   }
       
   680 }
       
   681 
       
   682 // Private.  This class shadows the Qt class QComboBoxPrivateContainer to provide access its
       
   683 // the 'combo' pointer in eventFilter().
       
   684 class xQComboBoxPrivateContainer : public QFrame
       
   685 {
       
   686   public:
       
   687     int spacing() const;
       
   688     QTimer blockMouseReleaseTimer;
       
   689     QBasicTimer adjustSizeTimer;
       
   690     QPoint initialClickPosition;
       
   691     QComboBox *combo;
       
   692     QAbstractItemView *view;
       
   693     void *top;
       
   694     void *bottom;
       
   695 };
       
   696 
       
   697 bool ChromeWidget::eventFilter(QObject *object, QEvent *event)
       
   698 {
       
   699     // Shameless hack here.  We need to intercept the creation of combobox drop-downs
       
   700     // in the chrome and move them into their correct positions since the system thinks they belong
       
   701     // off-screen over where the chrome page is actually rendered.  Since drop-downs are grandchildren
       
   702     // of the ChromeRenderer we start by watching for child added events, when one is created we
       
   703     // watch it also for child added events too, thereby watching grandchild events.  When we
       
   704     // see a QComboBoxPrivateContainer (the drop-down list) being moved we move it instead into
       
   705     // position just under the combobox.
       
   706 
       
   707     //qDebug() << "ChromeWidget::eventFilter: " << event->type();
       
   708 
       
   709     switch ((int)event->type()) {
       
   710       case QEvent::ChildAdded:
       
   711       case QEvent::ChildPolished:
       
   712       {
       
   713         QChildEvent *childEvt = static_cast<QChildEvent *>(event);
       
   714         //qDebug() << "    watching " << childEvt->child();
       
   715         childEvt->child()->installEventFilter(this);
       
   716         break;
       
   717       }
       
   718       case QEvent::Move:
       
   719       {
       
   720         //QMoveEvent *evt = static_cast<QMoveEvent *>(event);
       
   721         //qDebug() << "    oldpos " << evt->oldPos() << " pos " << evt->pos();
       
   722         if(object->inherits("QComboBoxPrivateContainer")) {
       
   723             xQComboBoxPrivateContainer *cbpc = static_cast<xQComboBoxPrivateContainer *>(object);
       
   724             QComboBox *combo = cbpc->combo;
       
   725             QRect comboRect = combo->geometry();
       
   726             QPoint comboPos = comboRect.topLeft();
       
   727             ChromeSnippet *snippet = getSnippet(comboPos);
       
   728             if(snippet) {
       
   729                 QPoint relativePos = comboPos - snippet->ownerArea().topLeft().toPoint();
       
   730                 static_cast<QWidget *>(object)->move(m_parentChromeView->mapToGlobal(QPoint(0,0))
       
   731                                                      + snippet->rect().topLeft().toPoint()
       
   732                                                      + relativePos
       
   733                                                      + QPoint(0, comboRect.height()));
       
   734             }
       
   735         }
       
   736         break;
       
   737       }
       
   738       default:
       
   739       {
       
   740         if(event->type() == UpdateBufferEvent::customType()) {
       
   741             if(object == this) {
       
   742                 //qDebug() << "ChromeWidget::eventFilter: UpdateBufferEvent " << (void*)object << event;
       
   743                 paintDirtyRegion();
       
   744             }
       
   745         }
       
   746         break;
       
   747       }
       
   748     }
       
   749 
       
   750     return QObject::eventFilter(object, event);
       
   751 }
       
   752 
       
   753 ChromeSnippet *ChromeWidget::getSnippet(QPoint pos) const {
       
   754     foreach(ChromeSnippet *snippet, m_snippetMap) {
       
   755         if(snippet->ownerArea().contains(pos))
       
   756             return snippet;
       
   757     }
       
   758     return 0;
       
   759 }
       
   760 
       
   761 void ChromeWidget::dump() {
       
   762     qDebug() << "ChromeWidget::dump";
       
   763     foreach(ChromeSnippet *snippet, m_snippetMap) {
       
   764         snippet->dump();
       
   765         qDebug() << "------";
       
   766     }
       
   767 }