diff -r b0dd75e285d2 -r 0954f5dd2cd0 ginebra2/EditorWidget.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ginebra2/EditorWidget.cpp Tue Jun 29 00:46:29 2010 -0400 @@ -0,0 +1,637 @@ +/* + * 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_maxTextLength(0) + , m_hints(Qt::ImhNoPredictiveText) // disable the predictive text + { + // 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() + { + if (m_maxTextLength > 0) + disconnect(document(), SIGNAL(contentsChange(int, int, int)), this, SLOT(contentsChange(int, int, int))); + QApplication::setStartDragDistance(m_defaultStartDragDistance); + } + + void GTextLineItem::setText(const QString & text) + { + QString newText = text; + if(m_maxTextLength > 0 && text.length() > m_maxTextLength ) { + newText = newText.left(m_maxTextLength); + } + setPlainText(newText); + m_textLine = document()->begin().layout()->lineForTextPosition(0); + } + + void GTextLineItem::setMaxTextLength(int length) + { + if (m_maxTextLength <= 0 && length > 0) { + QString text = toPlainText(); + if( text.length() > length ) { + setPlainText(text.left(length)); + } + connect(document(), SIGNAL(contentsChange(int, int, int)), this, SLOT(contentsChange(int, int, int))); + } else if (m_maxTextLength > 0 && length <= 0) { + disconnect(document(), SIGNAL(contentsChange(int, int, int)), this, SLOT(contentsChange(int, int, int))); + } + m_maxTextLength = length; + } + + void GTextLineItem::contentsChange(int position, int charsRemoved, int charsAdded) + { + Q_UNUSED(position); + Q_UNUSED(charsRemoved); + Q_UNUSED(charsAdded); + if( m_maxTextLength > 0 && toPlainText().length() > m_maxTextLength ) { + QTextCursor cursor = textCursor(); + cursor.deletePreviousChar(); + setTextCursor(cursor); + } + } + + // 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); + + QWidget* fw = QApplication::focusWidget(); + Qt::InputMethodHints hints = fw->inputMethodHints(); + if (hints != m_hints) + fw->setInputMethodHints(m_hints); + + if (event->reason() != Qt::PopupFocusReason) // to fix the special char issue on VKB + 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(); + } + + void GTextLineItem::setInputMethodHints(Qt::InputMethodHints hints) + { + m_hints = hints; + m_hints |= Qt::ImhNoPredictiveText; // disable the predictive text + } + + // 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. + 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(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_justFocusIn(false) + { + 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()); + + safe_connect(m_textEditor, SIGNAL(focusChanged(bool)),this, SLOT(focusChanged(bool))); + safe_connect(m_textEditor, SIGNAL(tapped(QPointF&)),this, SLOT(tapped(QPointF&))); +} + + void TextEditItem::tapped(QPointF& pos) +{ + bool hitText = m_textEditor->tappedOnText(pos.x()); + if (!m_justFocusIn && !hitText) + m_textEditor->unselect(); + + if (m_justFocusIn) { + m_justFocusIn = false; + if (hitText && !m_textEditor->hasSelection()) + m_textEditor->selectAll(); + } +} + +void TextEditItem::focusChanged(bool focusIn) +{ + if (focusIn) + m_justFocusIn = true; + else { + m_justFocusIn = false; + m_textEditor->unselect(); + m_textEditor->shiftToLeftEnd(); + } +} + + TextEditItem::~TextEditItem() + { + delete m_textEditor; + } + + void TextEditItem::resizeEvent(QGraphicsSceneResizeEvent * ev) + { + m_textEditor->resize(ev->newSize()); + } + + void TextEditItem::setTextOptions(int flag) + { + m_textEditor->setInputMethodHints((Qt::InputMethodHints)flag); + } + +} // namespace GVA