LayoutField

This commit is contained in:
Léa Saviot
2018-07-03 13:52:33 +02:00
parent 049cf772e2
commit d0304d4a83
14 changed files with 583 additions and 65 deletions

View File

@@ -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\

View File

@@ -0,0 +1,122 @@
#ifndef ESCHER_LAYOUT_FIELD_H
#define ESCHER_LAYOUT_FIELD_H
#include <escher/expression_view.h>
#include <escher/layout_field_delegate.h>
#include <escher/scrollable_view.h>
#include <escher/text_cursor_view.h>
#include <escher/text_field.h>
#include <kandinsky/point.h>
#include <poincare/layout_reference.h>
#include <poincare/layout_cursor.h>
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

View File

@@ -0,0 +1,19 @@
#ifndef ESCHER_LAYOUT_FIELD_DELEGATE_H
#define ESCHER_LAYOUT_FIELD_DELEGATE_H
#include <escher/toolbox.h>
#include <ion/events.h>
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

266
escher/src/layout_field.cpp Normal file
View File

@@ -0,0 +1,266 @@
#include <escher/layout_field.h>
#include <apps/i18n.h>
#include <escher/clipboard.h>
#include <escher/text_field.h>
#include <poincare/expression.h>
#include <poincare/expression_layout_cursor.h>
//#include <poincare/src/layout/matrix_layout.h> //TODO
#include <assert.h>
#include <string.h>
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();
}

View File

@@ -11,11 +11,18 @@ class LayoutCursor {
template <typename T>
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),

View File

@@ -2,6 +2,7 @@
#define POINCARE_LAYOUT_ENGINE_H
#include <poincare/expression.h>
#include <poincare/tree_reference.h>
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);
};
}

View File

@@ -1,7 +1,6 @@
#ifndef POINCARE_LAYOUT_NODE_H
#define POINCARE_LAYOUT_NODE_H
#include <poincare/print_float.h>
#include <poincare/tree_node.h>
#include <kandinsky.h>
@@ -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<LayoutNode *>(parentTree()); }
LayoutNode * childAtIndex(int i) { return static_cast<LayoutNode *>(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<LayoutNode *>(childTreeAtIndex(i)); }
virtual LayoutCursor equivalentCursor(LayoutCursor * cursor); //TODO
protected:
// Iterators

View File

@@ -33,13 +33,15 @@ public:
TreeReference<T>::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<LayoutNode> LayoutRef;

View File

@@ -1,6 +1,8 @@
#ifndef POINCARE_TREE_NODE_H
#define POINCARE_TREE_NODE_H
#include <poincare/print_float.h>
#include <assert.h>
#include <stddef.h>
#include <strings.h>
@@ -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);

View File

@@ -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 <typename T>
class TreeReference {
friend class TreeNode;
friend class AdditionNode;
friend class ExpressionNode;
friend class Cursor;
template <typename U>
friend class TreeReference;
template <typename U>
@@ -37,10 +31,6 @@ public:
inline bool operator==(TreeReference<TreeNode> t) { return m_identifier == t.identifier(); }
void setTo(const TreeReference & tr) {
setIdentifierAndRetain(tr.identifier());
}
TreeReference<T> clone() const {
TreeNode * myNode = node();
if (myNode->isAllocationFailure()) {
@@ -59,48 +49,40 @@ public:
node()->release();
}
}
operator TreeReference<TreeNode>() const { return TreeReference<TreeNode>(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<TreeNode>() const {
return TreeReference<TreeNode>(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<T*>(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<TreeNode> 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<T> parent() const {
return TreeReference(node()->parentTree());
}
TreeReference<T> treeChildAtIndex(int i) const {
return TreeReference(node()->childTreeAtIndex(i));
}
bool hasChild(TreeReference<TreeNode> t) const { return node()->hasChild(t.node()); };
int numberOfChildren() const { return node()->numberOfChildren(); }
TreeReference<T> parent() const { return TreeReference(node()->parentTree()); }
TreeReference<T> treeChildAtIndex(int i) const { return TreeReference(node()->childTreeAtIndex(i)); }
// Hierarchy operations
void addChild(TreeReference<TreeNode> t) {
return addChildAtIndex(t, 0);
}
void addChild(TreeReference<TreeNode> t) { return addChildAtIndex(t, 0); }
void addChildAtIndex(TreeReference<TreeNode> 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<T> firstChild = treeChildAtIndex(firstChildIndex);
TreeReference<T> 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;
};

View File

@@ -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<HorizontalLayoutNode *>(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);

View File

@@ -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;

View File

@@ -1,5 +1,6 @@
#include <poincare/layout_node.h>
#include <poincare/allocation_failed_layout_node.h>
#include <poincare/layout_cursor.h>
#include <poincare/layout_reference.h>
namespace Poincare {
@@ -65,4 +66,9 @@ TreeNode * LayoutNode::FailedAllocationStaticNode() {
return LayoutRef::FailedAllocationStaticNode();
}
// Tree navigation
LayoutCursor LayoutNode::equivalentCursor(LayoutCursor * cursor) {
return LayoutCursor(cursor->layoutReference());
}
}

View File

@@ -2,6 +2,7 @@
#include <poincare/layout_cursor.h>
#include <poincare/allocation_failed_layout_node.h>
#include <poincare/layout_node.h>
#include <poincare/layout_cursor.h>
#include <poincare/char_layout_node.h>
namespace Poincare {
@@ -21,6 +22,11 @@ LayoutCursor LayoutReference<T>::cursor() const {
return LayoutCursor(this->typedNode());
}
template<>
LayoutCursor LayoutRef::equivalentCursor(LayoutCursor * cursor) {
return this->typedNode()->equivalentCursor(cursor);
}
template LayoutCursor LayoutReference<LayoutNode>::cursor() const;
template LayoutCursor LayoutReference<CharLayoutNode>::cursor() const;