#include "matrix_layout.h" #include "empty_layout.h" #include "bracket_pair_layout.h" #include #include extern "C" { #include #include } namespace Poincare { ExpressionLayout * MatrixLayout::clone() const { MatrixLayout * layout = new MatrixLayout(children(), m_numberOfRows, m_numberOfColumns, true); return layout; } ExpressionLayoutCursor MatrixLayout::cursorLeftOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) { int childIndex = indexOfChild(cursor.pointedExpressionLayout()); if (childIndex >- 1 && cursor.position() == ExpressionLayoutCursor::Position::Left && childIsLeftOfGrid(childIndex)) { /* Case: Left of a child on the left of the grid. * Remove the grey squares of the grid, then go left of the grid. */ assert(hasGreySquares()); removeGreySquares(); *shouldRecomputeLayout = true; return ExpressionLayoutCursor(this, ExpressionLayoutCursor::Position::Left); } /* Case: Right. Add the grey squares to the matrix, then move to the bottom * right non empty nor grey child. */ if (cursor.pointedExpressionLayout() == this && cursor.position() == ExpressionLayoutCursor::Position::Right) { assert(!hasGreySquares()); addGreySquares(); *shouldRecomputeLayout = true; ExpressionLayout * lastChild = editableChild((m_numberOfColumns-1)*(m_numberOfRows-1)); assert(lastChild != nullptr); return ExpressionLayoutCursor(lastChild, ExpressionLayoutCursor::Position::Right); } return GridLayout::cursorLeftOf(cursor, shouldRecomputeLayout); } ExpressionLayoutCursor MatrixLayout::cursorRightOf(ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout) { // Case: Left. Add the grey squares to the matrix, then go to the first entry. if (cursor.pointedExpressionLayout() == this && cursor.position() == ExpressionLayoutCursor::Position::Left) { assert(!hasGreySquares()); addGreySquares(); *shouldRecomputeLayout = true; assert(m_numberOfColumns*m_numberOfRows >= 1); ExpressionLayout * firstChild = editableChild(0); assert(firstChild != nullptr); return ExpressionLayoutCursor(firstChild, ExpressionLayoutCursor::Position::Left); } // Case: The cursor points to a grid's child. int childIndex = indexOfChild(cursor.pointedExpressionLayout()); if (childIndex >- 1 && cursor.position() == ExpressionLayoutCursor::Position::Right && childIsRightOfGrid(childIndex)) { /* Case: Right of a child on the right of the grid. Remove the grey squares * of the grid, then go right of the grid. */ assert(hasGreySquares()); removeGreySquares(); *shouldRecomputeLayout = true; return ExpressionLayoutCursor(this, ExpressionLayoutCursor::Position::Right); } return GridLayout::cursorRightOf(cursor, shouldRecomputeLayout); } ExpressionLayoutCursor MatrixLayout::cursorVerticalOf(VerticalDirection direction, ExpressionLayoutCursor cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited) { bool shouldRemoveGreySquares = false; int firstIndex = direction == VerticalDirection::Up ? 0 : numberOfChildren() - m_numberOfColumns; int lastIndex = direction == VerticalDirection::Up ? m_numberOfColumns : numberOfChildren(); for (int childIndex = firstIndex; childIndex < lastIndex; childIndex++) { if (cursor.pointedExpressionLayout()->hasAncestor(child(childIndex), true)) { // The cursor is leaving the matrix, so remove the grey squares. shouldRemoveGreySquares = true; break; } } ExpressionLayoutCursor resultCursor = GridLayout::cursorVerticalOf(direction, cursor, shouldRecomputeLayout, equivalentPositionVisited); if (resultCursor.isDefined() && shouldRemoveGreySquares) { assert(hasGreySquares()); removeGreySquares(); *shouldRecomputeLayout = true; } return resultCursor; } void MatrixLayout::replaceChild(const ExpressionLayout * oldChild, ExpressionLayout * newChild, bool deleteOldChild) { int oldChildIndex = indexOfChild(oldChild); GridLayout::replaceChild(oldChild, newChild, deleteOldChild); childWasReplacedAtIndex(oldChildIndex); } void MatrixLayout::replaceChildAndMoveCursor(const ExpressionLayout * oldChild, ExpressionLayout * newChild, bool deleteOldChild, ExpressionLayoutCursor * cursor) { int oldChildIndex = indexOfChild(oldChild); int rowIndex = rowAtChildIndex(oldChildIndex); int columnIndex = columnAtChildIndex(oldChildIndex); replaceChild(oldChild, newChild, deleteOldChild); int newIndex = indexAtRowColumn(rowIndex, columnIndex); assert(newIndex < numberOfChildren()); cursor->setPointedExpressionLayout(editableChild(newIndex)); cursor->setPosition(ExpressionLayoutCursor::Position::Right); } void MatrixLayout::removePointedChildAtIndexAndMoveCursor(int index, bool deleteAfterRemoval, ExpressionLayoutCursor * cursor) { assert(index >= 0 && index < numberOfChildren()); assert(cursor->pointedExpressionLayout()->hasAncestor(child(index), true)); replaceChildAndMoveCursor(child(index), new EmptyLayout(), deleteAfterRemoval, cursor); } int MatrixLayout::writeTextInBuffer(char * buffer, int bufferSize) const { // The grid is a matrix. if (bufferSize == 0) { return -1; } buffer[bufferSize-1] = 0; int numberOfChar = 0; if (numberOfChar >= bufferSize-1) { return bufferSize-1;} buffer[numberOfChar++] = '['; if (numberOfChar >= bufferSize-1) { return bufferSize-1;} int maxRowIndex = hasGreySquares() ? m_numberOfRows - 1 : m_numberOfRows; int maxColumnIndex = hasGreySquares() ? m_numberOfColumns - 2 : m_numberOfColumns - 1; for (int i = 0; i < maxRowIndex; i++) { buffer[numberOfChar++] = '['; if (numberOfChar >= bufferSize-1) { return bufferSize-1;} numberOfChar += LayoutEngine::writeInfixExpressionLayoutTextInBuffer(this, buffer+numberOfChar, bufferSize-numberOfChar, ",", i*m_numberOfColumns, i* m_numberOfColumns + maxColumnIndex); if (numberOfChar >= bufferSize-1) { return bufferSize-1; } buffer[numberOfChar++] = ']'; if (numberOfChar >= bufferSize-1) { return bufferSize-1; } } buffer[numberOfChar++] = ']'; buffer[numberOfChar] = 0; return numberOfChar; } 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 boolean before modifying the layout if (GridLayout::childIsRightOfGrid(index)) { // Color the grey EmptyLayouts of the column in yellow. int correspondingColumn = m_numberOfColumns - 1; for (int i = 0; i < m_numberOfRows - 1; i++) { ExpressionLayout * lastLayoutOfRow = editableChild(i*m_numberOfColumns+correspondingColumn); if (lastLayoutOfRow->isEmpty()) { static_cast(lastLayoutOfRow)->setColor(EmptyLayout::Color::Yellow); } } // Add a column of grey EmptyLayouts on the right. addEmptyColumn(EmptyLayout::Color::Grey); } if (shouldAddNewRow) { // Color the grey EmptyLayouts of the row in yellow. for (int i = 0; i < m_numberOfColumns - 1; i++) { ExpressionLayout * lastLayoutOfColumn = editableChild(correspondingRow*m_numberOfColumns+i); if (lastLayoutOfColumn->isEmpty()) { static_cast(lastLayoutOfColumn)->setColor(EmptyLayout::Color::Yellow); } } // Add a row of grey EmptyLayouts at the bottom. addEmptyRow(EmptyLayout::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(EmptyLayout::Color::Grey); } } } void MatrixLayout::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { const ExpressionLayout * dummyGridLayout = new GridLayout(children(), m_numberOfRows, m_numberOfColumns, true); BracketPairLayout dummyLayout(dummyGridLayout, false); dummyLayout.render(ctx, p, expressionColor, backgroundColor); } KDSize MatrixLayout::computeSize() { const ExpressionLayout * dummyGridLayout = new GridLayout(children(), m_numberOfRows, m_numberOfColumns, true); BracketPairLayout dummyLayout(dummyGridLayout, false); return dummyLayout.size(); } KDPoint MatrixLayout::positionOfChild(ExpressionLayout * child) { assert(indexOfChild(child) > -1); ExpressionLayout * dummyGridLayout = new GridLayout(children(), m_numberOfRows, m_numberOfColumns, true); BracketPairLayout dummyLayout(dummyGridLayout, false); 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; } void MatrixLayout::addGreySquares() { if (!hasGreySquares()) { addEmptyRow(EmptyLayout::Color::Grey); addEmptyColumn(EmptyLayout::Color::Grey); } } void MatrixLayout::removeGreySquares() { if (hasGreySquares()) { deleteRowAtIndex(m_numberOfRows - 1); deleteColumnAtIndex(m_numberOfColumns - 1); } } bool MatrixLayout::hasGreySquares() const { assert(m_numberOfRows*m_numberOfColumns - 1 >= 0); const ExpressionLayout * lastChild = child(m_numberOfRows * m_numberOfColumns - 1); if (lastChild->isEmpty() && !lastChild->isHorizontal() && (static_cast(lastChild))->color() == EmptyLayout::Color::Grey) { assert(isRowEmpty(m_numberOfRows - 1)); assert(isColumnEmpty(m_numberOfColumns - 1)); return true; } return false; } }