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 } |
|