ginebra2/TextEditItem.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Tue, 04 May 2010 12:39:35 +0300
changeset 0 1450b09d0cfd
child 3 0954f5dd2cd0
permissions -rw-r--r--
Revision: 201015 Kit: 201018

/*
* 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);
  }

}