ginebra2/EditorWidget.cpp
changeset 5 0f2326c2a325
child 6 1c3b8676e58c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ginebra2/EditorWidget.cpp	Wed Jun 23 17:59:43 2010 +0300
@@ -0,0 +1,575 @@
+/*
+ * Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+ * All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, version 2.1 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not,
+ * see "http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html/".
+ *
+ * Description:
+ *
+ */
+
+#include "EditorWidget.h"
+#include "Utilities.h"
+#include "ChromeEffect.h"
+
+// FIXME ;;; Must address the following issues:
+//
+// * On Symbian, when focus is outside editor and user clicks inside editor, the editor
+//   receives FocusIn event but does not make the blinking cursor visible until
+//   the user starts to type or presses arrow keys.
+//
+// * Edit selection is not visible.
+//
+
+
+namespace GVA {
+
+  // Methods for class EditorItem
+
+  //GTextLineItem extends QGraphicsTextItem as a single-line editor.
+  //Signals horizontal cursor movement, which can be used to implement horizontal scrolling.
+
+  GTextLineItem::GTextLineItem(QGraphicsItem * parent)
+  : QGraphicsTextItem(parent)
+  , m_predictionDisabled(false)
+  , m_autoUppercase(false)
+  {
+    // Disable wrapping, force text to be stored and displayed
+    // as a single line.
+    QTextOption textOption = document()->defaultTextOption();
+    textOption.setWrapMode(QTextOption::NoWrap);
+    document()->setDefaultTextOption(textOption);
+    // Enable cursor keys.
+    setTextInteractionFlags(Qt::TextEditorInteraction);
+    // This is needed to initialize m_textLine.
+    setText("");
+    setAcceptDrops(false);
+    m_defaultStartDragDistance = QApplication::startDragDistance();
+  }
+
+  GTextLineItem::~GTextLineItem()
+  {
+    QApplication::setStartDragDistance(m_defaultStartDragDistance);
+  }
+
+  void GTextLineItem::setText(const QString & text)
+  {
+    setPlainText(text);
+    m_textLine = document()->begin().layout()->lineForTextPosition(0);
+  }
+
+  // Get the pixel offset of the cursor. Needed to implement scrolling.
+
+  qreal GTextLineItem::cursorX()
+  {
+    return m_textLine.cursorToX(textCursor().position());
+  }
+
+  qreal GTextLineItem::anchorX()
+  {
+    return m_textLine.cursorToX(textCursor().anchor());
+  }
+
+  QRectF GTextLineItem::selectionRectF()
+  {
+    qreal x1 = cursorX();
+    qreal x2 = anchorX();
+    if (x1 == x2)
+      return QRectF();
+    return QRectF(x1, 0, x2-x1 + 16, boundingRect().height());
+  }
+
+  void GTextLineItem::selectAll()
+  {
+    QTextCursor tc = textCursor();
+    tc.select(QTextCursor::Document);
+    setTextCursor(tc);
+    emit cursorXChanged(cursorX());
+  }
+
+  void GTextLineItem::unselect()
+  {
+    QTextCursor tc = textCursor();
+    tc.setPosition(tc.position());
+    setTextCursor(tc);
+  }
+
+  void GTextLineItem::setCursorPosition(int pos)
+  {
+    QTextCursor tc = textCursor();
+    tc.setPosition(pos);
+    setTextCursor(tc);
+  }
+
+  qreal GTextLineItem::textWidth()
+  {
+    return m_textLine.naturalTextWidth();
+  }
+
+  void GTextLineItem::paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget)
+  {
+    // Paint without ugly selection ants (the dashed line that surrounds
+    // the selected text). TODO: It is clearly a bug in QGraphicsTextItem
+    // that you cannot explicitly set the style of the selection indicator.  File this.
+    QStyleOptionGraphicsItem newOption = *option;
+    newOption.state &= (!QStyle::State_Selected | !QStyle::State_HasFocus);
+    painter->save();
+    painter->setRenderHint(QPainter::Antialiasing);
+    QGraphicsTextItem::paint(painter, &newOption, widget);
+    painter->restore();
+    if(!isEnabled()) {
+        ChromeEffect::paintDisabledRect(painter, option->exposedRect);
+    }
+  }
+
+  void GTextLineItem::mouseMoveEvent(QGraphicsSceneMouseEvent * event)
+  {
+    QGraphicsTextItem::mouseMoveEvent(event);
+    emit cursorXChanged(cursorX());
+  }
+
+  void GTextLineItem::mousePressEvent(QGraphicsSceneMouseEvent * event)
+  {
+    QGraphicsTextItem::mousePressEvent(event);
+  }
+
+  void GTextLineItem::mouseReleaseEvent(QGraphicsSceneMouseEvent * event)
+  {
+    QGraphicsTextItem::mouseReleaseEvent(event);
+    QPointF pos = event->pos();
+    emit tapped(pos);
+    // open vkb by single tap
+    QWidget * widget = event->widget();
+    QEvent vkbEvent(QEvent::RequestSoftwareInputPanel);
+    QApplication::sendEvent(widget, &vkbEvent);
+  }
+
+  void GTextLineItem::keyPressEvent(QKeyEvent * event)
+  {
+    // Signal horizontal cursor movement so that an editor widget can
+    // implement horizontal scrolling.
+    qreal oldX = cursorX();
+    QGraphicsTextItem::keyPressEvent(event);
+    qreal newX = cursorX();
+    if (newX != oldX) {
+      emit cursorXChanged(newX);
+    }
+  }
+
+  void GTextLineItem::keyReleaseEvent(QKeyEvent * event)
+  {
+    QGraphicsTextItem::keyReleaseEvent(event);
+    emit textMayChanged();
+  }
+
+  void GTextLineItem::focusInEvent(QFocusEvent * event)
+  {
+    Q_UNUSED(event);
+    // disable the drag & drop to fix the auto-delete-all issue
+    QApplication::setStartDragDistance(1000);
+    QGraphicsTextItem::focusInEvent(event);
+    // disable the text predictive
+    if (!m_predictionDisabled) {
+      QWidget* fw = QApplication::focusWidget();
+      Qt::InputMethodHints hints = fw->inputMethodHints();
+      hints |= Qt::ImhNoPredictiveText;
+      if (!m_autoUppercase)
+          hints |= Qt::ImhNoAutoUppercase;
+      fw->setInputMethodHints(hints);
+      m_predictionDisabled = true;
+    }
+    qDebug() << "GTextLineItem::focusInEvent";
+    if (event->reason() != Qt::PopupFocusReason){ // to fix the special char issue on VKB
+      qDebug() << "GTextLineItem::focusInEvent: emit focus changed ";
+      emit focusChanged(true);
+    }
+  }
+
+  void GTextLineItem::focusOutEvent(QFocusEvent * event)
+  {
+    Q_UNUSED(event);
+    // restore the drag & drop for other components
+    QApplication::setStartDragDistance(m_defaultStartDragDistance);
+    QGraphicsTextItem::focusOutEvent(event);
+    if (event->reason() != Qt::PopupFocusReason && event->reason() != Qt::ActiveWindowFocusReason) // to fix the special char issue on VKB
+      emit focusChanged(false);
+  }
+
+  void GTextLineItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
+  {
+    // ignore context menu event
+    event->ignore();
+  }
+
+
+  // Methods for class GLineEditor
+  // GLineEditor is a QGraphicsWidget that wraps a GTextLineItem to implement scrolling, 
+  // draw a background and set padding 
+
+  GLineEditor::GLineEditor(ChromeSnippet * snippet, ChromeWidget * chrome, QGraphicsItem * parent)
+  : QGraphicsWidget(parent)
+  , m_chrome(chrome)
+  , m_viewPortWidth(0.0)
+  , m_viewPortHeight(0.0)
+  , m_padding(0.0)
+  , m_rightTextMargin(0.0)
+  {
+    Q_UNUSED(snippet);
+
+    // The viewport clips the editor when text overflows
+    // viewport size will be set in resize()
+    m_viewPort = new QGraphicsWidget(this);
+    m_viewPort->setFlags(QGraphicsItem::ItemClipsChildrenToShape);
+
+    // The actual text editor item
+    m_editor = new GTextLineItem(m_viewPort);
+    m_editor->setDefaultTextColor(m_textColor);
+    m_editor->installEventFilter(this);
+
+    // Monitor editor cursor position changes for horizontal scrolling.
+    safe_connect(m_editor, SIGNAL(cursorXChanged(qreal)),
+                 this, SLOT(makeVisible(qreal)));
+
+    safe_connect(m_editor, SIGNAL(textMayChanged()),
+                 this, SIGNAL(textMayChanged()));
+
+    safe_connect(m_editor, SIGNAL(focusChanged(bool)),
+                 this, SIGNAL(focusChanged(bool)));
+    
+    safe_connect(m_editor, SIGNAL(tapped(QPointF&)),
+                 this, SIGNAL(tapped(QPointF&)));
+
+    setAcceptDrops(false);
+  }
+
+  GLineEditor::~GLineEditor()
+  {
+  }
+
+  // TODO: Be a good QGraphicsWidget: update this to use palette color?
+
+  void GLineEditor::setTextColor(QColor & color)
+  {
+    m_textColor = color;
+    m_editor->setDefaultTextColor(m_textColor);
+  }
+
+  void GLineEditor::setPadding(qreal padding)
+  {
+    m_padding = padding;
+    //Trigger a resize to adjust component sizes to new padding
+    resize(size());
+  }
+
+  void GLineEditor::setRightTextMargin(qreal margin)
+  {
+    m_rightTextMargin = margin;
+    resize(size());
+  }
+
+  QString GLineEditor::text() const
+  {
+    return m_editor->toPlainText();
+  }
+
+  void GLineEditor::paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget)
+  {
+    Q_UNUSED(option);
+    Q_UNUSED(widget);
+
+    painter->save();
+    painter->setRenderHint(QPainter::Antialiasing);
+    // First, fill rectangle with background color.
+    qDebug() << "GLineEditor::paint" << boundingRect().width() << " " << size().width();
+    painter->fillRect(boundingRect(), m_backgroundColor);
+    painter->restore();
+    if(!isEnabled()) {
+        ChromeEffect::paintDisabledRect(painter, option->exposedRect);
+    }
+    // Make sure any required horizontal scrolling happens
+    // before rendering editor widget, which will be drawn on top
+    // of the background rectangle.
+    makeVisible(m_editor->cursorX());
+  }
+
+  void GLineEditor::resizeEvent(QGraphicsSceneResizeEvent * event)
+  {
+    QSizeF size = event->newSize();
+    m_viewPortWidth  = size.width() - m_rightTextMargin  - m_padding * 2;
+    m_viewPortHeight = size.height() - m_padding * 2;
+    m_viewPort->setGeometry(
+                            m_padding,
+                            (size.height() - m_editor->boundingRect().height()) / 2,
+                            m_viewPortWidth,
+                            m_viewPortHeight);
+    m_editor->setTextWidth(m_viewPortWidth);
+    // move back the m_editor'x to 0
+    qreal editorShift = -1 * m_editor->pos().x();
+    m_editor->moveBy(editorShift, 0);
+    updateEditor();
+  }
+
+  void GLineEditor::setText(const QString & text)
+  {
+    m_editor->setText(text);
+    m_editor->setPos(0, m_editor->pos().y());
+    updateEditor();
+  }
+
+  void GLineEditor::updateEditor()
+  {
+    makeVisible(m_editor->cursorX());
+  }
+
+  // We divide the viewport into 3 distinct regions:
+  //
+  //
+  //        [ left | middle | right ]
+  //
+  // [ editor, shifted left by editorShift pixels ]
+  //
+  // When a cursor is in the middle section of the viewport we
+  // leave the editor shift unchanged, to preserve stability.
+  //
+  // When a cursor is in the right section or beyond we shift
+  // the editor left until the cursor appears at the border
+  // between the middle and right sections.
+  //
+  // When a cursor is in the left section or beyond we shift
+  // the editor right until the cursor appears at the border
+  // between the left and middle sections.
+  //
+  // We never shift the editor right of the viewport.
+
+  void GLineEditor::makeVisible(qreal cursorX)
+  {
+    qreal leftScrollBorder  = 0;
+    qreal rightScrollBorder = m_viewPortWidth - 20;
+    qreal editorShift = -1 * m_editor->pos().x();
+    qreal localX = cursorX - editorShift;
+
+    if (m_editor->textWidth() < rightScrollBorder) {
+      if (editorShift !=0)
+        m_editor->moveBy(editorShift, 0);
+      return;
+    }
+    
+    if (m_editor->textWidth() - editorShift < rightScrollBorder) {           
+      if (editorShift != 0)
+        m_editor->moveBy(rightScrollBorder - m_editor->textWidth() + editorShift, 0);
+      return;
+    }
+
+    if (localX < leftScrollBorder) {
+      // Before left section, scroll right.
+      // In left section, scroll right.
+      qreal shift = qMin(leftScrollBorder - localX, editorShift);
+      m_editor->moveBy(shift, 0);
+      return;
+    }
+    if (localX >= rightScrollBorder) {
+      // In right section, scroll left.
+      // After right section, scroll left.
+      qreal shift = localX - rightScrollBorder;
+      m_editor->moveBy(-shift, 0);
+      return;
+    }
+    // In middle section, no scroll needed.
+    return;
+  }
+
+  bool GLineEditor::tappedOnText(qreal x) const
+  {
+    qreal editorShift = m_editor->pos().x();
+    qreal editorWidth = m_editor->textWidth();
+    if (editorShift == 0 && editorWidth < x)
+      return false;
+    return true;
+  }
+
+  bool GLineEditor::eventFilter(QObject * object, QEvent * event)
+  {
+    // Filter editor key events.
+    if (object != m_editor)
+      return false;
+
+    if (event->type() != QEvent::KeyPress)
+      return false;
+
+    QKeyEvent * keyEvent = static_cast<QKeyEvent*>(event);
+    switch (keyEvent->key()) {
+    case Qt::Key_Select:
+    case Qt::Key_Return:
+    case Qt::Key_Enter:
+      // Signal that a carriage return-like key-press happened.
+      emit activated();
+      return true;
+    case Qt::Key_Down:
+    case Qt::Key_Up:
+      // Swallow arrow up/down keys, editor has just one line.
+      return true;
+    default:
+      return false;
+    }
+  }
+
+  //GTextEditor paints a styled frame around a GLineEditor
+
+  GTextEditor::GTextEditor(ChromeSnippet * snippet, ChromeWidget * chrome, QGraphicsItem * parent)
+  : GLineEditor(snippet, chrome, parent)
+  {
+  }
+
+  GTextEditor::~GTextEditor()
+  {
+  }
+
+  void GTextEditor::paintBorder(QPainter * painter)
+  {
+    if (m_padding > 0 ) {
+      QPainterPath border;
+      //qDebug() << "GTextEditor::paintBorder: " << boundingRect().width() << " " << size().width();
+      border.addRect(boundingRect());
+      border.addRoundedRect(
+                            m_padding,
+                            m_padding,
+                            size().width()-m_padding*2,
+                            size().height()-m_padding*2,
+                            4,
+                            4);
+      painter->fillPath(border, m_borderColor);
+    }
+  }
+
+  void GTextEditor::paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget)
+  {
+    Q_UNUSED(option);
+    Q_UNUSED(widget);
+
+    //updateEditor();
+    GLineEditor::paint(painter, option, widget);
+    painter->save();
+    painter->setRenderHint(QPainter::Antialiasing);
+    paintBorder(painter);
+    painter->restore();
+  }
+
+  //GProgressEditor adds a progress bar to GTextEditor. Joining these into a single QGraphicsWidget makes it
+  //possible to draw a text editor with a progress bar with minimal updates
+
+  GProgressEditor::GProgressEditor(ChromeSnippet * snippet, ChromeWidget * chrome, QGraphicsItem * parent)
+  : GTextEditor(snippet, chrome, parent)
+  , m_percent(0)
+  {
+  }
+
+  GProgressEditor::~GProgressEditor()
+  {
+  }
+
+  void GProgressEditor::paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget)
+  {
+    Q_UNUSED(option);
+    Q_UNUSED(widget);
+
+    updateEditor();
+
+    QRectF progressRect = boundingRect();
+    progressRect.setWidth(progressRect.width() * m_percent / 100.0);
+
+    painter->save();
+    painter->setRenderHint(QPainter::Antialiasing);
+    painter->fillRect(progressRect, m_progressColor);
+    paintBorder(painter);
+    painter->restore();
+    if(!isEnabled()) {
+        ChromeEffect::paintDisabledRect(painter, option->exposedRect);
+    }
+  }
+
+  void GProgressEditor::setProgress(int percent)
+  {
+    // Don't bother with small increments, but ...
+    //
+    // - ALWAYS show the final increment to 99 or 100, because
+    //   this tells the user a network request has completed.
+    //
+    // - ALWAYS show the initial increment from 0, because this
+    //   tells the user a new network request has started.
+    //
+    // - ALWAYS show decrements, because this tells the user
+    //   a new network request has started.
+    if (percent < 99) {
+      if (m_percent > 0) {
+        if (percent > m_percent) {
+          if (percent - m_percent < 10) {
+            //qDebug() << "UrlSearchSnippet::setProgress" << percent << "IGNORE";
+            return;
+          }
+        }
+      }
+    }
+
+    if (m_percent == percent) {
+      //qDebug() << "UrlSearchSnippet::setProgress" << percent << "IGNORE";
+      return;
+    }
+
+    //qDebug() << "UrlSearchSnippet::setProgress" << percent << "UPDATE";
+    m_percent = percent;
+    update();
+  }
+
+  //A chrome item that displays a GTextEditor. This can be embedded in HTML chrome instead of an input field.
+
+  TextEditItem::TextEditItem(ChromeSnippet * snippet, ChromeWidget * chrome, QGraphicsItem * parent)
+    : NativeChromeItem(snippet, parent)
+  {
+    m_textEditor = new GTextEditor(snippet, chrome, this);
+  
+    //Style via CSS
+    QWebElement we = m_snippet->element();
+
+    QColor textColor;
+    NativeChromeItem::CSSToQColor(we.styleProperty("color", QWebElement::ComputedStyle),
+                                  textColor);
+    m_textEditor->setTextColor(textColor);
+
+    //Background of text box 
+    QColor backgroundColor;
+    NativeChromeItem::CSSToQColor(we.styleProperty("background-color", QWebElement::ComputedStyle),
+                                  backgroundColor); 
+    m_textEditor->setBackgroundColor(backgroundColor);
+
+    QColor borderColor;
+    NativeChromeItem::CSSToQColor(we.styleProperty("border-top-color", QWebElement::ComputedStyle),
+                                  borderColor);
+    m_textEditor->setBorderColor(borderColor);
+
+    //Padding sets the "border" width
+    QString cssPadding = we.styleProperty("padding-top", QWebElement::ComputedStyle);
+    m_textEditor->setPadding(cssPadding.remove("px").toInt());
+  }
+  
+  TextEditItem::~TextEditItem()
+  {
+    delete m_textEditor;
+  }
+
+  void TextEditItem::resizeEvent(QGraphicsSceneResizeEvent * ev)
+  {
+    m_textEditor->resize(ev->newSize());
+  }
+
+} // namespace GVA