|
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 <QGraphicsTextItem> |
|
20 #include "TextEditItem.h" |
|
21 #include "ChromeSnippet.h" |
|
22 #include "GreenChromeSnippet.h" |
|
23 |
|
24 namespace GVA { |
|
25 EditorWidget::EditorWidget(QGraphicsItem * parent) |
|
26 : QGraphicsTextItem(parent) |
|
27 { |
|
28 setText(""); |
|
29 } |
|
30 void EditorWidget::paint(QPainter* painter, const QStyleOptionGraphicsItem * option, QWidget* widget) |
|
31 { |
|
32 //Hack to get rid of the ugly selection ants. This should be tunable in QGraphicsTextItem! |
|
33 QStyleOptionGraphicsItem newOption = *option; |
|
34 newOption.state &= (!QStyle::State_Selected | !QStyle::State_HasFocus); |
|
35 painter->save(); |
|
36 QGraphicsTextItem::paint(painter, &newOption, widget); |
|
37 painter->restore(); |
|
38 } |
|
39 |
|
40 // The editor document signals cursor movements when the document modified (because characters |
|
41 // are being added or removed, but not when the cursor is simply being moved. Also, the |
|
42 // document's idea of cursor position is based on the character count, not the actual pixel |
|
43 // position. To implement scrolling, we need our own cursor change event that supplies the |
|
44 // pixel change in all cases. |
|
45 |
|
46 void EditorWidget::keyPressEvent(QKeyEvent *event) |
|
47 { |
|
48 qreal oldX = cursorX(); |
|
49 QGraphicsTextItem::keyPressEvent(event); |
|
50 emit cursorXChanged(cursorX(), oldX); |
|
51 } |
|
52 |
|
53 void EditorWidget::setText(const QString& text, bool html) |
|
54 { |
|
55 if(html) |
|
56 setHtml(text); |
|
57 else |
|
58 setPlainText(text); |
|
59 //All this just to get the first (and only) text line of the document! |
|
60 m_textLine = document()->begin().layout()->lineForTextPosition(0); |
|
61 } |
|
62 |
|
63 // Use QTextLine to compute the text metrics for the current cursor position. |
|
64 |
|
65 qreal EditorWidget::cursorX() { |
|
66 return m_textLine.cursorToX(textCursor().position()); |
|
67 } |
|
68 |
|
69 TextEditItem::TextEditItem(ChromeSnippet * snippet, QGraphicsItem* parent) |
|
70 : NativeChromeItem(snippet, parent), |
|
71 m_textWidth(0), |
|
72 m_scrollPos(0) |
|
73 { |
|
74 setFlags(QGraphicsItem::ItemIsMovable); |
|
75 //The viewport clips the editor when text overflows |
|
76 m_viewPort = new QGraphicsWidget(this); |
|
77 m_viewPort->setFlags(QGraphicsItem::ItemClipsChildrenToShape); |
|
78 //The actual text editor item |
|
79 m_editor = new EditorWidget(m_viewPort); |
|
80 m_cursor = m_editor->textCursor(); |
|
81 connect(m_editor, SIGNAL(cursorXChanged(qreal, qreal)), this, SLOT(onCursorXChanged(qreal, qreal))); |
|
82 |
|
83 //Force the editor to be a single text line |
|
84 m_textOption = m_editor->document()->defaultTextOption(); |
|
85 m_textOption.setWrapMode(QTextOption::NoWrap); |
|
86 m_editor->document()->setDefaultTextOption(m_textOption); |
|
87 |
|
88 //Not exactly well-documented, but this flag is needed to make cursor keys work |
|
89 m_editor->setTextInteractionFlags(Qt::TextEditorInteraction); |
|
90 |
|
91 //Non-default key handling for scrolling, etc. |
|
92 m_editor->installEventFilter(this); |
|
93 |
|
94 //Set text and background colors from element css |
|
95 QString cssVal = m_snippet->element().styleProperty("color", QWebElement::ComputedStyle); |
|
96 CSSToQColor(cssVal, m_textColor); |
|
97 m_editor->setDefaultTextColor(m_textColor); |
|
98 cssVal = m_snippet->element().styleProperty("background-color", QWebElement::ComputedStyle); |
|
99 CSSToQColor(cssVal, m_backgroundColor); |
|
100 |
|
101 //For border-related properties, we constrain all values (top, left, etc.) to be the same. |
|
102 //These can be set using the css shorthand (e.g. padding:10px), but the computed css style will be for |
|
103 //the four primitive values (padding-top, padding-left) etc, which will all be equal. |
|
104 //Hence we just use one of the computed primitive values (top) to represent the common value. |
|
105 |
|
106 cssVal = m_snippet->element().styleProperty("border-top-color", QWebElement::ComputedStyle); |
|
107 CSSToQColor(cssVal, m_borderColor); |
|
108 cssVal = m_snippet->element().styleProperty("padding-top", QWebElement::ComputedStyle); |
|
109 m_padding = cssVal.remove("px").toInt(); |
|
110 cssVal = m_snippet->element().styleProperty("border-top-width", QWebElement::ComputedStyle); |
|
111 m_border = cssVal.remove("px").toInt(); |
|
112 |
|
113 //Cool effect, but shadow, if any, should be set by js |
|
114 //m_shadow = new QGraphicsDropShadowEffect(); |
|
115 //m_shadow->setOffset(3.0,3.0); |
|
116 //m_shadow->setBlurRadius(2.0); |
|
117 //setGraphicsEffect(m_shadow); |
|
118 } |
|
119 |
|
120 TextEditItem::~TextEditItem() |
|
121 { |
|
122 delete m_editor; |
|
123 } |
|
124 |
|
125 void TextEditItem::resizeEvent(QGraphicsSceneResizeEvent * ev) |
|
126 { |
|
127 NativeChromeItem::resizeEvent(ev); |
|
128 m_viewPortWidth = boundingRect().width()-m_padding*2; |
|
129 m_viewPort->setGeometry(m_padding,(boundingRect().height()-m_editor->boundingRect().height())/2,m_viewPortWidth, m_editor->boundingRect().height() ); |
|
130 m_editor->setTextWidth(m_viewPortWidth); |
|
131 //Make a rectangular background with a cut-out for the text. The width of the surrounding |
|
132 //background is set by padding |
|
133 m_background.addRect(boundingRect()); |
|
134 m_background.addRoundedRect(m_padding, m_padding, m_viewPortWidth, boundingRect().height()-m_padding*2,4,4); |
|
135 } |
|
136 |
|
137 //Filter key events to emit activate signal absorb up, down keys |
|
138 |
|
139 bool TextEditItem::eventFilter(QObject * obj, QEvent *ev) |
|
140 { |
|
141 if(obj == m_editor){ |
|
142 if(ev->type() == QEvent::KeyPress){ |
|
143 QKeyEvent *keyEvent = static_cast<QKeyEvent*>(ev); |
|
144 if(keyEvent->key() == Qt::Key_Select || keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) { |
|
145 //Signal that a carriage return-like key-press happened |
|
146 emit activated(); |
|
147 return true; |
|
148 } |
|
149 if(keyEvent->key() == Qt::Key_Down || keyEvent->key() == Qt::Key_Up) |
|
150 return true; |
|
151 //Otherwise, pass keypress to the text editor |
|
152 return false; |
|
153 } |
|
154 } |
|
155 return NativeChromeItem::eventFilter(obj, ev); |
|
156 } |
|
157 |
|
158 void TextEditItem::internalScroll(qreal deltaX) |
|
159 { |
|
160 if(deltaX > -m_scrollPos) |
|
161 m_editor->moveBy(-m_scrollPos,0); |
|
162 else |
|
163 m_editor->moveBy(deltaX,0); |
|
164 m_scrollPos = m_editor->pos().x(); |
|
165 } |
|
166 |
|
167 //Handle text scrolling |
|
168 //NB: Still needs some tweaking, for example to keep the last character visibile when |
|
169 //inserting! Rewrite as state machine? |
|
170 |
|
171 void TextEditItem::onCursorXChanged(qreal newX, qreal oldX) |
|
172 { |
|
173 qreal oldTextWidth = m_textWidth; |
|
174 m_textWidth = m_editor->document()->size().width(); |
|
175 if(oldTextWidth == 0) |
|
176 return; |
|
177 qreal textDelta = m_textWidth - oldTextWidth; |
|
178 qreal deltaX = oldX - newX; |
|
179 //Just moving the cursor, slide window as needed |
|
180 if(textDelta == 0){ |
|
181 //NB: Currently slides by one character, in some browsers slides by multiple characters |
|
182 if((newX <= -m_scrollPos)||(newX >= (m_viewPortWidth - m_scrollPos))){ |
|
183 internalScroll(deltaX); |
|
184 } |
|
185 } |
|
186 //Inserting characters |
|
187 else if (textDelta > 0){ |
|
188 if(newX >= (m_viewPortWidth - m_scrollPos)){ |
|
189 internalScroll(deltaX); |
|
190 } |
|
191 } |
|
192 //Deleting characters. |
|
193 else { |
|
194 if(m_scrollPos < 0){ |
|
195 //Delete may be a selected block, in which case the cursor movement may be |
|
196 //different from the text delta. |
|
197 internalScroll(-textDelta); |
|
198 } |
|
199 } |
|
200 } |
|
201 |
|
202 // Paint background and any border |
|
203 |
|
204 void TextEditItem::paint(QPainter* painter, const QStyleOptionGraphicsItem * option, QWidget* widget){ |
|
205 NativeChromeItem::paint(painter, option,widget); |
|
206 QPainterPath path; |
|
207 painter->save(); |
|
208 painter->setRenderHint(QPainter::Antialiasing); |
|
209 painter->setBrush(m_backgroundColor); |
|
210 if(m_border > 0){ |
|
211 QPen pen; |
|
212 pen.setWidth(m_border); |
|
213 pen.setBrush(m_borderColor); |
|
214 painter->setPen(pen); |
|
215 } |
|
216 painter->drawPath(m_background); |
|
217 painter->restore(); |
|
218 } |
|
219 |
|
220 //NB: Move these slots to the containing snippet so they can be exported to JS |
|
221 |
|
222 QString TextEditItem::text(){ |
|
223 return m_editor->toPlainText(); |
|
224 } |
|
225 |
|
226 void TextEditItem::setText(const QString & text){ |
|
227 m_editor->setText(text); |
|
228 } |
|
229 |
|
230 } |
|
231 |