/*
* Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:
*
*/
#include <QGraphicsTextItem>
#include "TextEditItem.h"
#include "ChromeSnippet.h"
#include "GreenChromeSnippet.h"
namespace GVA {
EditorWidget::EditorWidget(QGraphicsItem * parent)
: QGraphicsTextItem(parent)
{
setText("");
}
void EditorWidget::paint(QPainter* painter, const QStyleOptionGraphicsItem * option, QWidget* widget)
{
//Hack to get rid of the ugly selection ants. This should be tunable in QGraphicsTextItem!
QStyleOptionGraphicsItem newOption = *option;
newOption.state &= (!QStyle::State_Selected | !QStyle::State_HasFocus);
painter->save();
QGraphicsTextItem::paint(painter, &newOption, widget);
painter->restore();
}
// The editor document signals cursor movements when the document modified (because characters
// are being added or removed, but not when the cursor is simply being moved. Also, the
// document's idea of cursor position is based on the character count, not the actual pixel
// position. To implement scrolling, we need our own cursor change event that supplies the
// pixel change in all cases.
void EditorWidget::keyPressEvent(QKeyEvent *event)
{
qreal oldX = cursorX();
QGraphicsTextItem::keyPressEvent(event);
emit cursorXChanged(cursorX(), oldX);
}
void EditorWidget::setText(const QString& text, bool html)
{
if(html)
setHtml(text);
else
setPlainText(text);
//All this just to get the first (and only) text line of the document!
m_textLine = document()->begin().layout()->lineForTextPosition(0);
}
// Use QTextLine to compute the text metrics for the current cursor position.
qreal EditorWidget::cursorX() {
return m_textLine.cursorToX(textCursor().position());
}
TextEditItem::TextEditItem(ChromeSnippet * snippet, QGraphicsItem* parent)
: NativeChromeItem(snippet, parent),
m_textWidth(0),
m_scrollPos(0)
{
setFlags(QGraphicsItem::ItemIsMovable);
//The viewport clips the editor when text overflows
m_viewPort = new QGraphicsWidget(this);
m_viewPort->setFlags(QGraphicsItem::ItemClipsChildrenToShape);
//The actual text editor item
m_editor = new EditorWidget(m_viewPort);
m_cursor = m_editor->textCursor();
connect(m_editor, SIGNAL(cursorXChanged(qreal, qreal)), this, SLOT(onCursorXChanged(qreal, qreal)));
//Force the editor to be a single text line
m_textOption = m_editor->document()->defaultTextOption();
m_textOption.setWrapMode(QTextOption::NoWrap);
m_editor->document()->setDefaultTextOption(m_textOption);
//Not exactly well-documented, but this flag is needed to make cursor keys work
m_editor->setTextInteractionFlags(Qt::TextEditorInteraction);
//Non-default key handling for scrolling, etc.
m_editor->installEventFilter(this);
//Set text and background colors from element css
QString cssVal = m_snippet->element().styleProperty("color", QWebElement::ComputedStyle);
CSSToQColor(cssVal, m_textColor);
m_editor->setDefaultTextColor(m_textColor);
cssVal = m_snippet->element().styleProperty("background-color", QWebElement::ComputedStyle);
CSSToQColor(cssVal, m_backgroundColor);
//For border-related properties, we constrain all values (top, left, etc.) to be the same.
//These can be set using the css shorthand (e.g. padding:10px), but the computed css style will be for
//the four primitive values (padding-top, padding-left) etc, which will all be equal.
//Hence we just use one of the computed primitive values (top) to represent the common value.
cssVal = m_snippet->element().styleProperty("border-top-color", QWebElement::ComputedStyle);
CSSToQColor(cssVal, m_borderColor);
cssVal = m_snippet->element().styleProperty("padding-top", QWebElement::ComputedStyle);
m_padding = cssVal.remove("px").toInt();
cssVal = m_snippet->element().styleProperty("border-top-width", QWebElement::ComputedStyle);
m_border = cssVal.remove("px").toInt();
//Cool effect, but shadow, if any, should be set by js
//m_shadow = new QGraphicsDropShadowEffect();
//m_shadow->setOffset(3.0,3.0);
//m_shadow->setBlurRadius(2.0);
//setGraphicsEffect(m_shadow);
}
TextEditItem::~TextEditItem()
{
delete m_editor;
}
void TextEditItem::resizeEvent(QGraphicsSceneResizeEvent * ev)
{
NativeChromeItem::resizeEvent(ev);
m_viewPortWidth = boundingRect().width()-m_padding*2;
m_viewPort->setGeometry(m_padding,(boundingRect().height()-m_editor->boundingRect().height())/2,m_viewPortWidth, m_editor->boundingRect().height() );
m_editor->setTextWidth(m_viewPortWidth);
//Make a rectangular background with a cut-out for the text. The width of the surrounding
//background is set by padding
m_background.addRect(boundingRect());
m_background.addRoundedRect(m_padding, m_padding, m_viewPortWidth, boundingRect().height()-m_padding*2,4,4);
}
//Filter key events to emit activate signal absorb up, down keys
bool TextEditItem::eventFilter(QObject * obj, QEvent *ev)
{
if(obj == m_editor){
if(ev->type() == QEvent::KeyPress){
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(ev);
if(keyEvent->key() == Qt::Key_Select || keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) {
//Signal that a carriage return-like key-press happened
emit activated();
return true;
}
if(keyEvent->key() == Qt::Key_Down || keyEvent->key() == Qt::Key_Up)
return true;
//Otherwise, pass keypress to the text editor
return false;
}
}
return NativeChromeItem::eventFilter(obj, ev);
}
void TextEditItem::internalScroll(qreal deltaX)
{
if(deltaX > -m_scrollPos)
m_editor->moveBy(-m_scrollPos,0);
else
m_editor->moveBy(deltaX,0);
m_scrollPos = m_editor->pos().x();
}
//Handle text scrolling
//NB: Still needs some tweaking, for example to keep the last character visibile when
//inserting! Rewrite as state machine?
void TextEditItem::onCursorXChanged(qreal newX, qreal oldX)
{
qreal oldTextWidth = m_textWidth;
m_textWidth = m_editor->document()->size().width();
if(oldTextWidth == 0)
return;
qreal textDelta = m_textWidth - oldTextWidth;
qreal deltaX = oldX - newX;
//Just moving the cursor, slide window as needed
if(textDelta == 0){
//NB: Currently slides by one character, in some browsers slides by multiple characters
if((newX <= -m_scrollPos)||(newX >= (m_viewPortWidth - m_scrollPos))){
internalScroll(deltaX);
}
}
//Inserting characters
else if (textDelta > 0){
if(newX >= (m_viewPortWidth - m_scrollPos)){
internalScroll(deltaX);
}
}
//Deleting characters.
else {
if(m_scrollPos < 0){
//Delete may be a selected block, in which case the cursor movement may be
//different from the text delta.
internalScroll(-textDelta);
}
}
}
// Paint background and any border
void TextEditItem::paint(QPainter* painter, const QStyleOptionGraphicsItem * option, QWidget* widget){
NativeChromeItem::paint(painter, option,widget);
QPainterPath path;
painter->save();
painter->setRenderHint(QPainter::Antialiasing);
painter->setBrush(m_backgroundColor);
if(m_border > 0){
QPen pen;
pen.setWidth(m_border);
pen.setBrush(m_borderColor);
painter->setPen(pen);
}
painter->drawPath(m_background);
painter->restore();
}
//NB: Move these slots to the containing snippet so they can be exported to JS
QString TextEditItem::text(){
return m_editor->toPlainText();
}
void TextEditItem::setText(const QString & text){
m_editor->setText(text);
}
}