diff --git a/escher/Makefile b/escher/Makefile index 237f6ed2f..671ada2d8 100644 --- a/escher/Makefile +++ b/escher/Makefile @@ -29,6 +29,8 @@ objs += $(addprefix escher/src/,\ invocation.o\ input_view_controller.o\ key_view.o\ + layout_field.o\ + layout_field_content_view.o\ list_view_data_source.o\ message_table_cell.o\ message_table_cell_with_buffer.o\ diff --git a/escher/include/escher/layout_field.h b/escher/include/escher/layout_field.h new file mode 100644 index 000000000..9c27ae5bf --- /dev/null +++ b/escher/include/escher/layout_field.h @@ -0,0 +1,122 @@ +#ifndef ESCHER_LAYOUT_FIELD_H +#define ESCHER_LAYOUT_FIELD_H + +#include +#include +#include +#include +#include +#include +#include +#include + +class LayoutField : public ScrollableView, public ScrollViewDataSource { +public: + LayoutField(Responder * parentResponder, Poincare::LayoutRef layoutR, LayoutFieldDelegate * delegate = nullptr) : + ScrollableView(parentResponder, &m_contentView, this), + m_contentView(layoutR), + m_delegate(delegate) + {} + void setDelegate(LayoutFieldDelegate * delegate) { m_delegate = delegate; } + bool isEditing() const { return m_contentView.isEditing(); } + void setEditing(bool isEditing) { m_contentView.setEditing(isEditing); } + void clearLayout() { m_contentView.clearLayout(); } + void scrollToCursor() { + scrollToBaselinedRect(m_contentView.cursorRect(), m_contentView.cursor()->baseline()); + } + void reload(); + bool hasText() const { return layoutRef().hasText(); } + int writeTextInBuffer(char * buffer, int bufferLength) { return layoutRef().writeTextInBuffer(buffer, bufferLength); } + Poincare::LayoutRef layoutRef() const { return m_contentView.expressionView()->layoutRef(); } + char XNTChar() { return m_contentView.cursor()->layoutReference().XNTChar(); } + + // ScrollableView + void setBackgroundColor(KDColor c) override { + ScrollableView::setBackgroundColor(c); + m_contentView.setBackgroundColor(c); + } + + /* Responder */ + bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false) override; + bool handleEvent(Ion::Events::Event event) override; + Toolbox * toolbox() override { + return m_delegate != nullptr ? m_delegate->toolboxForLayoutField(this) : nullptr; + } + bool layoutFieldShouldFinishEditing(Ion::Events::Event event) { // TODO REMOVE ? + return m_delegate->layoutFieldShouldFinishEditing(this, event); + } + + /* View */ + KDSize minimalSizeForOptimalDisplay() const override { + KDSize contentViewSize = m_contentView.minimalSizeForOptimalDisplay(); + return KDSize(contentViewSize.width(), contentViewSize.height()); + } + +protected: + virtual bool privateHandleEvent(Ion::Events::Event event); + bool privateHandleMoveEvent(Ion::Events::Event event, bool * shouldRecomputeLayout); + +private: + constexpr static int k_maxNumberOfLayouts = 152; + static_assert(k_maxNumberOfLayouts == TextField::maxBufferSize(), "Maximal number of layouts in a layout field should be equal to max number of char in text field"); + void scrollRightOfLayout(Poincare::LayoutRef layoutR); + void scrollToBaselinedRect(KDRect rect, KDCoordinate baseline); + void insertLayoutAtCursor(Poincare::LayoutRef layoutR, Poincare::LayoutRef pointedLayoutRef, bool forceCursorRightOfLayout = false); + + class ContentView : public View { + public: + ContentView(Poincare::LayoutRef layoutR) : + m_cursor(layoutR, Poincare::LayoutCursor::Position::Right), + m_expressionView(0.0f, 0.5f, KDColorBlack, KDColorWhite), + m_cursorView(), + m_isEditing(false) + { + m_expressionView.setLayoutRef(layoutR); + } + + bool isEditing() const { return m_isEditing; } + void setEditing(bool isEditing) { + m_isEditing = isEditing; + markRectAsDirty(bounds()); + layoutSubviews(); + } + + void setBackgroundColor(KDColor c) { m_expressionView.setBackgroundColor(c); } + void setCursor(Poincare::LayoutCursor cursor) { m_cursor = cursor; } + void cursorPositionChanged() { layoutCursorSubview(); } + KDRect cursorRect() { return m_cursorView.frame(); } + Poincare::LayoutCursor * cursor() { return &m_cursor; } + const ExpressionView * expressionView() const { return &m_expressionView; } + ExpressionView * editableExpressionView() { return &m_expressionView; } + void clearLayout() { m_cursor.clearLayout(); } + /* View */ + KDSize minimalSizeForOptimalDisplay() const override { + KDSize evSize = m_expressionView.minimalSizeForOptimalDisplay(); + return KDSize(evSize.width() + Poincare::LayoutCursor::k_cursorWidth, evSize.height()); + } + private: + enum class Position { + Top, + Bottom + }; + int numberOfSubviews() const override { return 2; } + View * subviewAtIndex(int index) override { + assert(index >= 0 && index < 2); + View * m_views[] = {&m_expressionView, &m_cursorView}; + return m_views[index]; + } + void layoutSubviews() override { + m_expressionView.setFrame(bounds()); + layoutCursorSubview(); + } + void layoutCursorSubview(); + Poincare::LayoutCursor m_cursor; + ExpressionView m_expressionView; + TextCursorView m_cursorView; + bool m_isEditing; + }; + ContentView m_contentView; + LayoutFieldDelegate * m_delegate; +}; + +#endif diff --git a/escher/include/escher/layout_field_delegate.h b/escher/include/escher/layout_field_delegate.h new file mode 100644 index 000000000..e0ea86b89 --- /dev/null +++ b/escher/include/escher/layout_field_delegate.h @@ -0,0 +1,19 @@ +#ifndef ESCHER_LAYOUT_FIELD_DELEGATE_H +#define ESCHER_LAYOUT_FIELD_DELEGATE_H + +#include +#include + +class LayoutField; + +class LayoutFieldDelegate { +public: + virtual bool layoutFieldShouldFinishEditing(LayoutField * layoutField, Ion::Events::Event event) = 0; + virtual bool layoutFieldDidReceiveEvent(LayoutField * layoutField, Ion::Events::Event event) = 0; + virtual bool layoutFieldDidFinishEditing(LayoutField * layoutField, Poincare::LayoutRef layoutR, Ion::Events::Event event) { return false; } + virtual bool layoutFieldDidAbortEditing(LayoutField * layoutField) { return false; } + virtual void layoutFieldDidChangeSize(LayoutField * layoutField) {} + virtual Toolbox * toolboxForLayoutField(LayoutField * layoutField) = 0; +}; + +#endif diff --git a/escher/src/layout_field.cpp b/escher/src/layout_field.cpp new file mode 100644 index 000000000..1b0882e4e --- /dev/null +++ b/escher/src/layout_field.cpp @@ -0,0 +1,266 @@ +#include +#include +#include +#include +#include +#include +//#include //TODO +#include +#include + +using namespace Poincare; + +void LayoutField::ContentView::layoutCursorSubview() { + if (!m_isEditing) { + m_cursorView.setFrame(KDRectZero); + return; + } + KDPoint expressionViewOrigin = m_expressionView.absoluteDrawingOrigin(); + LayoutRef pointedLayoutR = m_cursor.layoutReference(); + LayoutCursor::Position cursorPosition = m_cursor.position(); + LayoutCursor eqCursor = pointedLayoutR.equivalentCursor(&m_cursor); + if (pointedLayoutR.hasChild(eqCursor.layoutReference())) { + pointedLayoutR = eqCursor.layoutReference(); + cursorPosition = eqCursor.position(); + } + KDPoint cursoredExpressionViewOrigin = pointedLayoutR.absoluteOrigin(); + KDCoordinate cursorX = expressionViewOrigin.x() + cursoredExpressionViewOrigin.x(); + if (cursorPosition == LayoutCursor::Position::Right) { + cursorX += pointedLayoutR.layoutSize().width(); + } + KDPoint cursorTopLeftPosition(cursorX, expressionViewOrigin.y() + cursoredExpressionViewOrigin.y() + pointedLayoutR.baseline() - m_cursor.baseline()); + m_cursorView.setFrame(KDRect(cursorTopLeftPosition, LayoutCursor::k_cursorWidth, m_cursor.cursorHeight())); +} + +void LayoutField::reload() { + KDSize previousSize = minimalSizeForOptimalDisplay(); + layoutRef().invalidAllSizesPositionsAndBaselines(); + KDSize newSize = minimalSizeForOptimalDisplay(); + if (m_delegate && previousSize.height() != newSize.height()) { + m_delegate->layoutFieldDidChangeSize(this); + } + m_contentView.cursorPositionChanged(); + scrollToCursor(); + markRectAsDirty(bounds()); +} + +bool LayoutField::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 { + Expression * resultExpression = Expression::parse(text); + if (resultExpression == nullptr) { + m_contentView.cursor()->insertText(text); + return true; + } + LayoutRef resultLayoutRef = resultExpression->createLayout(); + delete resultExpression; + if (currentNumberOfLayouts + resultLayoutRef.numberOfDescendants(true) >= k_maxNumberOfLayouts) { + return true; + } + // Find the pointed layout. + LayoutRef pointedLayoutRef(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. */ + pointedLayoutRef = resultLayoutRef; + } else if (resultLayoutRef.isHorizontal()) { + /* If the layout is horizontal, pick the first open parenthesis. For now, + * all horizontal layouts in MathToolbox have parentheses. */ + for (LayoutRef l : resultLayoutRef.directChildren()) { + if (l.isLeftParenthesis()) { + pointedLayoutRef = l; + break; + } + } + } + /* Insert the layout. If pointedLayout is nullptr, the cursor will be on the + * right of the inserted layout. */ + insertLayoutAtCursor(resultLayoutRef, pointedLayoutRef, forceCursorRightOfText); + } + return true; +} + +bool LayoutField::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 LayoutField::privateHandleEvent(Ion::Events::Event event) { + if (m_delegate && m_delegate->layoutFieldDidReceiveEvent(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() && m_delegate->layoutFieldShouldFinishEditing(this, event)) { //TODO use class method? + setEditing(false); + if (m_delegate->layoutFieldDidFinishEditing(this, layoutRef(), event)) { + clearLayout(); + } + return true; + } + if ((event == Ion::Events::OK || event == Ion::Events::EXE) && !isEditing()) { + setEditing(true); + m_contentView.cursor()->setPLayoutReference(layoutRef()); + m_contentView.cursor()->setPosition(LayoutCursor::Position::Right); + return true; + } + if (event == Ion::Events::Back && isEditing()) { + clearLayout(); + setEditing(false); + m_delegate->layoutFieldDidAbortEditing(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; +} + +bool LayoutField::privateHandleMoveEvent(Ion::Events::Event event, bool * shouldRecomputeLayout) { + LayoutCursor 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()->layoutReference()->removeGreySquaresFromAllMatrixAncestors()) { + *shouldRecomputeLayout = true; + } + result.setPointedLayoutRef(layoutRef()); + result.setPosition(LayoutCursor::Position::Left); + } else if (event == Ion::Events::ShiftRight) { + if (m_contentView.cursor()->layoutReference()->removeGreySquaresFromAllMatrixAncestors()) { + *shouldRecomputeLayout = true; + } + result.setPointedLayoutRef(layoutRef()); + result.setPosition(LayoutCursor::Position::Right); + } + if (result.isDefined()) { + m_contentView.setCursor(result); + return true; + } + return false; +} + +void LayoutField::scrollRightOfLayout(LayoutRef layoutR) { + KDRect layoutRect(layout.absoluteOrigin().translatedBy(m_contentView.expressionView()->drawingOrigin()), layout.size()); + scrollToBaselinedRect(layoutRect, layout.baseline()); +} + +void LayoutField::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 LayoutField::insertLayoutAtCursor(LayoutRef layoutR, LayoutRef pointedLayoutR, bool forceCursorRightOfLayout) { + if (!layoutR.isDefined()) { + return; + } + m_contentView.cursor()->showEmptyLayoutIfNeeded(); + bool layoutWillBeMerged = layoutR->isHorizontal(); + LayoutRef lastMergedLayoutChild = layoutWillBeMerged ? layout.child(layout.numberOfChildren()-1) : nullptr; + m_contentView.cursor()->addLayoutAndMoveCursor(layout); + if (!forceCursorRightOfLayout) { + if (pointedLayoutR.isDefined() && (pointedLayout != layout || !layoutWillBeMerged)) { + m_contentView.cursor()->setPointedLayoutRef(pointedLayoutR); + m_contentView.cursor()->setPosition(LayoutCursor::Position::Right); + } else if (!layoutWillBeMerged) { + m_contentView.cursor()->setPointedLayoutRef(layout.layoutToPointWhenInserting()); + m_contentView.cursor()->setPosition(LayoutCursor::Position::Right); + } + } else if (!layoutWillBeMerged) { + m_contentView.cursor()->setPointedLayoutRef(layout); + m_contentView.cursor()->setPosition(LayoutCursor::Position::Right); + } + m_contentView.cursor()->layoutReference().addGreySquaresToAllMatrixAncestors(); + m_contentView.cursor()->hideEmptyLayoutIfNeeded(); + reload(); + if (!layoutWillBeMerged) { + scrollRightOfLayout(layout); + } else { + assert(lastMergedLayoutChild != nullptr); + scrollRightOfLayout(lastMergedLayoutChild); + } + scrollToCursor(); +} diff --git a/poincare/include/poincare/layout_cursor.h b/poincare/include/poincare/layout_cursor.h index 542f71849..87512e658 100644 --- a/poincare/include/poincare/layout_cursor.h +++ b/poincare/include/poincare/layout_cursor.h @@ -11,11 +11,18 @@ class LayoutCursor { template friend class LayoutReference; public: + constexpr static KDCoordinate k_cursorWidth = 1; + enum class Position { Left, Right }; + LayoutCursor(LayoutRef layoutR, Position position = Position::Right) : + m_layoutRef(layoutR.node()), + m_position(position) + {} + /* Debug */ void log() { #if TREE_LOG @@ -56,6 +63,21 @@ public: void moveRight(bool * shouldRecomputeLayout); void moveAbove(bool * shouldRecomputeLayout); void moveUnder(bool * shouldRecomputeLayout); + + /* Layout modification */ + void clearLayout() {} //TODO + void addFractionLayoutAndCollapseSiblings() {} //TODO + void addEmptyExponentialLayout() {} //TODO + void addEmptyPowerLayout() {} //TODO + void addEmptySquareRootLayout() {} //TODO + void addEmptySquarePowerLayout() {} //TODO + void addEmptyTenPowerLayout() {} //TODO + void addEmptyMatrixLayout() {} //TODO + void insertText(const char * text) {} //TODO + void performBackspace() {} //TODO + bool showEmptyLayoutIfNeeded() { return false; } //TODO + bool hideEmptyLayoutIfNeeded() { return false; } //TODO + private: LayoutCursor(LayoutNode * node, Position position = Position::Right) : m_layoutRef(node), diff --git a/poincare/include/poincare/layout_engine.h b/poincare/include/poincare/layout_engine.h index cc6465e66..1256621cc 100644 --- a/poincare/include/poincare/layout_engine.h +++ b/poincare/include/poincare/layout_engine.h @@ -2,6 +2,7 @@ #define POINCARE_LAYOUT_ENGINE_H #include +#include namespace Poincare { @@ -51,6 +52,16 @@ public: const char * operatorName, bool writeFirstChild = true); + /* LayoutReference to Text */ + static int writeInfixTreeRefTextInBuffer( + const TreeRef treeRef, + char * buffer, + int bufferSize, + int numberOfDigits, + const char * operatorName, + int firstChildIndex = 0, + int lastChildIndex = -1); + /* Write one char in buffer */ static int writeOneCharInBuffer(char * buffer, int bufferSize, char charToWrite); @@ -59,6 +70,8 @@ private: // These two functions return the index of the null-terminating char. static int writeInfixExpressionOrExpressionLayoutTextInBuffer(const Expression * expression, const ExpressionLayout * expressionLayout, char * buffer, int bufferSize, int numberOfDigits, const char * operatorName, int firstChildIndex, int lastChildIndex, ChildNeedsParenthesis childNeedsParenthesis); static int writePrefixExpressionOrExpressionLayoutTextInBuffer(const Expression * expression, const ExpressionLayout * expressionLayout, char * buffer, int bufferSize, int numberOfDigits, const char * operatorName, bool writeFirstChild = true); + + static void writeChildTreeInBuffer(TreeRef childRef, TreeRef parentRef, char * buffer, int bufferSize, int numberOfDigits, int * numberOfChar); }; } diff --git a/poincare/include/poincare/layout_node.h b/poincare/include/poincare/layout_node.h index 990a4c135..132d5831d 100644 --- a/poincare/include/poincare/layout_node.h +++ b/poincare/include/poincare/layout_node.h @@ -1,7 +1,6 @@ #ifndef POINCARE_LAYOUT_NODE_H #define POINCARE_LAYOUT_NODE_H -#include #include #include @@ -23,6 +22,8 @@ public: { } + virtual char XNTChar() const { return 'x'; } + // Rendering void draw(KDContext * ctx, KDPoint p, KDColor expressionColor = KDColorBlack, KDColor backgroundColor = KDColorWhite); KDPoint origin(); @@ -31,9 +32,6 @@ public: KDCoordinate baseline(); virtual void invalidAllSizesPositionsAndBaselines(); - // Serialization - virtual int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const = 0; - // TreeNode static TreeNode * FailedAllocationStaticNode(); TreeNode * failedAllocationStaticNode() override { return FailedAllocationStaticNode(); } @@ -46,15 +44,14 @@ public: // Hierarchy LayoutNode * parent() const { return static_cast(parentTree()); } + LayoutNode * childAtIndex(int i) { return static_cast(childTreeAtIndex(i)); } // Tree navigation virtual void moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) {} virtual void moveCursorRight(LayoutCursor * cursor, bool * shouldRecomputeLayout) {} virtual void moveCursorUp(LayoutCursor * cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited = false) {} virtual void moveCursorDown(LayoutCursor * cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited = false) {} - - - LayoutNode * childAtIndex(int i) { return static_cast(childTreeAtIndex(i)); } + virtual LayoutCursor equivalentCursor(LayoutCursor * cursor); //TODO protected: // Iterators diff --git a/poincare/include/poincare/layout_reference.h b/poincare/include/poincare/layout_reference.h index a3da269a1..389b4c5d3 100644 --- a/poincare/include/poincare/layout_reference.h +++ b/poincare/include/poincare/layout_reference.h @@ -33,13 +33,15 @@ public: TreeReference::replaceChildAtIndex(oldChildIndex, newChild); } - KDPoint layoutOrigin() { - return this->typedNode()->layoutOrigin(); - } + bool hasText() { return this->typedNode()->hasText(); } + char XNTChar() const { return this->typedNode()->XNTChar(); } + KDSize layoutSize() { return this->typedNode()->layoutSize(); } + KDPoint layoutOrigin() { return this->typedNode()->layoutOrigin(); } + KDPoint absoluteOrigin() { return this->typedNode()->absoluteOrigin(); } + KDCoordinate baseline() { return this->typedNode()->baseline(); } + LayoutCursor equivalentCursor(LayoutCursor * cursor); + void invalidAllSizesPositionsAndBaselines() { return this->typedNode()->invalidAllSizesPositionsAndBaselines(); } - KDPoint absoluteOrigin() { - return this->typedNode()->absoluteOrigin(); - } }; typedef LayoutReference LayoutRef; diff --git a/poincare/include/poincare/tree_node.h b/poincare/include/poincare/tree_node.h index 497692629..5ffec60c8 100644 --- a/poincare/include/poincare/tree_node.h +++ b/poincare/include/poincare/tree_node.h @@ -1,6 +1,8 @@ #ifndef POINCARE_TREE_NODE_H #define POINCARE_TREE_NODE_H +#include + #include #include #include @@ -36,6 +38,12 @@ public: virtual const char * description() const { return "UNKNOWN"; } + + // Serialization + virtual bool needsParenthesisWithParent(TreeNode * parentNode) { return false; } //TODO virtual pure and override on expresionNode/layoutNode + virtual int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const { return 0; } //TODO virtual pure + + // Allocation failure virtual bool isAllocationFailure() const { return false; } virtual TreeNode * failedAllocationStaticNode() { assert(false); diff --git a/poincare/include/poincare/tree_reference.h b/poincare/include/poincare/tree_reference.h index 9d7556fa4..b8340fad4 100644 --- a/poincare/include/poincare/tree_reference.h +++ b/poincare/include/poincare/tree_reference.h @@ -6,17 +6,11 @@ namespace Poincare { -static inline int min(int i, int j) { return i < j ? i : j; } -static inline int max(int i, int j) { return i > j ? i : j; } - -class Cursor; - template class TreeReference { friend class TreeNode; friend class AdditionNode; friend class ExpressionNode; - friend class Cursor; template friend class TreeReference; template @@ -37,10 +31,6 @@ public: inline bool operator==(TreeReference t) { return m_identifier == t.identifier(); } - void setTo(const TreeReference & tr) { - setIdentifierAndRetain(tr.identifier()); - } - TreeReference clone() const { TreeNode * myNode = node(); if (myNode->isAllocationFailure()) { @@ -59,48 +49,40 @@ public: node()->release(); } } + operator TreeReference() const { return TreeReference(this->node()); } - bool isDefined() const { return m_identifier >= 0 && node() != nullptr; } - bool isAllocationFailure() const { return node()->isAllocationFailure(); } - - int nodeRetainCount() const { return node()->retainCount(); } - void incrementNumberOfChildren(int increment = 1) { return node()->incrementNumberOfChildren(increment); } - void decrementNumberOfChildren(int decrement = 1) { return node()->decrementNumberOfChildren(decrement); } - - operator TreeReference() const { - return TreeReference(this->node()); - } - + int identifier() const { return m_identifier; } + TreeNode * node() const { return TreePool::sharedPool()->node(m_identifier); } T * typedNode() const { // TODO: Here, assert that the node type is indeed T // ?? Might be allocation failure, not T return static_cast(node()); } - TreeNode * node() const { - return TreePool::sharedPool()->node(m_identifier); - } + bool isDefined() const { return m_identifier >= 0 && node() != nullptr; } //TODO m_identifier != -1 + bool isAllocationFailure() const { return node()->isAllocationFailure(); } - int identifier() const { return m_identifier; } + int nodeRetainCount() const { return node()->retainCount(); } + void incrementNumberOfChildren(int increment = 1) { return node()->incrementNumberOfChildren(increment); } + void decrementNumberOfChildren(int decrement = 1) { return node()->decrementNumberOfChildren(decrement); } + + + + // Serialization + bool needsParenthesisWithParent(TreeReference parentRef) { return node()->needsParenthesisWithParent(parentRef.node()); } + int writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits = PrintFloat::k_numberOfStoredSignificantDigits) const { + return node()->writeTextInBuffer(buffer, bufferSize, numberOfSignificantDigits); + } // Hierarchy - int numberOfChildren() const { - return node()->numberOfChildren(); - } - - TreeReference parent() const { - return TreeReference(node()->parentTree()); - } - - TreeReference treeChildAtIndex(int i) const { - return TreeReference(node()->childTreeAtIndex(i)); - } + bool hasChild(TreeReference t) const { return node()->hasChild(t.node()); }; + int numberOfChildren() const { return node()->numberOfChildren(); } + TreeReference parent() const { return TreeReference(node()->parentTree()); } + TreeReference treeChildAtIndex(int i) const { return TreeReference(node()->childTreeAtIndex(i)); } // Hierarchy operations - void addChild(TreeReference t) { - return addChildAtIndex(t, 0); - } + void addChild(TreeReference t) { return addChildAtIndex(t, 0); } void addChildAtIndex(TreeReference t, int index) { if (node()->isAllocationFailure()) { @@ -202,8 +184,8 @@ public: if (i == j) { return; } - int firstChildIndex = min(i, j); - int secondChildIndex = max(i, j); + int firstChildIndex = i < j ? i : j; + int secondChildIndex = i > j ? i : j; TreeReference firstChild = treeChildAtIndex(firstChildIndex); TreeReference secondChild = treeChildAtIndex(secondChildIndex); TreeNode * firstChildNode = firstChild.node(); @@ -242,6 +224,9 @@ protected: node()->retain(); } private: + void setTo(const TreeReference & tr) { + setIdentifierAndRetain(tr.identifier()); + } int m_identifier; }; diff --git a/poincare/src/horizontal_layout_node.cpp b/poincare/src/horizontal_layout_node.cpp index 15d1503fe..4268bc074 100644 --- a/poincare/src/horizontal_layout_node.cpp +++ b/poincare/src/horizontal_layout_node.cpp @@ -3,7 +3,7 @@ namespace Poincare { -static inline KDCoordinate max(KDCoordinate c1, KDCoordinate c2) { return c1 > c2 ? c1 : c2; } +static inline KDCoordinate maxCoordinate(KDCoordinate c1, KDCoordinate c2) { return c1 > c2 ? c1 : c2; } int HorizontalLayoutNode::writeTextInBuffer(char * buffer, int bufferSize, int numberOfSignificantDigits) const { if (numberOfChildren() == 0) { @@ -13,7 +13,7 @@ int HorizontalLayoutNode::writeTextInBuffer(char * buffer, int bufferSize, int n buffer[0] = 0; return 0; } - return LayoutEngine::writeInfixExpressionLayoutTextInBuffer(this, buffer, bufferSize, numberOfSignificantDigits, ""); + return LayoutEngine::writeInfixTreeRefTextInBuffer(TreeRef(const_cast(this)), buffer, bufferSize, numberOfSignificantDigits, ""); } void HorizontalLayoutNode::moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) { @@ -64,16 +64,16 @@ void HorizontalLayoutNode::moveCursorRight(LayoutCursor * cursor, bool * shouldR } } -KDSize HorizontalLayoutNode::computeSize() { +void HorizontalLayoutNode::computeSize() { assert(!m_sized); KDCoordinate totalWidth = 0; KDCoordinate maxUnderBaseline = 0; KDCoordinate maxAboveBaseline = 0; - for (LayoutNode * l : directChildren()) { - KDSize childSize = l->size(); + for (LayoutNode * l : children()) { + KDSize childSize = l->layoutSize(); totalWidth += childSize.width(); - maxUnderBaseline = max(maxUnderBaseline, childSize.height() - l->baseline()); - maxAboveBaseline = max(maxAboveBaseline, l->baseline()); + maxUnderBaseline = maxCoordinate(maxUnderBaseline, childSize.height() - l->baseline()); + maxAboveBaseline = maxCoordinate(maxAboveBaseline, l->baseline()); } m_frame.setSize(KDSize(totalWidth, maxUnderBaseline + maxAboveBaseline)); m_sized = true; @@ -82,8 +82,8 @@ KDSize HorizontalLayoutNode::computeSize() { void HorizontalLayoutNode::computeBaseline() { assert(!m_baselined); m_baseline = 0; - for (LayoutNode * l : directChildren()) { - m_baseline = max(m_baseline, l->baseline()); + for (LayoutNode * l : children()) { + m_baseline = maxCoordinate(m_baseline, l->baseline()); } m_baselined = true; } @@ -94,8 +94,8 @@ KDPoint HorizontalLayoutNode::positionOfChild(LayoutNode * l) { int index = indexOfChild(l); assert(index > -1); if (index > 0) { - LayoutNode * previousChild = child(index-1); - x = previousChild->origin().x() + previousChild->size().width(); + LayoutNode * previousChild = childAtIndex(index-1); + x = previousChild->origin().x() + previousChild->layoutSize().width(); } KDCoordinate y = baseline() - l->baseline(); return KDPoint(x, y); diff --git a/poincare/src/layout_engine.cpp b/poincare/src/layout_engine.cpp index 15f7c9d61..8a82ba026 100644 --- a/poincare/src/layout_engine.cpp +++ b/poincare/src/layout_engine.cpp @@ -186,6 +186,76 @@ int LayoutEngine::writePrefixExpressionOrExpressionLayoutTextInBuffer(const Expr return numberOfChar; } +/* LayoutReference to Text */ +int LayoutEngine::writeInfixTreeRefTextInBuffer( + const TreeRef treeRef, + char * buffer, + int bufferSize, + int numberOfDigits, + const char * operatorName, + int firstChildIndex, + int lastChildIndex) +{ + // If buffer has size 0 or 1, put a zero if it fits and return + assert(treeRef.isDefined()); + if (bufferSize == 0) { + return -1; + } + + buffer[bufferSize-1] = 0; // Null-terminate the buffer + if (bufferSize == 1) { + return 0; + } + + // Get some information on the TreeRef + int numberOfChar = 0; + int numberOfOperands = treeRef.numberOfChildren(); + assert(numberOfOperands > 0); + + // Write the first child, with parentheses if needed + writeChildTreeInBuffer(treeRef.treeChildAtIndex(firstChildIndex), treeRef, buffer, bufferSize, numberOfDigits, &numberOfChar); + if (numberOfChar >= bufferSize-1) { + return bufferSize-1; + } + // For all remaining children: + int lastIndex = lastChildIndex < 0 ? numberOfOperands - 1 : lastChildIndex; + for (int i = firstChildIndex + 1; i < lastIndex+1; i++) { + // Write the operator + numberOfChar += strlcpy(buffer+numberOfChar, operatorName, bufferSize-numberOfChar); + if (numberOfChar >= bufferSize-1) { + return bufferSize-1; + } + // Write the child, with parentheses if needed + writeChildTreeInBuffer(treeRef.treeChildAtIndex(i), treeRef, buffer, bufferSize, numberOfDigits, &numberOfChar); + if (numberOfChar >= bufferSize-1) { + return bufferSize-1; + } + } + + // Null-terminate the buffer + buffer[numberOfChar] = 0; + return numberOfChar; +} + +void LayoutEngine::writeChildTreeInBuffer(TreeRef childRef, TreeRef parentRef, char * buffer, int bufferSize, int numberOfDigits, int * numberOfChar) { + // Write the child with parentheses if needed + bool addParentheses = childRef.needsParenthesisWithParent(parentRef); + if (addParentheses) { + buffer[*numberOfChar++] = '('; //TODO ok ? + if (*numberOfChar >= bufferSize-1) { + return; + } + } + *numberOfChar += childRef.writeTextInBuffer(buffer + *numberOfChar, bufferSize - *numberOfChar, numberOfDigits); + if (*numberOfChar >= bufferSize-1) { + return; + } + if (addParentheses) { + buffer[*numberOfChar++] = ')'; + } +} + + int LayoutEngine::writeOneCharInBuffer(char * buffer, int bufferSize, char charToWrite) { if (bufferSize == 0) { return -1; diff --git a/poincare/src/layout_node.cpp b/poincare/src/layout_node.cpp index ff3aace35..2e607c4c4 100644 --- a/poincare/src/layout_node.cpp +++ b/poincare/src/layout_node.cpp @@ -1,5 +1,6 @@ #include #include +#include #include namespace Poincare { @@ -65,4 +66,9 @@ TreeNode * LayoutNode::FailedAllocationStaticNode() { return LayoutRef::FailedAllocationStaticNode(); } +// Tree navigation +LayoutCursor LayoutNode::equivalentCursor(LayoutCursor * cursor) { + return LayoutCursor(cursor->layoutReference()); +} + } diff --git a/poincare/src/layout_reference.cpp b/poincare/src/layout_reference.cpp index eb04ed627..d701e7a41 100644 --- a/poincare/src/layout_reference.cpp +++ b/poincare/src/layout_reference.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include namespace Poincare { @@ -21,6 +22,11 @@ LayoutCursor LayoutReference::cursor() const { return LayoutCursor(this->typedNode()); } +template<> +LayoutCursor LayoutRef::equivalentCursor(LayoutCursor * cursor) { + return this->typedNode()->equivalentCursor(cursor); +} + template LayoutCursor LayoutReference::cursor() const; template LayoutCursor LayoutReference::cursor() const;