#include "DocumentItem.h" #include "DocumentBuffer.h" #include #include #include #include #include #include #include DocumentItem::DocumentItem(int lineCharacterCount, int lineCount, QGraphicsItem *parent) : QGraphicsObject(parent), m_buffer(new DocumentBuffer), m_lineCharacterCount(lineCharacterCount), m_lineCount(lineCount), m_currentRow(0), m_currentCol(0), m_scrollTop(0), m_editable(true) { setFlag(ItemIsFocusable, true); } DocumentItem::~DocumentItem() { delete m_buffer; } void DocumentItem::keyPressEvent(QKeyEvent *event) { if (event->modifiers().testFlag(Qt::AltModifier) || event->modifiers().testFlag(Qt::ControlModifier)) return; if ((event->key() == Qt::Key_Backspace && !event->isAutoRepeat()) || event->key() == Qt::Key_Left) { if (--m_currentCol == -1) { m_currentRow = m_currentRow - 1; if (m_currentRow == -1) { m_currentRow = 0; m_currentCol = 0; } else m_currentCol = qMin(m_lineCharacterCount - 1, m_buffer->lineSize(m_currentRow)); } } else if (event->key() == Qt::Key_Return && !event->isAutoRepeat()) { m_buffer->insertCharacterAt(QLatin1Char('\n'), m_currentRow, m_currentCol); m_currentCol = 0; ++m_currentRow; } else if (event->key() == Qt::Key_Down) { m_currentRow = qMin(m_currentRow + 1, m_buffer->linesCount() - 1); m_currentCol = qMin(m_currentCol, m_buffer->lineSize(m_currentRow)); } else if (event->key() == Qt::Key_Right) { if (m_currentCol < m_buffer->lineSize(m_currentRow)) ++m_currentCol; else { m_currentRow = qMin(m_currentRow + 1, m_buffer->linesCount() - 1); if (m_currentRow != m_buffer->linesCount() - 1) m_currentCol = 0; } } else if (event->key() == Qt::Key_Up) { m_currentRow = qMax(0, m_currentRow - 1); m_currentCol = qMin(m_currentCol, m_buffer->lineSize(m_currentRow)); } else if (event->key() == Qt::Key_PageUp) scroll(-m_lineCount * 3 / 4); else if (event->key() == Qt::Key_PageDown) scroll(m_lineCount * 3 / 4); else if (m_editable && !event->isAutoRepeat() && event->text().size() == 1 && event->text().at(0).isPrint()) { m_buffer->insertCharacterAt(event->text().at(0), m_currentRow, m_currentCol++); if (m_currentCol == m_lineCharacterCount) { m_currentCol = 0; ++m_currentRow; } } if (m_currentRow - m_scrollTop >= m_lineCount) ++m_scrollTop; update(); } void DocumentItem::wheelEvent(QGraphicsSceneWheelEvent *event) { if (event->orientation() != Qt::Vertical) return; if (m_buffer->linesCount() <= m_lineCount) return; scroll(-event->delta() / 50); } void DocumentItem::scroll(int lines) { m_scrollTop += lines; if (m_scrollTop < 0) m_scrollTop = 0; else if (m_scrollTop >= m_buffer->linesCount()) m_scrollTop = m_buffer->linesCount() - 1; m_currentRow = qBound(m_scrollTop, m_currentRow, m_scrollTop + m_lineCount - 1); m_currentCol = qMin(m_currentCol, m_buffer->lineSize(m_currentRow)); update(); } QRectF DocumentItem::boundingRect() const { return QRectF(0, 0, m_lineCharacterCount * CHARACTER_WIDTH, m_lineCount * CHARACTER_HEIGHT); } void DocumentItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option) Q_UNUSED(widget) // Draw background painter->save(); if (hasFocus()) painter->setBrush(QBrush(QColor(255, 255, 200))); else painter->setBrush(QBrush(QColor(255, 255, 220))); painter->drawRect(boundingRect()); painter->restore(); // Setup font QFont font; font.setFamily("CMU Typewriter Text"); font.setKerning(false); font.setFixedPitch(true); // Draw scroll indication painter->save(); font.setPixelSize(CHARACTER_WIDTH * 5); painter->setFont(font); painter->setBrush(QBrush(QColor(200, 200, 200))); painter->setOpacity(0.25); if (m_scrollTop + m_lineCount < m_buffer->linesCount()) painter->drawText(boundingRect(), Qt::AlignBottom | Qt::AlignRight, QString::fromUtf8("\u2193")); if (m_scrollTop > 0) painter->drawText(boundingRect(), Qt::AlignTop | Qt::AlignRight, QString::fromUtf8("\u2191")); painter->restore(); // Draw text painter->save(); font.setPixelSize(CHARACTER_WIDTH * 1.5); painter->setFont(font); painter->setBrush(QBrush(QColor(30, 30, 30))); painter->setPen(QPen(QColor(30, 30, 30))); for (int row = m_scrollTop; row < m_lineCount + m_scrollTop; ++row) { const double y = (row - m_scrollTop) * CHARACTER_HEIGHT; if (row >= m_buffer->linesCount()) break; for (int col = 0; col < m_buffer->lineSize(row); ++col) { const double x = col * CHARACTER_WIDTH; const QLinkedList &characters = m_buffer->charactersAt(row, col); const int size = characters.size(); int i = 0; foreach (const QChar &character, characters) { painter->setOpacity(qMin(1.0, 1.0 / static_cast(size - i++) + 0.2)); painter->drawText(x, y, CHARACTER_WIDTH, CHARACTER_HEIGHT, Qt::TextSingleLine | Qt::AlignHCenter | Qt::AlignVCenter, QString(character)); } } } painter->restore(); // Draw cursor if (hasFocus() && isEditable()) { painter->save(); painter->setOpacity(0.5); painter->setBrush(QBrush(QColor(0, 0, 0))); painter->drawRect(m_currentCol * CHARACTER_WIDTH, (m_currentRow - m_scrollTop) * CHARACTER_HEIGHT, CHARACTER_WIDTH, CHARACTER_HEIGHT); painter->restore(); } // Draw rule lines painter->save(); QPen pen(QColor(190, 0, 0, isEditable() ? 64 : 32)); pen.setCapStyle(Qt::FlatCap); pen.setWidth(1); pen.setCosmetic(true); painter->setPen(pen); for (int i = 1; i < m_lineCount; ++i) { const double height = i * CHARACTER_HEIGHT; painter->drawLine(QPointF(boundingRect().left(), height), QPointF(boundingRect().right(), height)); } painter->restore(); } const DocumentBuffer& DocumentItem::buffer() const { return *m_buffer; } void DocumentItem::setBuffer(DocumentBuffer *buffer) { if (m_buffer) delete m_buffer; m_buffer = buffer; } bool DocumentItem::isEditable() const { return m_editable; } void DocumentItem::setEditable(bool on) { m_editable = on; } void DocumentItem::setText(const QString &text) { const QPair place = m_buffer->setText(text, m_lineCharacterCount); m_currentRow = place.first; m_currentCol = place.second; } QPair DocumentItem::cursorLocation() const { return qMakePair(m_currentRow, m_currentCol); } void DocumentItem::setCursorLocation(const QPair &location) { m_currentRow = location.first; m_currentCol = location.second; update(); } int DocumentItem::scrollTop() const { return m_scrollTop; } void DocumentItem::setScrollTop(int top) { m_scrollTop = top; update(); }