|
1 /* |
|
2 * Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). |
|
3 * All rights reserved. |
|
4 * This component and the accompanying materials are made available |
|
5 * under the terms of "Eclipse Public License v1.0" |
|
6 * which accompanies this distribution, and is available |
|
7 * at the URL "http://www.eclipse.org/legal/epl-v10.html". |
|
8 * |
|
9 * Initial Contributors: |
|
10 * Nokia Corporation - initial contribution. |
|
11 * |
|
12 * Contributors: |
|
13 * |
|
14 * Description: |
|
15 * |
|
16 */ |
|
17 |
|
18 |
|
19 #include "UrlSearchSnippet.h" |
|
20 #include "Utilities.h" |
|
21 |
|
22 #include "ChromeRenderer.h" |
|
23 #include "ChromeWidget.h" |
|
24 #include "ViewController.h" |
|
25 #include "WebChromeSnippet.h" |
|
26 |
|
27 #include "webpagecontroller.h" |
|
28 |
|
29 namespace GVA { |
|
30 |
|
31 // Methods for class UrlEditorWidget |
|
32 |
|
33 UrlEditorWidget::UrlEditorWidget(QGraphicsItem * parent) |
|
34 : QGraphicsTextItem(parent) |
|
35 { |
|
36 // Disable wrapping, force text to be stored and displayed |
|
37 // as a single line. |
|
38 |
|
39 QTextOption textOption = document()->defaultTextOption(); |
|
40 textOption.setWrapMode(QTextOption::NoWrap); |
|
41 document()->setDefaultTextOption(textOption); |
|
42 |
|
43 // Enable cursor keys. |
|
44 |
|
45 setTextInteractionFlags(Qt::TextEditorInteraction); |
|
46 |
|
47 // This is needed to initialize m_textLine. |
|
48 |
|
49 setText(""); |
|
50 } |
|
51 |
|
52 UrlEditorWidget::~UrlEditorWidget() |
|
53 { |
|
54 } |
|
55 |
|
56 void UrlEditorWidget::setText(const QString & text) |
|
57 { |
|
58 setPlainText(text); |
|
59 m_textLine = document()->begin().layout()->lineForTextPosition(0); |
|
60 } |
|
61 |
|
62 qreal UrlEditorWidget::cursorX() |
|
63 { |
|
64 return m_textLine.cursorToX(textCursor().position()); |
|
65 } |
|
66 |
|
67 void UrlEditorWidget::paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget) |
|
68 { |
|
69 // Paint without ugly selection ants (the dashed line that surrounds |
|
70 // the selected text). |
|
71 |
|
72 QStyleOptionGraphicsItem newOption = *option; |
|
73 newOption.state &= (!QStyle::State_Selected | !QStyle::State_HasFocus); |
|
74 |
|
75 painter->save(); |
|
76 |
|
77 QGraphicsTextItem::paint(painter, &newOption, widget); |
|
78 |
|
79 painter->restore(); |
|
80 } |
|
81 |
|
82 void UrlEditorWidget::keyPressEvent(QKeyEvent * event) |
|
83 { |
|
84 // Signal horizontal cursor movement so UrlSearchSnippet can |
|
85 // implement horizontal scrolling. |
|
86 |
|
87 qreal oldX = cursorX(); |
|
88 |
|
89 QGraphicsTextItem::keyPressEvent(event); |
|
90 |
|
91 qreal newX = cursorX(); |
|
92 |
|
93 if (newX != oldX) { |
|
94 emit cursorXChanged(newX); |
|
95 } |
|
96 } |
|
97 |
|
98 // Methods for class UrlSearchSnippet |
|
99 |
|
100 UrlSearchSnippet::UrlSearchSnippet(ChromeSnippet * snippet, ChromeWidget * chrome, QGraphicsItem * parent) |
|
101 : NativeChromeItem(snippet, parent) |
|
102 , m_chrome(chrome) |
|
103 , m_percent(0) |
|
104 , m_pendingClearCalls(0) |
|
105 , m_viewPortWidth(0.0) |
|
106 , m_viewPortHeight(0.0) |
|
107 { |
|
108 setFlags(QGraphicsItem::ItemIsMovable); |
|
109 |
|
110 // Extract style information from element CSS. |
|
111 |
|
112 // For border-related properties, we constrain all values (top, left, etc.) |
|
113 // to be the same. These can be set using the css shorthand (e.g. padding:10px), |
|
114 // but the computed css style will be for the four primitive values (padding-top, |
|
115 // padding-left) etc, which will all be equal. Hence we just use one of the |
|
116 // computed primitive values (top) to represent the common value. |
|
117 |
|
118 QWebElement we = m_snippet->element(); |
|
119 |
|
120 NativeChromeItem::CSSToQColor( |
|
121 we.styleProperty("color", QWebElement::ComputedStyle), |
|
122 m_textColor); |
|
123 |
|
124 NativeChromeItem::CSSToQColor( |
|
125 we.styleProperty("background-color", QWebElement::ComputedStyle), |
|
126 m_backgroundColor); |
|
127 |
|
128 NativeChromeItem::CSSToQColor( |
|
129 we.styleProperty("border-top-color", QWebElement::ComputedStyle), |
|
130 m_borderColor); |
|
131 |
|
132 QString cssPadding = we.styleProperty("padding-top", QWebElement::ComputedStyle); |
|
133 m_padding = cssPadding.remove("px").toInt(); |
|
134 |
|
135 QString cssBorder = we.styleProperty("border-top-width", QWebElement::ComputedStyle); |
|
136 m_border = cssBorder.remove("px").toInt(); |
|
137 |
|
138 // The viewport clips the editor when text overflows |
|
139 |
|
140 m_viewPort = new QGraphicsWidget(this); |
|
141 m_viewPort->setFlags(QGraphicsItem::ItemClipsChildrenToShape); |
|
142 |
|
143 // The actual text editor item |
|
144 |
|
145 m_editor = new UrlEditorWidget(m_viewPort); |
|
146 m_editor->setDefaultTextColor(m_textColor); |
|
147 m_editor->installEventFilter(this); |
|
148 |
|
149 // Monitor editor cursor position changes for horizontal scrolling. |
|
150 |
|
151 safe_connect(m_editor, SIGNAL(cursorXChanged(qreal)), |
|
152 this, SLOT(makeVisible(qreal))); |
|
153 |
|
154 // Monitor resize events. |
|
155 |
|
156 safe_connect(m_chrome->renderer(), SIGNAL(chromeResized()), |
|
157 this, SLOT(resize())); |
|
158 |
|
159 // Update state as soon as chrome completes loading. |
|
160 |
|
161 safe_connect(m_chrome, SIGNAL(chromeComplete()), |
|
162 this, SLOT(setStarted())); |
|
163 |
|
164 // Monitor page loading. |
|
165 |
|
166 WebPageController * pageController = WebPageController::getSingleton(); |
|
167 |
|
168 safe_connect(pageController, SIGNAL(pageUrlChanged(const QString)), |
|
169 this, SLOT(setUrlText(const QString &))); |
|
170 |
|
171 safe_connect(pageController, SIGNAL(pageLoadStarted()), |
|
172 this, SLOT(setStarted())); |
|
173 |
|
174 safe_connect(pageController, SIGNAL(pageLoadProgress(const int)), |
|
175 this, SLOT(setProgress(int))); |
|
176 |
|
177 safe_connect(pageController, SIGNAL(pageLoadFinished(bool)), |
|
178 this, SLOT(setFinished(bool))); |
|
179 |
|
180 // Monitor view changes. |
|
181 |
|
182 ViewController * viewController = chrome->viewController(); |
|
183 |
|
184 safe_connect(viewController, SIGNAL(currentViewChanged()), |
|
185 this, SLOT(viewChanged())); |
|
186 } |
|
187 |
|
188 UrlSearchSnippet::~UrlSearchSnippet() |
|
189 { |
|
190 } |
|
191 |
|
192 bool UrlSearchSnippet::eventFilter(QObject * object, QEvent * event) |
|
193 { |
|
194 // Filter editor key events. |
|
195 |
|
196 if (object != m_editor) { |
|
197 return false; |
|
198 } |
|
199 |
|
200 if (event->type() != QEvent::KeyPress) { |
|
201 return false; |
|
202 } |
|
203 |
|
204 QKeyEvent * keyEvent = static_cast<QKeyEvent*>(event); |
|
205 |
|
206 switch (keyEvent->key()) { |
|
207 case Qt::Key_Select: |
|
208 case Qt::Key_Return: |
|
209 case Qt::Key_Enter: |
|
210 // Signal that a carriage return-like key-press happened. |
|
211 emit activated(); |
|
212 return true; |
|
213 |
|
214 case Qt::Key_Down: |
|
215 case Qt::Key_Up: |
|
216 // Swallow arrow up/down keys, editor has just one line. |
|
217 return true; |
|
218 |
|
219 default: |
|
220 return false; |
|
221 } |
|
222 } |
|
223 |
|
224 void UrlSearchSnippet::paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget) |
|
225 { |
|
226 // Make sure any required horizontal scrolling happens |
|
227 // before rendering UrlEditorWidget. |
|
228 |
|
229 makeVisible(m_editor->cursorX()); |
|
230 |
|
231 NativeChromeItem::paint(painter, option,widget); |
|
232 |
|
233 painter->save(); |
|
234 |
|
235 painter->setRenderHint(QPainter::Antialiasing); |
|
236 painter->setBrush(m_backgroundColor); |
|
237 |
|
238 // First, do progress bar. |
|
239 |
|
240 QRectF g = boundingRect(); |
|
241 g.setWidth(g.width() * m_percent / 100.0); |
|
242 painter->fillRect(g, QColor::fromRgb(0, 200, 200, 50)); |
|
243 |
|
244 // Next, background matte. |
|
245 |
|
246 if (m_border > 0) { |
|
247 QPen pen; |
|
248 pen.setWidth(m_border); |
|
249 pen.setBrush(m_borderColor); |
|
250 painter->setPen(pen); |
|
251 } |
|
252 |
|
253 QPainterPath background; |
|
254 background.addRect(boundingRect()); |
|
255 background.addRoundedRect( |
|
256 m_padding, |
|
257 m_padding, |
|
258 m_viewPortWidth, |
|
259 m_viewPortHeight, |
|
260 4, |
|
261 4); |
|
262 painter->drawPath(background); |
|
263 |
|
264 painter->restore(); |
|
265 } |
|
266 |
|
267 void UrlSearchSnippet::resizeEvent(QGraphicsSceneResizeEvent * event) |
|
268 { |
|
269 QSizeF size = event->newSize(); |
|
270 |
|
271 m_viewPort->resize(size); |
|
272 |
|
273 m_viewPortWidth = size.width() - m_padding * 2; |
|
274 m_viewPortHeight = size.height() - m_padding * 2; |
|
275 |
|
276 m_viewPort->setGeometry( |
|
277 m_padding, |
|
278 (size.height() - m_editor->boundingRect().height()) / 2, |
|
279 m_viewPortWidth, |
|
280 m_viewPortHeight); |
|
281 |
|
282 m_editor->setTextWidth(m_viewPortWidth); |
|
283 } |
|
284 |
|
285 void UrlSearchSnippet::resize() |
|
286 { |
|
287 QWebElement we = m_snippet->element(); |
|
288 |
|
289 QRectF g = we.geometry(); |
|
290 |
|
291 qreal newWidth = g.width(); |
|
292 |
|
293 qreal newHeight = g.height(); |
|
294 |
|
295 QGraphicsWidget::resize(newWidth, newHeight); |
|
296 } |
|
297 |
|
298 void UrlSearchSnippet::setUrlText(const QString & text) |
|
299 { |
|
300 m_editor->setText(text); |
|
301 m_editor->setPos(0, m_editor->pos().y()); |
|
302 |
|
303 makeVisible(m_editor->cursorX()); |
|
304 } |
|
305 |
|
306 void UrlSearchSnippet::setStarted() |
|
307 { |
|
308 // Strictly speaking we should set progress to 0. |
|
309 // But set it higher to give immediate visual feedback |
|
310 // that something is happening. |
|
311 |
|
312 int progress = 0; |
|
313 |
|
314 WebPageController * pageController = WebPageController::getSingleton(); |
|
315 |
|
316 if (pageController->isPageLoading()) { |
|
317 progress = 5; |
|
318 } |
|
319 |
|
320 setProgress(progress); |
|
321 } |
|
322 |
|
323 void UrlSearchSnippet::setProgress(int percent) |
|
324 { |
|
325 m_percent = percent; |
|
326 update(); |
|
327 } |
|
328 |
|
329 // Wait a half-second before actually clearing the progress bar. |
|
330 // |
|
331 // We have to be careful of the following two use cases: |
|
332 // |
|
333 // 1. Another page starts loading between the call to setFinished() |
|
334 // and the scheduled call to clearProgress(). |
|
335 // |
|
336 // We don't want to clear the progress bar if another page is |
|
337 // loading. WebPageController::isPageLoading() can tell us |
|
338 // if that is the case. |
|
339 // |
|
340 // 2. Another page finishes loading between the call to setFinished() |
|
341 // and the scheduled call to clearProgress(). The sequence here is: |
|
342 // |
|
343 // setFinished(ok) // for URL #1 |
|
344 // setFinished(ok) // for URL #2 |
|
345 // clearProgress() // for URL #1 |
|
346 // clearProgress() // for URL #2 |
|
347 // |
|
348 // We don't want to clear the progress bar in the first call to |
|
349 // clearProgress() because we want the progress bar to retain its |
|
350 // appearance for the full timeout period. We manage this by |
|
351 // tracking the number of pending calls to clearProgress() and |
|
352 // only clearing the progress bar when that number becomes 0. |
|
353 |
|
354 void UrlSearchSnippet::setFinished(bool ok) |
|
355 { |
|
356 if (ok) { |
|
357 setProgress(99); |
|
358 } |
|
359 |
|
360 ++m_pendingClearCalls; |
|
361 |
|
362 QTimer::singleShot(500, this, SLOT(clearProgress())); |
|
363 } |
|
364 |
|
365 void UrlSearchSnippet::clearProgress() |
|
366 { |
|
367 --m_pendingClearCalls; |
|
368 |
|
369 if (m_pendingClearCalls > 0) { |
|
370 return; |
|
371 } |
|
372 |
|
373 WebPageController * pageController = WebPageController::getSingleton(); |
|
374 |
|
375 if (pageController->isPageLoading()) { |
|
376 return; |
|
377 } |
|
378 |
|
379 setProgress(0); |
|
380 } |
|
381 |
|
382 void UrlSearchSnippet::viewChanged() |
|
383 { |
|
384 WebPageController * pageController = WebPageController::getSingleton(); |
|
385 |
|
386 setUrlText(pageController->currentDocUrl()); |
|
387 |
|
388 int progress = pageController->loadProgressValue(); |
|
389 if (progress >= 100) { |
|
390 progress = 0; |
|
391 } |
|
392 setProgress(progress); |
|
393 } |
|
394 |
|
395 // We divide the viewport into 3 distinct regions: |
|
396 // |
|
397 // |
|
398 // [ left | middle | right ] |
|
399 // |
|
400 // [ editor, shifted left by editorShift pixels ] |
|
401 // |
|
402 // When a cursor is in the middle section of the viewport we |
|
403 // leave the editor shift unchanged, to preserve stability. |
|
404 // |
|
405 // When a cursor is in the right section or beyond we shift |
|
406 // the editor left until the cursor appears at the border |
|
407 // between the middle and right sections. |
|
408 // |
|
409 // When a cursor is in the left section or beyond we shift |
|
410 // the editor right until the cursor appears at the border |
|
411 // between the left and middle sections. |
|
412 // |
|
413 // We never shift the editor right of the viewport. |
|
414 |
|
415 void UrlSearchSnippet::makeVisible(qreal cursorX) |
|
416 { |
|
417 qreal leftScrollBorder = 0; |
|
418 |
|
419 qreal rightScrollBorder = m_viewPortWidth - 10; |
|
420 |
|
421 qreal editorShift = -1 * m_editor->pos().x(); |
|
422 |
|
423 qreal localX = cursorX - editorShift; |
|
424 |
|
425 if (localX < leftScrollBorder) { |
|
426 // Before left section, scroll right. |
|
427 // In left section, scroll right. |
|
428 qreal shift = qMin(leftScrollBorder - localX, editorShift); |
|
429 m_editor->moveBy(shift, 0); |
|
430 return; |
|
431 } |
|
432 |
|
433 if (localX >= rightScrollBorder) { |
|
434 // In right section, scroll left. |
|
435 // After right section, scroll left. |
|
436 qreal shift = localX - rightScrollBorder; |
|
437 m_editor->moveBy(-shift, 0); |
|
438 return; |
|
439 } |
|
440 |
|
441 // In middle section, no scroll needed. |
|
442 return; |
|
443 } |
|
444 |
|
445 } // namespace GVA |