diff --git a/apps/expression_editor/controller.cpp b/apps/expression_editor/controller.cpp index fdf9702cd..f19771346 100644 --- a/apps/expression_editor/controller.cpp +++ b/apps/expression_editor/controller.cpp @@ -12,9 +12,8 @@ Controller::Controller(Responder * parentResponder, ExpressionLayout * expressio m_view(parentResponder, expressionLayout, &m_cursor), m_expressionLayout(expressionLayout), m_resultLayout(nullptr) - //m_context((GlobalContext *)((AppsContainer *)(app()->container()))->globalContext()) { - m_cursor.setPointedExpressionLayout(expressionLayout->editableChild(0)); + m_cursor.setPointedExpressionLayout(expressionLayout); } void Controller::didBecomeFirstResponder() { diff --git a/apps/expression_editor/expression_and_layout.cpp b/apps/expression_editor/expression_and_layout.cpp index 36f09fcfb..24e0f3df4 100644 --- a/apps/expression_editor/expression_and_layout.cpp +++ b/apps/expression_editor/expression_and_layout.cpp @@ -39,7 +39,6 @@ ExpressionAndLayout::ExpressionAndLayout() { m_expression = Poincare::Expression::parse(expression); m_expressionLayout = new Poincare::HorizontalLayout(); - m_expressionLayout->addChildAtIndex(new Poincare::EmptyVisibleLayout(), 0); } ExpressionAndLayout::~ExpressionAndLayout() { if (m_expressionLayout) { diff --git a/poincare/include/poincare/dynamic_layout_hierarchy.h b/poincare/include/poincare/dynamic_layout_hierarchy.h index a40a7fd13..4157d15fe 100644 --- a/poincare/include/poincare/dynamic_layout_hierarchy.h +++ b/poincare/include/poincare/dynamic_layout_hierarchy.h @@ -28,6 +28,7 @@ public: void addChildrenAtIndex(const ExpressionLayout * const * operands, int numberOfOperands, int indexForInsertion, bool removeEmptyChildren); bool addChildAtIndex(ExpressionLayout * operand, int index) override; void removeChildAtIndex(int index, bool deleteAfterRemoval) override; + void removePointedChildAtIndexAndMoveCursor(int index, bool deleteAfterRemoval, ExpressionLayoutCursor * cursor) override; void mergeChildrenAtIndex(DynamicLayoutHierarchy * eL, int index, bool removeEmptyChildren); // WITHOUT delete. bool isEmpty() const override; diff --git a/poincare/include/poincare/expression_layout.h b/poincare/include/poincare/expression_layout.h index 691f0f768..715ad8fbe 100644 --- a/poincare/include/poincare/expression_layout.h +++ b/poincare/include/poincare/expression_layout.h @@ -55,6 +55,7 @@ public: void detachChildren(); //Removes all children WITHOUT deleting them virtual bool addChildAtIndex(ExpressionLayout * child, int index) { return false; } virtual void removeChildAtIndex(int index, bool deleteAfterRemoval); + virtual void removePointedChildAtIndexAndMoveCursor(int index, bool deleteAfterRemoval, ExpressionLayoutCursor * cursor); virtual void backspaceAtCursor(ExpressionLayoutCursor * cursor); /* Tree navigation */ @@ -78,6 +79,7 @@ public: virtual bool isLeftBracket() const { return false; } virtual bool isRightBracket() const { return false; } virtual bool isEmpty() const { return false; } + virtual bool isMatrix() const { return false; } virtual char XNTChar() const; protected: diff --git a/poincare/src/layout/bracket_left_right_layout.h b/poincare/src/layout/bracket_left_right_layout.h index 4af7a09a3..98cf4e875 100644 --- a/poincare/src/layout/bracket_left_right_layout.h +++ b/poincare/src/layout/bracket_left_right_layout.h @@ -11,11 +11,11 @@ public: void invalidAllSizesPositionsAndBaselines() override; bool moveLeft(ExpressionLayoutCursor * cursor) override; bool moveRight(ExpressionLayoutCursor * cursor) override; +protected: constexpr static KDCoordinate k_bracketWidth = 5; constexpr static KDCoordinate k_lineThickness = 1; constexpr static KDCoordinate k_widthMargin = 5; constexpr static KDCoordinate k_externWidthMargin = 2; -protected: KDSize computeSize() override; KDCoordinate operandHeight(); virtual void computeOperandHeight() = 0; diff --git a/poincare/src/layout/dynamic_layout_hierarchy.cpp b/poincare/src/layout/dynamic_layout_hierarchy.cpp index ce8c5fa28..7678abd18 100644 --- a/poincare/src/layout/dynamic_layout_hierarchy.cpp +++ b/poincare/src/layout/dynamic_layout_hierarchy.cpp @@ -1,5 +1,6 @@ #include #include "empty_visible_layout.h" +#include extern "C" { #include #include @@ -113,9 +114,37 @@ void DynamicLayoutHierarchy::removeChildAtIndex(int index, bool deleteAfterRemov } } +void DynamicLayoutHierarchy::removePointedChildAtIndexAndMoveCursor(int index, bool deleteAfterRemoval, ExpressionLayoutCursor * cursor) { + assert(index >= 0 && index < numberOfChildren()); + assert(cursor->pointedExpressionLayout() == child(index)); + if (numberOfChildren() == 1) { + if (m_parent) { + if (!deleteAfterRemoval) { + detachChild(editableChild(0)); + } + m_parent->removePointedChildAtIndexAndMoveCursor(m_parent->indexOfChild(this), true, cursor); + return; + } + removeChildAtIndex(index, deleteAfterRemoval); + cursor->setPointedExpressionLayout(this); + cursor->setPosition(ExpressionLayoutCursor::Position::Left); + return; + } + removeChildAtIndex(index, deleteAfterRemoval); + if (index < numberOfChildren()) { + cursor->setPointedExpressionLayout(editableChild(index)); + cursor->setPosition(ExpressionLayoutCursor::Position::Left); + return; + } + int indexOfNewPointedLayout = index - 1; + assert(indexOfNewPointedLayout >= 0); + assert(indexOfNewPointedLayout < numberOfChildren()); + cursor->setPointedExpressionLayout(editableChild(indexOfNewPointedLayout)); +} + bool DynamicLayoutHierarchy::isEmpty() const { if (m_numberOfChildren == 0 - || (m_numberOfChildren == 1&& child(0)->isEmpty())) + || (m_numberOfChildren == 1 && child(0)->isEmpty())) { return true; } diff --git a/poincare/src/layout/expression_layout.cpp b/poincare/src/layout/expression_layout.cpp index 8587ea8e6..f79a62e0e 100644 --- a/poincare/src/layout/expression_layout.cpp +++ b/poincare/src/layout/expression_layout.cpp @@ -1,6 +1,7 @@ #include #include "empty_visible_layout.h" #include "horizontal_layout.h" +#include "matrix_layout.h" #include #include #include @@ -230,10 +231,26 @@ void ExpressionLayout::removeChildAtIndex(int index, bool deleteAfterRemoval) { replaceChild(editableChild(index), new EmptyVisibleLayout(), deleteAfterRemoval); } -void ExpressionLayout::backspaceAtCursor(ExpressionLayoutCursor * cursor) { +void ExpressionLayout::removePointedChildAtIndexAndMoveCursor(int index, bool deleteAfterRemoval, ExpressionLayoutCursor * cursor) { + assert(index >= 0 && index < numberOfChildren()); + assert(cursor->pointedExpressionLayout() == child(index)); + removeChildAtIndex(index, deleteAfterRemoval); + if (index < numberOfChildren()) { + cursor->setPointedExpressionLayout(editableChild(index)); + cursor->setPosition(ExpressionLayoutCursor::Position::Left); + return; + } + int indexOfNewPointedLayout = index - 1; + assert(indexOfNewPointedLayout >= 0); + assert(indexOfNewPointedLayout < numberOfChildren()); + cursor->setPointedExpressionLayout(editableChild(indexOfNewPointedLayout)); +} +void ExpressionLayout::backspaceAtCursor(ExpressionLayoutCursor * cursor) { int indexOfPointedExpression = indexOfChild(cursor->pointedExpressionLayout()); if (indexOfPointedExpression >= 0) { + // Case: The pointed layout is a child. + // Point to the previous child, else to this. assert(cursor->position() == ExpressionLayoutCursor::Position::Left); if (indexOfPointedExpression == 0) { cursor->setPointedExpressionLayout(this); @@ -245,14 +262,20 @@ void ExpressionLayout::backspaceAtCursor(ExpressionLayoutCursor * cursor) { return; } assert(cursor->pointedExpressionLayout() == this); + // Case: this is the pointed layout. if (m_parent == nullptr) { + // If there is no parent, return. return; } if (cursor->position() == ExpressionLayoutCursor::Position::Left) { + // Case: Left. + // Ask the parent. m_parent->backspaceAtCursor(cursor); return; } assert(cursor->position() == ExpressionLayoutCursor::Position::Right); + // Case: Right. + // If the layout has children, move to the last one. if (numberOfChildren() > 0) { cursor->setPointedExpressionLayout(editableChild(numberOfChildren()-1)); cursor->performBackspace(); @@ -260,23 +283,16 @@ void ExpressionLayout::backspaceAtCursor(ExpressionLayoutCursor * cursor) { } int indexInParent = m_parent->indexOfChild(this); ExpressionLayout * previousParent = m_parent; - if (previousParent->numberOfChildren() == 1) { + // Case: Right. + // If the layout has no child and is only child, replace it with an empty layout. + /*if (previousParent->numberOfChildren() == 1) { ExpressionLayout * newLayout = new EmptyVisibleLayout(); replaceWith(newLayout, true); cursor->setPointedExpressionLayout(newLayout); cursor->setPosition(ExpressionLayoutCursor::Position::Left); return; - } - previousParent->removeChildAtIndex(indexInParent, true); - if (indexInParent < previousParent->numberOfChildren()) { - cursor->setPointedExpressionLayout(previousParent->editableChild(indexInParent)); - cursor->setPosition(ExpressionLayoutCursor::Position::Left); - return; - } - int indexOfNewPointedLayout = indexInParent - 1; - assert(indexOfNewPointedLayout >= 0); - assert(indexOfNewPointedLayout < previousParent->numberOfChildren()); - cursor->setPointedExpressionLayout(previousParent->editableChild(indexOfNewPointedLayout)); + }*/ + previousParent->removePointedChildAtIndexAndMoveCursor(indexInParent, true, cursor); } char ExpressionLayout::XNTChar() const { diff --git a/poincare/src/layout/grid_layout.cpp b/poincare/src/layout/grid_layout.cpp index 39790b4d6..831c72f83 100644 --- a/poincare/src/layout/grid_layout.cpp +++ b/poincare/src/layout/grid_layout.cpp @@ -247,6 +247,22 @@ void GridLayout::addEmptyColumn(EmptyVisibleLayout::Color color) { } } +void GridLayout::deleteRowAtIndex(int index) { + assert(index >= 0 && index < m_numberOfRows); + for (int i = 0; i < m_numberOfColumns; i++) { + DynamicLayoutHierarchy::removeChildAtIndex(index * m_numberOfColumns, true); + } + m_numberOfRows--; +} + +void GridLayout::deleteColumnAtIndex(int index) { + assert(index >= 0 && index < m_numberOfColumns); + for (int i = (m_numberOfRows - 1) * m_numberOfColumns + index; i > -1; i-= m_numberOfColumns) { + DynamicLayoutHierarchy::removeChildAtIndex(i, true); + } + m_numberOfColumns--; +} + bool GridLayout::childIsLeftOfGrid(int index) const { assert(index >= 0 && index < m_numberOfRows*m_numberOfColumns); return columnAtChildIndex(index) == 0; diff --git a/poincare/src/layout/grid_layout.h b/poincare/src/layout/grid_layout.h index bee1df01a..dd03e309c 100644 --- a/poincare/src/layout/grid_layout.h +++ b/poincare/src/layout/grid_layout.h @@ -35,6 +35,8 @@ protected: KDPoint positionOfChild(ExpressionLayout * child) override; void addEmptyRow(EmptyVisibleLayout::Color color); void addEmptyColumn(EmptyVisibleLayout::Color color); + void deleteRowAtIndex(int index); + void deleteColumnAtIndex(int index); bool childIsRightOfGrid(int index) const; bool childIsBottomOfGrid(int index) const; int rowAtChildIndex(int index) const; diff --git a/poincare/src/layout/horizontal_layout.cpp b/poincare/src/layout/horizontal_layout.cpp index 62159aa03..2f75b68e8 100644 --- a/poincare/src/layout/horizontal_layout.cpp +++ b/poincare/src/layout/horizontal_layout.cpp @@ -61,7 +61,7 @@ void HorizontalLayout::privateReplaceChild(const ExpressionLayout * oldChild, Ex } // If the new layout is empty and it was the only horizontal layout child, // replace the horizontal layout with this empty layout (only if this is not - // the main layout, so only if the layout has a parent. + // the main layout, so only if the layout has a parent). if (m_parent) { if (cursor) { replaceWithAndMoveCursor(newChild, deleteOldChild, cursor); diff --git a/poincare/src/layout/matrix_layout.cpp b/poincare/src/layout/matrix_layout.cpp index 5ad8a03d5..eee2bcf42 100644 --- a/poincare/src/layout/matrix_layout.cpp +++ b/poincare/src/layout/matrix_layout.cpp @@ -17,6 +17,12 @@ ExpressionLayout * MatrixLayout::clone() const { return layout; } +void MatrixLayout::replaceChild(const ExpressionLayout * oldChild, ExpressionLayout * newChild, bool deleteOldChild) { + int oldChildIndex = indexOfChild(const_cast(oldChild)); + GridLayout::replaceChild(oldChild, newChild, deleteOldChild); + childWasReplacedAtIndex(oldChildIndex); +} + void MatrixLayout::replaceChildAndMoveCursor(const ExpressionLayout * oldChild, ExpressionLayout * newChild, bool deleteOldChild, ExpressionLayoutCursor * cursor) { int oldChildIndex = indexOfChild(const_cast(oldChild)); int rowIndex = rowAtChildIndex(oldChildIndex); @@ -59,6 +65,7 @@ int MatrixLayout::writeTextInBuffer(char * buffer, int bufferSize) const { } void MatrixLayout::newRowOrColumnAtIndex(int index) { + assert(index >= 0 && index < m_numberOfColumns*m_numberOfRows); bool shouldAddNewRow = GridLayout::childIsBottomOfGrid(index); int correspondingRow = rowAtChildIndex(index); // We need to compute this bool before modifying the layout.:w @@ -73,7 +80,7 @@ void MatrixLayout::newRowOrColumnAtIndex(int index) { } } // Add a column of grey EmptyVisibleLayouts on the right. - GridLayout::addEmptyColumn(EmptyVisibleLayout::Color::Grey); + addEmptyColumn(EmptyVisibleLayout::Color::Grey); } if (shouldAddNewRow) { // Color the grey EmptyVisibleLayouts of the row in yellow. @@ -84,7 +91,30 @@ void MatrixLayout::newRowOrColumnAtIndex(int index) { } } // Add a row of grey EmptyVisibleLayouts at the bottom. - GridLayout::addEmptyRow(EmptyVisibleLayout::Color::Grey); + addEmptyRow(EmptyVisibleLayout::Color::Grey); + } +} + +void MatrixLayout::childWasReplacedAtIndex(int index) { + assert(index >= 0 && index < m_numberOfColumns*m_numberOfRows); + int rowIndex = rowAtChildIndex(index); + int columnIndex = columnAtChildIndex(index); + bool rowIsEmpty = isRowEmpty(rowIndex); + bool columnIsEmpty = isColumnEmpty(columnIndex); + if (rowIsEmpty && m_numberOfRows > 2) { + deleteRowAtIndex(rowIndex); + } + if (columnIsEmpty && m_numberOfColumns > 2) { + deleteColumnAtIndex(columnIndex); + } + if (!rowIsEmpty && !columnIsEmpty) { + ExpressionLayout * newChild = editableChild(index); + if (newChild->isEmpty() + && (childIsRightOfGrid(index) + || childIsBottomOfGrid(index))) + { + static_cast(newChild)->setColor(EmptyVisibleLayout::Color::Grey); + } } } @@ -108,6 +138,7 @@ ExpressionLayout * dummyGridLayout = new GridLayout(children(), m_numberOfRows, } KDPoint MatrixLayout::positionOfChild(ExpressionLayout * child) { + assert(indexOfChild(child) > -1); BracketLeftLayout * dummyLeftBracket = new BracketLeftLayout(); BracketRightLayout * dummyRightBracket = new BracketRightLayout(); ExpressionLayout * dummyGridLayout = new GridLayout(children(), m_numberOfRows, m_numberOfColumns, true); @@ -115,4 +146,24 @@ ExpressionLayout * dummyGridLayout = new GridLayout(children(), m_numberOfRows, return GridLayout::positionOfChild(child).translatedBy(dummyLayout.positionOfChild(dummyGridLayout)); } +bool MatrixLayout::isRowEmpty(int index) const { + assert(index >= 0 && index < m_numberOfRows); + for (int i = index * m_numberOfColumns; i < (index+1) * m_numberOfColumns; i++) { + if (!child(i)->isEmpty()) { + return false; + } + } + return true; +} + +bool MatrixLayout::isColumnEmpty(int index) const { + assert(index >= 0 && index < m_numberOfColumns); + for (int i = index; i < m_numberOfRows * m_numberOfColumns; i+= m_numberOfColumns) { + if (!child(i)->isEmpty()) { + return false; + } + } + return true; +} + } diff --git a/poincare/src/layout/matrix_layout.h b/poincare/src/layout/matrix_layout.h index 83008d440..047753ca4 100644 --- a/poincare/src/layout/matrix_layout.h +++ b/poincare/src/layout/matrix_layout.h @@ -9,13 +9,19 @@ class MatrixLayout : public GridLayout { public: using GridLayout::GridLayout; ExpressionLayout * clone() const override; + void replaceChild(const ExpressionLayout * oldChild, ExpressionLayout * newChild, bool deleteOldChild = true) override; void replaceChildAndMoveCursor(const ExpressionLayout * oldChild, ExpressionLayout * newChild, bool deleteOldChild, ExpressionLayoutCursor * cursor) override; int writeTextInBuffer(char * buffer, int bufferSize) const override; + bool isMatrix() const override { return true; } void newRowOrColumnAtIndex(int index); + void childWasReplacedAtIndex(int index); protected: void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override; KDSize computeSize() override; KDPoint positionOfChild(ExpressionLayout * child) override; +private: + bool isRowEmpty(int index) const; + bool isColumnEmpty(int index) const; }; }