diff -r b0dd75e285d2 -r 0f2326c2a325 ginebra2/EditorWidget.cpp --- /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(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