#include #include #include #include #include #include #include #include #include ExpressionLayoutField::ExpressionLayoutField(Responder * parentResponder, Poincare::ExpressionLayout * expressionLayout, ExpressionLayoutFieldDelegate * delegate) : ScrollableView(parentResponder, &m_contentView, this), m_contentView(expressionLayout), m_delegate(delegate) { } bool ExpressionLayoutField::isEditing() const { return m_contentView.isEditing(); } void ExpressionLayoutField::setEditing(bool isEditing) { m_contentView.setEditing(isEditing); } void ExpressionLayoutField::clearLayout() { m_contentView.clearLayout(); } void ExpressionLayoutField::scrollToCursor() { scrollToBaselinedRect(m_contentView.cursorRect(), m_contentView.cursor()->baseline()); } Toolbox * ExpressionLayoutField::toolbox() { if (m_delegate) { return m_delegate->toolboxForExpressionLayoutField(this); } return nullptr; } bool ExpressionLayoutField::handleEvent(Ion::Events::Event event) { bool didHandleEvent = false; bool shouldRecomputeLayout = m_contentView.cursor()->showEmptyLayoutIfNeeded(); bool moveEventChangedLayout = false; if (privateHandleMoveEvent(event, &moveEventChangedLayout)) { if (!isEditing()) { setEditing(true); } shouldRecomputeLayout = shouldRecomputeLayout || moveEventChangedLayout; didHandleEvent = true; } else if (privateHandleEvent(event)) { shouldRecomputeLayout = true; didHandleEvent = true; } if (didHandleEvent) { shouldRecomputeLayout = m_contentView.cursor()->hideEmptyLayoutIfNeeded() || shouldRecomputeLayout; if (!shouldRecomputeLayout) { m_contentView.cursorPositionChanged(); scrollToCursor(); } else { reload(); } return true; } m_contentView.cursor()->hideEmptyLayoutIfNeeded(); return false; } bool ExpressionLayoutField::expressionLayoutFieldShouldFinishEditing(Ion::Events::Event event) { return m_delegate->expressionLayoutFieldShouldFinishEditing(this, event); } KDSize ExpressionLayoutField::minimalSizeForOptimalDisplay() const { KDSize contentViewSize = m_contentView.minimalSizeForOptimalDisplay(); return KDSize(contentViewSize.width(), contentViewSize.height()); } bool ExpressionLayoutField::privateHandleMoveEvent(Ion::Events::Event event, bool * shouldRecomputeLayout) { Poincare::ExpressionLayoutCursor result; if (event == Ion::Events::Left) { result = m_contentView.cursor()->cursorOnLeft(shouldRecomputeLayout); } else if (event == Ion::Events::Right) { result = m_contentView.cursor()->cursorOnRight(shouldRecomputeLayout); } else if (event == Ion::Events::Up) { result = m_contentView.cursor()->cursorAbove(shouldRecomputeLayout); } else if (event == Ion::Events::Down) { result = m_contentView.cursor()->cursorUnder(shouldRecomputeLayout); } else if (event == Ion::Events::ShiftLeft) { *shouldRecomputeLayout = true; if (m_contentView.cursor()->pointedExpressionLayout()->removeGreySquaresFromAllMatrixAncestors()) { *shouldRecomputeLayout = true; } result.setPointedExpressionLayout(expressionLayout()); result.setPosition(Poincare::ExpressionLayoutCursor::Position::Left); } else if (event == Ion::Events::ShiftRight) { if (m_contentView.cursor()->pointedExpressionLayout()->removeGreySquaresFromAllMatrixAncestors()) { *shouldRecomputeLayout = true; } result.setPointedExpressionLayout(expressionLayout()); result.setPosition(Poincare::ExpressionLayoutCursor::Position::Right); } if (result.isDefined()) { m_contentView.setCursor(result); return true; } return false; } bool ExpressionLayoutField::privateHandleEvent(Ion::Events::Event event) { if (m_delegate && m_delegate->expressionLayoutFieldDidReceiveEvent(this, event)) { return true; } if (Responder::handleEvent(event)) { /* The only event Responder handles is 'Toolbox' displaying. In that case, * the ExpressionLayoutField is forced into editing mode. */ if (!isEditing()) { setEditing(true); } return true; } if (isEditing() && expressionLayoutFieldShouldFinishEditing(event)) { setEditing(false); if (m_delegate->expressionLayoutFieldDidFinishEditing(this, expressionLayout(), event)) { clearLayout(); } return true; } if ((event == Ion::Events::OK || event == Ion::Events::EXE) && !isEditing()) { setEditing(true); m_contentView.cursor()->setPointedExpressionLayout(expressionLayout()); m_contentView.cursor()->setPosition(Poincare::ExpressionLayoutCursor::Position::Right); return true; } if (event == Ion::Events::Back && isEditing()) { clearLayout(); setEditing(false); m_delegate->expressionLayoutFieldDidAbortEditing(this); return true; } if (event.hasText() || event == Ion::Events::Paste || event == Ion::Events::Backspace) { if (!isEditing()) { setEditing(true); } if (event.hasText()) { handleEventWithText(event.text()); } else if (event == Ion::Events::Paste) { handleEventWithText(Clipboard::sharedClipboard()->storedText(), false, true); } else { assert(event == Ion::Events::Backspace); m_contentView.cursor()->performBackspace(); } return true; } if (event == Ion::Events::Clear && isEditing()) { clearLayout(); return true; } return false; } void ExpressionLayoutField::reload() { KDSize previousSize = minimalSizeForOptimalDisplay(); expressionLayout()->invalidAllSizesPositionsAndBaselines(); KDSize newSize = minimalSizeForOptimalDisplay(); if (m_delegate && previousSize.height() != newSize.height()) { m_delegate->expressionLayoutFieldDidChangeSize(this); } m_contentView.cursorPositionChanged(); scrollToCursor(); markRectAsDirty(bounds()); } bool ExpressionLayoutField::hasText() const { return expressionLayout()->hasText(); } int ExpressionLayoutField::writeTextInBuffer(char * buffer, int bufferLength) { return expressionLayout()->writeTextInBuffer(buffer, bufferLength); } bool ExpressionLayoutField::handleEventWithText(const char * text, bool indentation, bool forceCursorRightOfText) { if (text[0] == 0) { // The text is empty return true; } int currentNumberOfLayouts = m_contentView.expressionView()->numberOfLayouts(); if (currentNumberOfLayouts >= k_maxNumberOfLayouts - 6) { /* We add -6 because in some cases (Ion::Events::Division, * Ion::Events::Exp...) we let the layout cursor handle the layout insertion * and these events may add at most 6 layouts (e.g *10^ΓΈ). */ return true; } // Handle special cases if (strcmp(text, Ion::Events::Division.text()) == 0) { m_contentView.cursor()->addFractionLayoutAndCollapseSiblings(); } else if (strcmp(text, Ion::Events::Exp.text()) == 0) { m_contentView.cursor()->addEmptyExponentialLayout(); } else if (strcmp(text, Ion::Events::Power.text()) == 0) { m_contentView.cursor()->addEmptyPowerLayout(); } else if (strcmp(text, Ion::Events::Sqrt.text()) == 0) { m_contentView.cursor()->addEmptySquareRootLayout(); } else if (strcmp(text, Ion::Events::Square.text()) == 0) { m_contentView.cursor()->addEmptySquarePowerLayout(); } else if (strcmp(text, Ion::Events::EE.text()) == 0) { m_contentView.cursor()->addEmptyTenPowerLayout(); } else if ((strcmp(text, "[") == 0) || (strcmp(text, "]") == 0)) { m_contentView.cursor()->addEmptyMatrixLayout(); } else { Poincare::Expression * resultExpression = Poincare::Expression::parse(text); if (resultExpression == nullptr) { m_contentView.cursor()->insertText(text); return true; } Poincare::ExpressionLayout * resultLayout = resultExpression->createLayout(Poincare::Preferences::sharedPreferences()->displayMode(), Poincare::PrintFloat::k_numberOfStoredSignificantDigits); delete resultExpression; if (currentNumberOfLayouts + resultLayout->numberOfDescendants(true) >= k_maxNumberOfLayouts) { delete resultLayout; return true; } // Find the pointed layout. Poincare::ExpressionLayout * pointedLayout = nullptr; if (strcmp(text, I18n::translate(I18n::Message::RandomCommandWithArg)) == 0) { /* Special case: if the text is "random()", the cursor should not be set * inside the parentheses. */ pointedLayout = resultLayout; } else if (resultLayout->isHorizontal()) { /* If the layout is horizontal, pick the first open parenthesis. For now, * all horizontal layouts in MathToolbox have parentheses. */ for (int i = 0; i < resultLayout->numberOfChildren(); i++) { if (resultLayout->editableChild(i)->isLeftParenthesis()) { pointedLayout = resultLayout->editableChild(i); break; } } } /* Insert the layout. If pointedLayout is nullptr, the cursor will be on the * right of the inserted layout. */ insertLayoutAtCursor(resultLayout, pointedLayout, forceCursorRightOfText); } return true; } Poincare::ExpressionLayout * ExpressionLayoutField::expressionLayout() const { return m_contentView.expressionView()->expressionLayout(); } char ExpressionLayoutField::XNTChar() { return m_contentView.cursor()->pointedExpressionLayout()->XNTChar(); } void ExpressionLayoutField::setBackgroundColor(KDColor c) { ScrollableView::setBackgroundColor(c); m_contentView.setBackgroundColor(c); } void ExpressionLayoutField::scrollRightOfLayout(Poincare::ExpressionLayout * layout) { KDRect layoutRect(layout->absoluteOrigin().translatedBy(m_contentView.expressionView()->drawingOrigin()), layout->size()); scrollToBaselinedRect(layoutRect, layout->baseline()); } void ExpressionLayoutField::scrollToBaselinedRect(KDRect rect, KDCoordinate baseline) { scrollToContentRect(rect, true); // Show the rect area around its baseline KDCoordinate underBaseline = rect.height() - baseline; KDCoordinate minAroundBaseline = min(baseline, underBaseline); minAroundBaseline = min(minAroundBaseline, bounds().height() / 2); KDRect balancedRect(rect.x(), rect.y() + baseline - minAroundBaseline, rect.width(), 2 * minAroundBaseline); scrollToContentRect(balancedRect, true); } void ExpressionLayoutField::insertLayoutAtCursor(Poincare::ExpressionLayout * layout, Poincare::ExpressionLayout * pointedLayout, bool forceCursorRightOfLayout) { if (layout == nullptr) { return; } m_contentView.cursor()->showEmptyLayoutIfNeeded(); bool layoutWillBeMerged = layout->isHorizontal(); Poincare::ExpressionLayout * lastMergedLayoutChild = layoutWillBeMerged ? layout->editableChild(layout->numberOfChildren()-1) : nullptr; m_contentView.cursor()->addLayoutAndMoveCursor(layout); if (!forceCursorRightOfLayout) { if (pointedLayout != nullptr && (pointedLayout != layout || !layoutWillBeMerged)) { m_contentView.cursor()->setPointedExpressionLayout(pointedLayout); m_contentView.cursor()->setPosition(Poincare::ExpressionLayoutCursor::Position::Right); } else if (!layoutWillBeMerged) { m_contentView.cursor()->setPointedExpressionLayout(layout->layoutToPointWhenInserting()); m_contentView.cursor()->setPosition(Poincare::ExpressionLayoutCursor::Position::Right); } } else if (!layoutWillBeMerged) { m_contentView.cursor()->setPointedExpressionLayout(layout); m_contentView.cursor()->setPosition(Poincare::ExpressionLayoutCursor::Position::Right); } m_contentView.cursor()->pointedExpressionLayout()->addGreySquaresToAllMatrixAncestors(); m_contentView.cursor()->hideEmptyLayoutIfNeeded(); reload(); if (!layoutWillBeMerged) { scrollRightOfLayout(layout); } else { assert(lastMergedLayoutChild != nullptr); scrollRightOfLayout(lastMergedLayoutChild); } scrollToCursor(); }