Fixed some conflicts

This commit is contained in:
Quentin Guidée
2020-02-12 17:42:58 +01:00
687 changed files with 13048 additions and 4057 deletions

View File

@@ -4,6 +4,7 @@ escher_src += $(addprefix escher/src/,\
alternate_empty_view_controller.cpp \
app.cpp \
bank_view_controller.cpp \
bordered.cpp \
buffer_text_view.cpp \
button.cpp \
button_row_controller.cpp \
@@ -44,7 +45,6 @@ escher_src += $(addprefix escher/src/,\
message_table_cell_with_message.cpp \
message_table_cell_with_switch.cpp \
message_text_view.cpp \
message_tree.cpp \
modal_view_controller.cpp \
nested_menu_controller.cpp \
palette.cpp \
@@ -54,13 +54,12 @@ escher_src += $(addprefix escher/src/,\
scroll_view.cpp \
scroll_view_data_source.cpp \
scroll_view_indicator.cpp \
scrollable_expression_view.cpp \
scrollable_view.cpp \
selectable_table_view.cpp \
selectable_table_view_delegate.cpp \
simple_list_view_data_source.cpp \
simple_table_view_data_source.cpp \
solid_color_view.cpp \
solid_text_area.cpp \
stack_view.cpp \
stack_view_controller.cpp \
switch_view.cpp \
@@ -87,6 +86,9 @@ escher_src += $(addprefix escher/src/,\
window.cpp \
)
tests_src += $(addprefix escher/test/,\
layout_field.cpp\
)
$(eval $(call rule_for, \
HOSTCC, \

View File

@@ -58,6 +58,7 @@
#include <escher/scroll_view_data_source.h>
#include <escher/scroll_view_indicator.h>
#include <escher/scrollable_view.h>
#include <escher/scrollable_expression_view.h>
#include <escher/selectable_table_view.h>
#include <escher/selectable_table_view_data_source.h>
#include <escher/selectable_table_view_delegate.h>

View File

@@ -21,7 +21,7 @@ private:
ContentView(ViewController * mainViewController, AlternateEmptyViewDelegate * delegate);
ViewController * mainViewController() const;
AlternateEmptyViewDelegate * alternateEmptyViewDelegate() const;
void layoutSubviews() override;
void layoutSubviews(bool force = false) override;
private:
int numberOfSubviews() const override;
View * subviewAtIndex(int index) override;

View File

@@ -9,6 +9,7 @@
#include <escher/view_controller.h>
#include <escher/warning_controller.h>
#include <ion/storage.h>
#include <poincare/context.h>
/* An app is fed events and outputs drawing calls.
*
@@ -25,10 +26,10 @@ class App : public Responder {
public:
class Descriptor {
public:
virtual I18n::Message name();
virtual I18n::Message upperName();
virtual I18n::Message name() { return (I18n::Message)0; }
virtual I18n::Message upperName() { return (I18n::Message)0; }
virtual int examinationLevel();
virtual const Image * icon();
virtual const Image * icon() { return nullptr; }
const int NoExaminationLevel = 0;
const int BasicExaminationLevel = 1;
@@ -39,37 +40,44 @@ public:
virtual App * unpack(Container * container) = 0;
void pack(App * app);
/* reset all instances to their initial values */
virtual void reset();
virtual void reset() {}
virtual void storageDidChangeForRecord(Ion::Storage::Record) {}
virtual Descriptor * descriptor() = 0;
#if EPSILON_GETOPT
virtual void setOpt(const char * name, const char * value) {}
#endif
/* tidy clean all dynamically-allocated data */
virtual void tidy();
virtual void tidy() {}
};
/* The destructor has to be virtual. Otherwise calling a destructor on an
* App * pointing to a Derived App would have undefined behaviour. */
virtual ~App() = default;
Snapshot * snapshot() const { return m_snapshot; }
void setFirstResponder(Responder * responder);
Responder * firstResponder();
Responder * firstResponder() { return m_firstResponder; }
virtual bool processEvent(Ion::Events::Event event);
/* prepareForExit returns true if the app can be switched off in the current
* runloop step, else it prepares for a switch off and returns false. */
virtual bool prepareForExit() { return true; }
void displayModalViewController(ViewController * vc, float verticalAlignment, float horizontalAlignment,
KDCoordinate topMargin = 0, KDCoordinate leftMargin = 0, KDCoordinate bottomMargin = 0, KDCoordinate rightMargin = 0);
void dismissModalViewController();
void dismissModalViewController(bool willExitApp = false);
void displayWarning(I18n::Message warningMessage1, I18n::Message warningMessage2 = (I18n::Message) 0, bool specialExitKeys = false);
virtual void didBecomeActive(Window * window);
virtual void willBecomeInactive();
View * modalView();
virtual int numberOfTimers();
virtual Timer * timerAtIndex(int i);
virtual int numberOfTimers() { return 0; }
virtual Timer * timerAtIndex(int i) { assert(false); return nullptr; }
virtual Poincare::Context * localContext() { return nullptr; }
protected:
App(Snapshot * snapshot, ViewController * rootViewController, I18n::Message warningMessage = (I18n::Message)0);
App(Snapshot * snapshot, ViewController * rootViewController, I18n::Message warningMessage = (I18n::Message)0) :
Responder(nullptr),
m_modalViewController(this, rootViewController),
m_firstResponder(nullptr),
m_snapshot(snapshot),
m_warningController(this, warningMessage)
{}
ModalViewController m_modalViewController;
private:
Responder * m_firstResponder;

View File

@@ -31,8 +31,8 @@ private:
assert(index == 0);
return m_subview;
}
void layoutSubviews() override {
m_subview->setFrame(bounds());
void layoutSubviews(bool force = false) override {
m_subview->setFrame(bounds(), force);
}
View * m_subview;
};

View File

@@ -0,0 +1,16 @@
#ifndef ESCHER_BORDERED_H
#define ESCHER_BORDERED_H
#include <escher/metric.h>
#include <kandinsky/context.h>
class Bordered {
public:
void drawBorderOfRect(KDContext * ctx, KDRect rect, KDColor borderColor) const;
void drawInnerRect(KDContext * ctx, KDRect rect, KDColor backgroundColor) const;
protected:
constexpr static KDCoordinate k_separatorThickness = Metric::CellSeparatorThickness;
};
#endif

View File

@@ -27,7 +27,7 @@ private:
constexpr static KDCoordinate k_horizontalMarginLarge = 20;
int numberOfSubviews() const override;
View * subviewAtIndex(int index) override;
void layoutSubviews() override;
void layoutSubviews(bool force = false) override;
Invocation m_invocation;
const KDFont * m_font;
};

View File

@@ -45,7 +45,7 @@ private:
void reload();
int numberOfSubviews() const override;
View * subviewAtIndex(int index) override;
void layoutSubviews() override;
void layoutSubviews(bool force = false) override;
void drawRect(KDContext * ctx, KDRect rect) const override;
bool setSelectedButton(int selectedButton);
int selectedButton() const { return m_selectedButton; }
@@ -57,8 +57,6 @@ private:
constexpr static KDCoordinate k_embossedStyleHeightLarge = 52;
constexpr static KDCoordinate k_embossedStyleHeightMarginSmall = 6;
constexpr static KDCoordinate k_embossedStyleHeightMarginLarge = 8;
constexpr static KDColor k_separatorHeaderColor = KDColor::RGB24(0xDEE0E2);
constexpr static KDColor k_selectedBackgroundColor = KDColor::RGB24(0x426DA7);
ViewController * m_mainViewController;
int m_selectedButton;
ButtonRowDelegate * m_delegate;

View File

@@ -7,7 +7,7 @@
class Clipboard {
public:
static Clipboard * sharedClipboard();
void store(const char * storedText);
void store(const char * storedText, int length = -1);
const char * storedText() const { return m_textBuffer; }
void reset();
private:

View File

@@ -0,0 +1,11 @@
#ifndef ESCHER_CONTEXT_PROVIDER_H
#define ESCHER_CONTEXT_PROVIDER_H
#include <poincare/context.h>
class ContextProvider {
public:
virtual Poincare::Context * context() const { return nullptr; }
};
#endif

View File

@@ -5,6 +5,11 @@
#include <escher/input_event_handler.h>
#include <ion/unicode/code_point.h>
/* TODO: improve classes hierarchy to share selection handling (and some other
* features) between EditableField and TextInput. Refactor the following classes:
* InputEventHandler, TextInput, TextArea, EditableField, LayoutField,
* TextField, and their delegates! */
class EditableField : public InputEventHandler {
public:
using InputEventHandler::InputEventHandler;

View File

@@ -26,7 +26,7 @@ public:
}
int numberOfSubviews() const override;
View * subviewAtIndex(int index) override;
void layoutSubviews() override;
void layoutSubviews(bool force = false) override;
void didBecomeFirstResponder() override;
KDSize minimalSizeForOptimalDisplay() const override;
private:

View File

@@ -26,7 +26,7 @@ protected:
static constexpr KDCoordinate k_horizontalMargin = Metric::CellMargin;
int numberOfSubviews() const override;
View * subviewAtIndex(int index) override;
void layoutSubviews() override;
void layoutSubviews(bool force = false) override;
BufferTextView m_bufferTextView;
};

View File

@@ -14,7 +14,7 @@ private:
assert(index==0);
return &m_ellipsisView;
}
void layoutSubviews() override;
void layoutSubviews(bool force = false) override;
EllipsisView m_ellipsisView;
};

View File

@@ -21,7 +21,7 @@ public:
private:
int numberOfSubviews() const override;
View * subviewAtIndex(int index) override;
void layoutSubviews() override;
void layoutSubviews(bool force = false) override;
EditableTextCell m_editableCell;
};

View File

@@ -23,7 +23,7 @@ public:
protected:
int numberOfSubviews() const override;
View * subviewAtIndex(int index) override;
void layoutSubviews() override;
void layoutSubviews(bool force = false) override;
ExpressionView m_expressionView;
KDCoordinate m_leftMargin;
KDCoordinate m_rightMargin;

View File

@@ -18,7 +18,7 @@ protected:
constexpr static KDCoordinate k_horizontalMargin = Metric::CellMargin;
int numberOfSubviews() const override;
View * subviewAtIndex(int index) override;
void layoutSubviews() override;
void layoutSubviews(bool force = false) override;
MessageTextView m_messageTextView;
};

View File

@@ -21,30 +21,31 @@ public:
* use text() there... TODO: change text() for fillTextInBuffer?*/
const char * text();
void setText(const char * text);
void reload();
bool editionIsInTextField() const;
bool isEmpty() const;
bool heightIsMaximal() const;
bool inputViewHeightDidChange();
bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false);
/* View */
int numberOfSubviews() const override { return 1; }
View * subviewAtIndex(int index) override;
void layoutSubviews() override;
void layoutSubviews(bool force = false) override;
void drawRect(KDContext * ctx, KDRect rect) const override;
KDSize minimalSizeForOptimalDisplay() const override;
/* Responder */
bool handleEvent(Ion::Events::Event event) override;
void didBecomeFirstResponder() override;
private:
static constexpr int k_textFieldBufferSize = TextField::maxBufferSize();
static constexpr KDCoordinate k_textFieldHeight = 37;
static constexpr KDCoordinate k_minimalHeight = 37;
static constexpr KDCoordinate k_maximalHeight = 0.6*Ion::Display::Height;
static constexpr KDCoordinate k_horizontalMargin = 5;
static constexpr KDCoordinate k_verticalMargin = 5;
constexpr static KDCoordinate k_separatorThickness = Metric::CellSeparatorThickness;
KDCoordinate inputViewHeight() const;
KDCoordinate maximalHeight() const;
KDCoordinate m_inputViewMemoizedHeight;
TextField m_textField;
LayoutField m_layoutField;
};

View File

@@ -1,18 +1,26 @@
#ifndef ESCHER_EXPRESSION_TABLE_CELL_H
#define ESCHER_EXPRESSION_TABLE_CELL_H
#include <escher/expression_view.h>
#include <escher/scrollable_expression_view.h>
#include <escher/table_cell.h>
class ExpressionTableCell : public TableCell {
class ExpressionTableCell : public Responder, public TableCell {
public:
ExpressionTableCell(Layout layout = Layout::Horizontal);
ExpressionTableCell(Responder * responder = nullptr, Layout layout = Layout::HorizontalRightOverlap);
View * labelView() const override;
void setHighlighted(bool highlight) override;
void setLayout(Poincare::Layout layout);
Poincare::Layout layout() const override { return m_labelExpressionView.layout(); }
Responder * responder() override {
return this;
}
void didBecomeFirstResponder() override;
virtual void reloadScroll() { m_labelExpressionView.reloadScroll(); }
private:
ExpressionView m_labelExpressionView;
// Remove margins added by TableCell because they're already handled by ScrollableInputExactApproximateExpressionsView
KDCoordinate labelMargin() const override { return 0; }
ScrollableExpressionView m_labelExpressionView;
};
#endif

View File

@@ -6,12 +6,16 @@
class ExpressionTableCellWithExpression : public ExpressionTableCell {
public:
ExpressionTableCellWithExpression();
ExpressionTableCellWithExpression(Responder * parentResponder = nullptr);
View * accessoryView() const override;
void setHighlighted(bool highlight) override;
void setAccessoryLayout(Poincare::Layout l);
void didBecomeFirstResponder() override;
void reloadScroll() override { m_accessoryExpressionView.reloadScroll(); }
private:
ExpressionView m_accessoryExpressionView;
// Accessory margin is already handled in ScrollableExpressionView
KDCoordinate accessoryMargin() const override { return 0; }
ScrollableExpressionView m_accessoryExpressionView;
};
#endif

View File

@@ -7,7 +7,7 @@
class ExpressionTableCellWithPointer : public ExpressionTableCell {
public:
ExpressionTableCellWithPointer(I18n::Message accessoryMessage = (I18n::Message)0, Layout layout = Layout::Horizontal);
ExpressionTableCellWithPointer(Responder * responder = nullptr, I18n::Message accessoryMessage = (I18n::Message)0, Layout layout = Layout::HorizontalRightOverlap);
View * accessoryView() const override;
void setHighlighted(bool highlight) override;
void setAccessoryMessage(I18n::Message messageBody);

View File

@@ -14,7 +14,7 @@
class ExpressionView : public View {
public:
ExpressionView(float horizontalAlignment = 0.0f, float verticalAlignment = 0.5f,
KDColor textColor = Palette::PrimaryText, KDColor backgroundColor = Palette::ListCellBackground);
KDColor textColor = Palette::PrimaryText, KDColor backgroundColor = Palette::ListCellBackground, Poincare::Layout * selectionStart = nullptr, Poincare::Layout * selectionEnd = nullptr);
Poincare::Layout layout() const { return m_layout; }
bool setLayout(Poincare::Layout layout);
void drawRect(KDContext * ctx, KDRect rect) const override;
@@ -26,17 +26,20 @@ public:
KDSize minimalSizeForOptimalDisplay() const override;
KDPoint drawingOrigin() const;
KDPoint absoluteDrawingOrigin() const;
private:
protected:
/* Warning: we do not need to delete the previous expression layout when
* deleting object or setting a new expression layout. Indeed, the expression
* layout is always possessed by a controller which only gives a pointer to
* the expression view (without cloning it). The named controller is then
* responsible for freeing the expression layout when required. */
mutable Poincare::Layout m_layout; // TODO find better way to have minimalSizeForOptimalDisplay const
float m_horizontalAlignment;
float m_verticalAlignment;
KDColor m_textColor;
KDColor m_backgroundColor;
Poincare::Layout * m_selectionStart;
Poincare::Layout * m_selectionEnd;
private:
float m_horizontalAlignment;
float m_verticalAlignment;
KDCoordinate m_horizontalMargin;
};

View File

@@ -3,6 +3,8 @@
#include <ion/events.h>
// See TODO in EditableField
class InputEventHandlerDelegate;
class InputEventHandler {

View File

@@ -64,7 +64,6 @@ private:
InputEventHandlerDelegate * m_inputEventHandlerDelegate;
TextFieldDelegate * m_textFieldDelegate;
LayoutFieldDelegate * m_layoutFieldDelegate;
bool m_inputViewHeightIsMaximal;
};
#endif

View File

@@ -11,6 +11,8 @@
#include <poincare/layout.h>
#include <poincare/layout_cursor.h>
// See TODO in EditableField
class LayoutField : public ScrollableView, public ScrollViewDataSource, public EditableField {
public:
LayoutField(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, LayoutFieldDelegate * delegate = nullptr) :
@@ -20,15 +22,17 @@ public:
m_delegate(delegate)
{}
void setDelegates(InputEventHandlerDelegate * inputEventHandlerDelegate, LayoutFieldDelegate * delegate) { m_inputEventHandlerDelegate = inputEventHandlerDelegate; m_delegate = delegate; }
Poincare::Context * context() const;
bool isEditing() const override { return m_contentView.isEditing(); }
void setEditing(bool isEditing) override;
void clearLayout() { m_contentView.clearLayout(); }
void scrollToCursor() {
scrollToBaselinedRect(m_contentView.cursorRect(), m_contentView.cursor()->baseline());
scrollToBaselinedRect(m_contentView.cursorRect(), m_contentView.cursor()->baselineWithoutSelection());
}
bool hasText() const { return layout().hasText(); }
Poincare::Layout layout() const { return m_contentView.expressionView()->layout(); }
CodePoint XNTCodePoint(CodePoint defaultXNTCodePoint) override;
void putCursorRightOfLayout();
// ScrollableView
void setBackgroundColor(KDColor c) override {
@@ -39,18 +43,20 @@ public:
/* Responder */
bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false) override;
bool handleEvent(Ion::Events::Event event) override;
bool shouldFinishEditing(Ion::Events::Event event) override { // TODO REMOVE ?
return m_delegate->layoutFieldShouldFinishEditing(this, event);
}
// TODO: factorize with TextField (see TODO of EditableField)
bool shouldFinishEditing(Ion::Events::Event event) override;
protected:
void reload(KDSize previousSize);
virtual bool privateHandleEvent(Ion::Events::Event event);
bool privateHandleMoveEvent(Ion::Events::Event event, bool * shouldRecomputeLayout);
// Selection
bool resetSelection() { return m_contentView.resetSelection(); }
void deleteSelection();
private:
constexpr static int k_maxNumberOfLayouts = 220;
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 reload(KDSize previousSize);
virtual bool privateHandleEvent(Ion::Events::Event event);
bool privateHandleMoveEvent(Ion::Events::Event event, bool * shouldRecomputeLayout);
bool privateHandleSelectionEvent(Ion::Events::Event event, bool * shouldRecomputeLayout);
void scrollRightOfLayout(Poincare::Layout layoutR);
void scrollToBaselinedRect(KDRect rect, KDCoordinate baseline);
void insertLayoutAtCursor(Poincare::Layout layoutR, Poincare::Expression correspondingExpression, bool forceCursorRightOfLayout = false);
@@ -62,26 +68,35 @@ private:
bool setEditing(bool isEditing); // returns True if LayoutField should reload
void setBackgroundColor(KDColor c) { m_expressionView.setBackgroundColor(c); }
void setCursor(Poincare::LayoutCursor cursor) { m_cursor = cursor; }
void cursorPositionChanged() { layoutCursorSubview(); }
void cursorPositionChanged() { layoutCursorSubview(false); }
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();
/* View */
// View
KDSize minimalSizeForOptimalDisplay() const override;
// Selection
Poincare::Layout * selectionStart() { return &m_selectionStart; }
Poincare::Layout * selectionEnd() { return &m_selectionEnd; }
void addSelection(Poincare::Layout addedLayout);
bool resetSelection(); // returns true if the selection was indeed reset
void copySelection(Poincare::Context * context);
bool selectionIsEmpty() const;
void deleteSelection();
private:
enum class Position {
Top,
Bottom
};
int numberOfSubviews() const override { return 2; }
View * subviewAtIndex(int index) override;
void layoutSubviews() override;
void layoutCursorSubview();
void layoutSubviews(bool force = false) override;
void layoutCursorSubview(bool force);
KDRect selectionRect() const;
Poincare::LayoutCursor m_cursor;
ExpressionView m_expressionView;
TextCursorView m_cursorView;
/* The selection starts on the left of m_selectionStart, and ends on the
* right of m_selectionEnd. */
Poincare::Layout m_selectionStart;
Poincare::Layout m_selectionEnd;
bool m_isEditing;
};
ContentView m_contentView;

View File

@@ -1,12 +1,13 @@
#ifndef ESCHER_LAYOUT_FIELD_DELEGATE_H
#define ESCHER_LAYOUT_FIELD_DELEGATE_H
#include <escher/context_provider.h>
#include <ion/events.h>
#include <poincare/layout.h>
class LayoutField;
class LayoutFieldDelegate {
class LayoutFieldDelegate : public ContextProvider{
public:
virtual bool layoutFieldShouldFinishEditing(LayoutField * layoutField, Ion::Events::Event event) = 0;
virtual bool layoutFieldDidReceiveEvent(LayoutField * layoutField, Ion::Events::Event event) = 0;

View File

@@ -7,7 +7,7 @@
class MessageTableCell : public TableCell {
public:
MessageTableCell(I18n::Message label = (I18n::Message)0, const KDFont * font = KDFont::SmallFont, Layout layout = Layout::Horizontal);
MessageTableCell(I18n::Message label = (I18n::Message)0, const KDFont * font = KDFont::SmallFont, Layout layout = Layout::HorizontalLeftOverlap);
View * labelView() const override;
virtual void setHighlighted(bool highlight) override;
void setMessage(I18n::Message message);

View File

@@ -28,7 +28,7 @@ public:
void setAccessoryText(const char * text);
void setTextColor(KDColor color) override;
private:
void layoutSubviews() override;
void layoutSubviews(bool force = false) override;
TextField m_textField;
char m_textBody[Poincare::PrintFloat::k_maxFloatCharSize];
};

View File

@@ -5,18 +5,18 @@
class MessageTree {
public:
constexpr MessageTree(I18n::Message label = (I18n::Message)0, int numberOfChildren = 0) :
constexpr MessageTree(I18n::Message label = (I18n::Message)0, const int numberOfChildren = 0) :
m_label(label),
m_numberOfChildren(numberOfChildren)
{
};
virtual const MessageTree * children(int index) const = 0;
I18n::Message label() const;
int numberOfChildren() const;
bool isNull() const;
I18n::Message label() const { return m_label; }
int numberOfChildren() const { return m_numberOfChildren; }
bool isNull() const { return (m_label == (I18n::Message)0); }
protected:
I18n::Message m_label;
int m_numberOfChildren;
const int m_numberOfChildren;
};
#endif

View File

@@ -17,11 +17,12 @@ public:
constexpr static KDCoordinate ParameterCellHeight = 35;
constexpr static KDCoordinate ModalTopMargin = 5;
constexpr static KDCoordinate ModalBottomMargin = 18;
constexpr static KDCoordinate TableCellLabelTopMargin = 3;
constexpr static KDCoordinate TableCellVerticalMargin = 3;
constexpr static KDCoordinate TableCellHorizontalMargin = 10;
constexpr static KDCoordinate TabHeight = 27;
constexpr static KDCoordinate ScrollStep = 10;
constexpr static KDCoordinate PopUpLeftMargin = 32;
constexpr static KDCoordinate PopUpRightMargin = 32;
constexpr static KDCoordinate PopUpLeftMargin = 27;
constexpr static KDCoordinate PopUpRightMargin = 27;
constexpr static KDCoordinate PopUpTopMargin = 50;
constexpr static KDCoordinate ExamPopUpTopMargin = 27;
constexpr static KDCoordinate ExamPopUpBottomMargin = 55;
@@ -34,6 +35,7 @@ public:
constexpr static KDCoordinate CellSeparatorThickness = 1;
constexpr static KDCoordinate TableSeparatorThickness = 5;
constexpr static KDCoordinate ExpressionViewHorizontalMargin = 5;
constexpr static KDCoordinate EllipsisCellWidth = 37;
};
#endif

View File

@@ -14,7 +14,7 @@ public:
void displayModalViewController(ViewController * vc, float verticalAlignment, float horizontalAlignment,
KDCoordinate topMargin = 0, KDCoordinate leftMargin = 0, KDCoordinate bottomMargin = 0, KDCoordinate rightMargin = 0);
void reloadModalViewController();
void dismissModalViewController();
void dismissModalViewController(bool willExitApp = false);
bool isDisplayingModal();
void initView() override;
void viewWillAppear() override;
@@ -28,10 +28,10 @@ private:
void setMainView(View * regularView);
int numberOfSubviews() const override;
View * subviewAtIndex(int index) override;
void layoutSubviews() override;
void layoutSubviews(bool force = false) override;
void presentModalView(View * modalView, float verticalAlignment, float horizontalAlignment,
KDCoordinate topMargin, KDCoordinate leftMargin, KDCoordinate bottomMargin, KDCoordinate rightMargin);
void dismissModalView();
void dismissModalView(bool willExitApp = false);
bool isDisplayingModal() const;
void reload();
private:

View File

@@ -6,6 +6,13 @@
#include <escher/scroll_view_indicator.h>
class ScrollView : public View {
/* TODO: Should we add a reload method that forces the relayouting of the
* subviews? Or should ScrollView::setFrame always force the layouting of the
* subviews ? Because the scroll view frame might not change but its content
* might need to be relayouted.
* cf TableView, InputViewController, EditExpressionController. */
public:
ScrollView(View * contentView, ScrollViewDataSource * dataSource);
ScrollView(ScrollView&& other);
@@ -35,7 +42,7 @@ public:
virtual ~Decorator() = default;
virtual int numberOfIndicators() const { return 0; }
virtual View * indicatorAtIndex(int index) { assert(false); return nullptr; }
virtual KDRect layoutIndicators(KDSize content, KDPoint offset, KDRect frame, KDRect * dirtyRect1, KDRect * dirtyRect2) { return frame; }
virtual KDRect layoutIndicators(KDSize content, KDPoint offset, KDRect frame, KDRect * dirtyRect1, KDRect * dirtyRect2, bool force) { return frame; }
virtual void setBackgroundColor(KDColor c) {}
};
@@ -44,7 +51,7 @@ public:
BarDecorator() : m_verticalBar(), m_horizontalBar() {}
int numberOfIndicators() const override { return 2; }
View * indicatorAtIndex(int index) override;
KDRect layoutIndicators(KDSize content, KDPoint offset, KDRect frame, KDRect * dirtyRect1, KDRect * dirtyRect2) override;
KDRect layoutIndicators(KDSize content, KDPoint offset, KDRect frame, KDRect * dirtyRect1, KDRect * dirtyRect2, bool force) override;
ScrollViewVerticalBar * verticalBar() { return &m_verticalBar; }
ScrollViewHorizontalBar * horizontalBar() { return &m_horizontalBar; }
private:
@@ -63,7 +70,7 @@ public:
{}
int numberOfIndicators() const override { return 4; }
View * indicatorAtIndex(int index) override;
KDRect layoutIndicators(KDSize content, KDPoint offset, KDRect frame, KDRect * dirtyRect1, KDRect * dirtyRect2) override;
KDRect layoutIndicators(KDSize content, KDPoint offset, KDRect frame, KDRect * dirtyRect1, KDRect * dirtyRect2, bool force) override;
void setBackgroundColor(KDColor c) override;
private:
ScrollViewArrow m_topArrow;
@@ -96,7 +103,7 @@ protected:
return m_frame.height() - m_topMargin - m_bottomMargin;
}
KDRect visibleContentRect();
void layoutSubviews() override;
void layoutSubviews(bool force = false) override;
virtual KDSize contentSize() const { return m_contentView->minimalSizeForOptimalDisplay(); }
#if ESCHER_VIEW_LOGGING
virtual const char * className() const override;

View File

@@ -0,0 +1,19 @@
#ifndef ESCHER_SCROLLABLE_EXPRESSION_VIEW_H
#define ESCHER_SCROLLABLE_EXPRESSION_VIEW_H
#include <escher/scrollable_view.h>
#include <escher/scroll_view_data_source.h>
#include <escher/expression_view.h>
class ScrollableExpressionView : public ScrollableView, public ScrollViewDataSource {
public:
ScrollableExpressionView(Responder * parentResponder, KDCoordinate leftRightMargin, KDCoordinate topBottomMargin, float horizontalAlignment = 0.0f, float verticalAlignment = 0.5f, KDColor textColor = KDColorBlack, KDColor backgroundColor = KDColorWhite);
Poincare::Layout layout() const;
void setLayout(Poincare::Layout layout);
void setBackgroundColor(KDColor backgroundColor) override;
void setExpressionBackgroundColor(KDColor backgroundColor);
private:
ExpressionView m_expressionView;
};
#endif

View File

@@ -1,16 +1,18 @@
#ifndef ESCHER_SELECTABLE_TABLE_VIEW_DELEGATE_H
#define ESCHER_SELECTABLE_TABLE_VIEW_DELEGATE_H
#include <escher/context_provider.h>
class SelectableTableView;
class SelectableTableViewDelegate {
class SelectableTableViewDelegate : public ContextProvider {
public:
/* withinTemporarySelection flag indicates when the selection change happens
* in a temporary deselection: indeed, when reloading the data of the table,
* we deselect the table before re-layouting the entire table and re-select
* the previous selected cell. We might implement different course of action
* when the selection change is 'real' or within temporary selection. */
virtual void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection = false);
virtual void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection = false) {}
};
#endif

View File

@@ -1,10 +1,11 @@
#ifndef ESCHER_STACK_VIEW_H
#define ESCHER_STACK_VIEW_H
#include <escher/bordered.h>
#include <escher/view.h>
#include <escher/view_controller.h>
class StackView : public View {
class StackView : public View, public Bordered {
public:
StackView();
void drawRect(KDContext * ctx, KDRect rect) const override;

View File

@@ -57,7 +57,7 @@ private:
private:
int numberOfSubviews() const override;
View * subviewAtIndex(int index) override;
void layoutSubviews() override;
void layoutSubviews(bool force = false) override;
StackView m_stackViews[kMaxNumberOfStacks];
View * m_contentView;

View File

@@ -27,7 +27,7 @@ private:
constexpr static KDCoordinate k_activeTabHeight = 5;
int numberOfSubviews() const override;
View * subviewAtIndex(int index) override;
void layoutSubviews() override;
void layoutSubviews(bool force = false) override;
constexpr static uint8_t k_maxNumberOfTabs = 4;
TabViewCell m_cells[k_maxNumberOfTabs];

View File

@@ -37,7 +37,7 @@ private:
private:
int numberOfSubviews() const override;
View * subviewAtIndex(int index) override;
void layoutSubviews() override;
void layoutSubviews(bool force = false) override;
View * m_activeView;
};

View File

@@ -1,29 +1,36 @@
#ifndef ESCHER_TABLE_CELL_H
#define ESCHER_TABLE_CELL_H
#include <escher/bordered.h>
#include <escher/highlight_cell.h>
#include <escher/metric.h>
class TableCell : public HighlightCell {
class TableCell : public Bordered, public HighlightCell {
public:
/* Layout enum class determines the way subviews are layouted.
* We can split the cell vertically or horizontally.
* We can choose which subviews frames are optimized (if there is not enough
* space for all subviews, which one is cropped). This case happens so far only
* for horizontally splitted cell, so we distinguish only these sub cases.
* TODO: implement VerticalTopOverlap, VerticalBottomlap? */
enum class Layout {
Vertical,
Horizontal
HorizontalLeftOverlap, // Label overlaps on SubAccessory which overlaps on Accessory
HorizontalRightOverlap, // Reverse
};
TableCell(Layout layout = Layout::Horizontal);
TableCell(Layout layout = Layout::HorizontalLeftOverlap);
virtual View * labelView() const;
virtual View * accessoryView() const;
virtual View * subAccessoryView() const;
void drawRect(KDContext * ctx, KDRect rect) const override;
constexpr static KDCoordinate k_labelMargin = 10;
constexpr static KDCoordinate k_accessoryMargin = 10;
protected:
virtual KDCoordinate labelMargin() const { return Metric::TableCellHorizontalMargin; }
virtual KDCoordinate accessoryMargin() const { return Metric::TableCellHorizontalMargin; }
int numberOfSubviews() const override;
View * subviewAtIndex(int index) override;
void layoutSubviews() override;
constexpr static KDCoordinate k_separatorThickness = Metric::CellSeparatorThickness;
void layoutSubviews(bool force = false) override;
constexpr static KDCoordinate k_verticalMargin = Metric::TableCellVerticalMargin;
constexpr static KDCoordinate k_horizontalMargin = Metric::TableCellHorizontalMargin;
private:
constexpr static KDCoordinate k_accessoryBottomMargin = 3;
Layout m_layout;
};

View File

@@ -25,7 +25,7 @@ protected:
const char * className() const override;
#endif
TableViewDataSource * dataSource();
void layoutSubviews() override;
void layoutSubviews(bool force = false) override;
class ContentView : public View {
public:
ContentView(TableView * tableView, TableViewDataSource * dataSource, KDCoordinate horizontalCellOverlap, KDCoordinate verticalCellOverlap);
@@ -42,7 +42,7 @@ protected:
int numberOfDisplayableRows() const;
int numberOfDisplayableColumns() const;
KDRect cellFrame(int i, int j) const;
void layoutSubviews() override;
void layoutSubviews(bool force = false) override;
protected:
#if ESCHER_VIEW_LOGGING
const char * className() const override;

View File

@@ -7,6 +7,8 @@
#include <assert.h>
#include <string.h>
// See TODO in EditableField
class TextArea : public TextInput, public InputEventHandler {
public:
static constexpr int k_indentationSpaces = 2;
@@ -80,6 +82,7 @@ protected:
void insertSpacesAtLocation(int numberOfSpaces, char * location);
CodePoint removePreviousGlyph(char * * position);
size_t removeText(const char * start, const char * end);
size_t removeRemainingLine(const char * position, int direction);
char operator[](size_t index) {
assert(index < m_bufferSize);
@@ -105,8 +108,8 @@ protected:
m_cursorLocation = m_text.text();
}
void drawRect(KDContext * ctx, KDRect rect) const override;
void drawStringAt(KDContext * ctx, int line, int column, const char * text, size_t length, KDColor textColor, KDColor backgroundColor) const;
virtual void drawLine(KDContext * ctx, int line, const char * text, size_t length, int fromColumn, int toColumn) const = 0;
void drawStringAt(KDContext * ctx, int line, int column, const char * text, int length, KDColor textColor, KDColor backgroundColor, const char * selectionStart, const char * selectionEnd, KDColor backgroundHighlightColor) const;
virtual void drawLine(KDContext * ctx, int line, const char * text, size_t length, int fromColumn, int toColumn, const char * selectionStart, const char * selectionEnd) const = 0;
virtual void clearRect(KDContext * ctx, KDRect rect) const = 0;
KDSize minimalSizeForOptimalDisplay() const override;
void setText(char * textBuffer, size_t textBufferSize);
@@ -114,11 +117,12 @@ protected:
const char * editedText() const override { return m_text.text(); }
size_t editedTextLength() const override { return m_text.textLength(); }
const Text * getText() const { return &m_text; }
bool insertTextAtLocation(const char * text, const char * location) override;
bool insertTextAtLocation(const char * text, char * location) override;
void moveCursorGeo(int deltaX, int deltaY);
bool removePreviousGlyph() override;
bool removeEndOfLine() override;
bool removeStartOfLine();
size_t deleteSelection() override;
protected:
KDRect glyphFrameAtPosition(const char * text, const char * position) const override;
Text m_text;
@@ -126,6 +130,7 @@ protected:
ContentView * contentView() { return static_cast<ContentView *>(TextInput::contentView()); }
private:
void selectUpDown(bool up);
TextAreaDelegate * m_delegate;
};

View File

@@ -6,6 +6,8 @@
#include <escher/text_field_delegate.h>
#include <string.h>
// See TODO in EditableField
/* TODO: TextField currently uses using 2 buffers:
* - one to keep the displayed text
* - another one to edit the text while keeping the previous text in the first
@@ -28,7 +30,6 @@ public:
char * draftTextBuffer() const { return const_cast<char *>(m_contentView.editedText()); }
size_t draftTextLength() const;
void setText(const char * text);
void setAlignment(float horizontalAlignment, float verticalAlignment);
void setEditing(bool isEditing) override { m_contentView.setEditing(isEditing); }
CodePoint XNTCodePoint(CodePoint defaultXNTCodePoint) override;
bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false) override;
@@ -37,12 +38,13 @@ public:
return ContentView::k_maxBufferSize;
}
void scrollToCursor() override;
bool shouldFinishEditing(Ion::Events::Event event) override { return m_delegate->textFieldShouldFinishEditing(this, event); }
// TODO: factorize with TextField (see TODO of EditableField)
bool shouldFinishEditing(Ion::Events::Event event) override;
const KDFont * font() const { return m_contentView.font(); }
protected:
class ContentView : public TextInput::ContentView {
public:
ContentView(char * textBuffer, size_t textBufferSize, size_t draftTextBufferSize, const KDFont * font, float horizontalAlignment, float verticalAlignment, KDColor textColor, KDColor);
ContentView(char * textBuffer, size_t textBufferSize, size_t draftTextBufferSize, const KDFont * font, float horizontalAlignment, float verticalAlignment, KDColor textColor, KDColor backgroundColor);
void setBackgroundColor(KDColor backgroundColor);
KDColor backgroundColor() const { return m_backgroundColor; }
void setTextColor(KDColor textColor);
@@ -52,19 +54,19 @@ protected:
const char * editedText() const override;
size_t editedTextLength() const override { return m_currentDraftTextLength; }
void setText(const char * text);
void setAlignment(float horizontalAlignment, float verticalAlignment);
void setEditing(bool isEditing);
void reinitDraftTextBuffer();
void setDraftTextBufferSize(size_t size) { m_draftTextBufferSize = size; }
/* If the text to be appended is too long to be added without overflowing the
* buffer, nothing is done (not even adding few letters from the text to reach
* the maximum buffer capacity) and false is returned. */
bool insertTextAtLocation(const char * text, const char * location) override;
bool insertTextAtLocation(const char * text, char * location) override;
KDSize minimalSizeForOptimalDisplay() const override;
bool removePreviousGlyph() override;
bool removeEndOfLine() override;
void willModifyTextBuffer();
void didModifyTextBuffer();
size_t deleteSelection() override;
/* In some app (ie Calculation), text fields record expression results whose
* lengths can reach 70 (ie
* [[1.234567e-123*e^(1.234567e-123*i), 1.234567e-123*e^(1.234567e-123*i)]]).
@@ -75,15 +77,13 @@ protected:
* = 212 characters. */
constexpr static int k_maxBufferSize = 220;
private:
void layoutSubviews() override;
void layoutSubviews(bool force = false) override;
KDRect glyphFrameAtPosition(const char * buffer, const char * position) const override;
bool m_isEditing;
char * m_textBuffer;
size_t m_textBufferSize;
size_t m_draftTextBufferSize;
size_t m_currentDraftTextLength;
float m_horizontalAlignment;
float m_verticalAlignment;
KDColor m_textColor;
KDColor m_backgroundColor;
};
@@ -92,7 +92,9 @@ protected:
private:
bool privateHandleEvent(Ion::Events::Event event);
bool privateHandleMoveEvent(Ion::Events::Event event);
bool privateHandleSelectEvent(Ion::Events::Event event);
virtual void removeWholeText();
bool storeInClipboard() const;
TextFieldDelegate * m_delegate;
};

View File

@@ -6,6 +6,8 @@
#include <assert.h>
#include <string.h>
// See TODO in EditableField
class TextInput : public ScrollableView, public ScrollViewDataSource {
public:
TextInput(Responder * parentResponder, View * contentView) : ScrollableView(parentResponder, contentView, this) {}
@@ -15,32 +17,68 @@ public:
const char * cursorLocation() const { return nonEditableContentView()->cursorLocation(); }
bool setCursorLocation(const char * location);
virtual void scrollToCursor();
// Selection
bool selectionIsEmpty() const { return nonEditableContentView()->selectionIsEmpty(); }
void resetSelection() { contentView()->resetSelection(); }
void deleteSelection();
// Alignment
void setAlignment(float horizontalAlignment, float verticalAlignment);
protected:
class ContentView : public View {
public:
ContentView(const KDFont * font) :
ContentView(const KDFont * font, float horizontalAlignment = 0.0f, float verticalAlignment = 0.5f) :
View(),
m_cursorView(),
m_font(font),
m_cursorLocation(nullptr)
m_selectionStart(nullptr),
m_selectionEnd(nullptr),
m_cursorLocation(nullptr),
m_horizontalAlignment(horizontalAlignment),
m_verticalAlignment(verticalAlignment)
{}
// Font
void setFont(const KDFont * font);
const KDFont * font() const { return m_font; }
// Cursor location
const char * cursorLocation() const { assert(m_cursorLocation != nullptr); return m_cursorLocation; }
void setCursorLocation(const char * cursorLocation);
KDRect cursorRect();
// Virtual text get/add/remove
virtual const char * text() const = 0;
virtual bool insertTextAtLocation(const char * text, const char * location) = 0;
virtual bool insertTextAtLocation(const char * text, char * location) = 0;
virtual bool removePreviousGlyph() = 0;
virtual bool removeEndOfLine() = 0;
KDRect cursorRect();
// Selection
const char * selectionStart() const { return m_selectionStart; }
const char * selectionEnd() const { return m_selectionEnd; }
void addSelection(const char * left, const char * right);
bool resetSelection(); // returns true if the selection was indeed reset
bool selectionIsEmpty() const;
virtual size_t deleteSelection() = 0;
// Alignment
void setAlignment(float horizontalAlignment, float verticalAlignment);
float horizontalAlignment() const { return m_horizontalAlignment; }
// Reload
void reloadRectFromPosition(const char * position, bool includeFollowingLines = false);
protected:
virtual void layoutSubviews() override;
void reloadRectFromPosition(const char * position, bool lineBreak = false);
virtual void layoutSubviews(bool force = false) override;
void reloadRectFromAndToPositions(const char * start, const char * end);
virtual KDRect glyphFrameAtPosition(const char * buffer, const char * position) const = 0;
virtual KDRect dirtyRectFromPosition(const char * position, bool includeFollowingLines) const;
TextCursorView m_cursorView;
const KDFont * m_font;
const char * m_selectionStart;
const char * m_selectionEnd;
const char * m_cursorLocation;
virtual KDRect dirtyRectFromPosition(const char * position, bool lineBreak) const;
float m_horizontalAlignment;
float m_verticalAlignment;
private:
int numberOfSubviews() const override { return 1; }
View * subviewAtIndex(int index) override {
@@ -54,7 +92,7 @@ protected:
/* If the text to be appended is too long to be added without overflowing the
* buffer, nothing is done (not even adding few letters from the text to reach
* the maximum buffer capacity) and false is returned. */
bool insertTextAtLocation(const char * textBuffer, const char * location);
bool insertTextAtLocation(const char * textBuffer, char * location);
bool removeEndOfLine();
ContentView * contentView() {
return const_cast<ContentView *>(nonEditableContentView());
@@ -62,6 +100,7 @@ protected:
virtual const ContentView * nonEditableContentView() const = 0;
bool moveCursorLeft();
bool moveCursorRight();
bool selectLeftRight(bool left, bool all); // While indicates if all the text on the left/right should be selected
private:
virtual void willSetCursorLocation(const char * * location) {}
virtual bool privateRemoveEndOfLine();

View File

@@ -30,7 +30,7 @@ class View {
friend class TransparentView;
friend class Shared::RoundCursorView;
public:
View();
View() : m_frame(KDRectZero), m_superview(nullptr), m_dirtyRect(KDRectZero) {}
virtual ~View() {
for (int i = 0; i < numberOfSubviews(); i++) {
View * subview = subviewAtIndex(i);
@@ -49,16 +49,18 @@ public:
/* The drawRect method should be implemented by each View subclass. In a
* typical drawRect implementation, a subclass will make drawing calls to the
* Kandinsky library using the provided context. */
virtual void drawRect(KDContext * ctx, KDRect rect) const;
virtual void drawRect(KDContext * ctx, KDRect rect) const {
// By default, a view doesn't do anything, it's transparent
}
void setSize(KDSize size);
void setFrame(KDRect frame);
void setFrame(KDRect frame, bool force);
KDPoint pointFromPointInView(View * view, KDPoint point);
KDRect bounds() const;
View * subview(int index);
virtual KDSize minimalSizeForOptimalDisplay() const;
virtual KDSize minimalSizeForOptimalDisplay() const { return KDSizeZero; }
#if ESCHER_VIEW_LOGGING
friend std::ostream &operator<<(std::ostream &os, View &view);
@@ -80,13 +82,9 @@ protected:
#endif
KDRect m_frame;
private:
virtual int numberOfSubviews() const {
return 0;
}
virtual View * subviewAtIndex(int index) {
return nullptr;
}
virtual void layoutSubviews();
virtual int numberOfSubviews() const { return 0; }
virtual View * subviewAtIndex(int index) { return nullptr; }
virtual void layoutSubviews(bool force = false) {}
virtual const Window * window() const;
KDRect redraw(KDRect rect, KDRect forceRedrawRect = KDRectZero);
KDPoint absoluteOrigin() const;

View File

@@ -20,7 +20,7 @@ private:
void setLabels(I18n::Message message1, I18n::Message message2);
int numberOfSubviews() const override;
View * subviewAtIndex(int index) override;
void layoutSubviews() override;
void layoutSubviews(bool force = false) override;
KDSize minimalSizeForOptimalDisplay() const override;
private:
constexpr static KDCoordinate k_topAndBottomMargin = 20;

View File

@@ -13,7 +13,7 @@ protected:
const char * className() const override;
#endif
virtual int numberOfSubviews() const override;
virtual void layoutSubviews() override;
virtual void layoutSubviews(bool force = false) override;
virtual View * subviewAtIndex(int index) override;
View * m_contentView;
private:

View File

@@ -22,11 +22,11 @@ View * AlternateEmptyViewController::ContentView::subviewAtIndex(int index) {
return m_mainViewController->view();
}
void AlternateEmptyViewController::ContentView::layoutSubviews() {
void AlternateEmptyViewController::ContentView::layoutSubviews(bool force) {
if (alternateEmptyViewDelegate()->isEmpty()) {
m_delegate->emptyView()->setFrame(bounds());
m_delegate->emptyView()->setFrame(bounds(), force);
} else {
m_mainViewController->view()->setFrame(bounds());
m_mainViewController->view()->setFrame(bounds(), force);
}
}

View File

@@ -5,43 +5,16 @@ extern "C" {
#include <assert.h>
}
I18n::Message App::Descriptor::name() {
return (I18n::Message)0;
}
I18n::Message App::Descriptor::upperName() {
return (I18n::Message)0;
}
int App::Descriptor::examinationLevel() {
return App::Descriptor::NoExaminationLevel;
}
const Image * App::Descriptor::icon() {
return nullptr;
}
void App::Snapshot::pack(App * app) {
tidy();
app->~App();
assert(Poincare::TreePool::sharedPool()->numberOfNodes() == 0);
}
void App::Snapshot::reset() {
}
void App::Snapshot::tidy() {
}
App::App(Snapshot * snapshot, ViewController * rootViewController, I18n::Message warningMessage) :
Responder(nullptr),
m_modalViewController(this, rootViewController),
m_firstResponder(nullptr),
m_snapshot(snapshot),
m_warningController(this, warningMessage)
{
}
bool App::processEvent(Ion::Events::Event event) {
Responder * responder = m_firstResponder;
bool didHandleEvent = false;
@@ -55,10 +28,6 @@ bool App::processEvent(Ion::Events::Event event) {
return false;
}
Responder * App::firstResponder() {
return m_firstResponder;
}
void App::setFirstResponder(Responder * responder) {
Responder * previousResponder = m_firstResponder;
m_firstResponder = responder;
@@ -90,8 +59,8 @@ void App::displayModalViewController(ViewController * vc, float verticalAlignmen
m_modalViewController.displayModalViewController(vc, verticalAlignment, horizontalAlignment, topMargin, leftMargin, bottomMargin, rightMargin);
}
void App::dismissModalViewController() {
m_modalViewController.dismissModalViewController();
void App::dismissModalViewController(bool willExitApp) {
m_modalViewController.dismissModalViewController(willExitApp);
}
void App::displayWarning(I18n::Message warningMessage1, I18n::Message warningMessage2, bool specialExitKeys) {
@@ -109,7 +78,7 @@ void App::didBecomeActive(Window * window) {
void App::willBecomeInactive() {
if (m_modalViewController.isDisplayingModal()) {
dismissModalViewController();
dismissModalViewController(true);
}
setFirstResponder(nullptr);
m_modalViewController.viewDidDisappear();
@@ -118,12 +87,3 @@ void App::willBecomeInactive() {
View * App::modalView() {
return m_modalViewController.view();
}
int App::numberOfTimers() {
return 0;
}
Timer * App::timerAtIndex(int i) {
assert(false);
return nullptr;
}

15
escher/src/bordered.cpp Normal file
View File

@@ -0,0 +1,15 @@
#include <escher/bordered.h>
void Bordered::drawBorderOfRect(KDContext * ctx, KDRect rect, KDColor borderColor) const {
KDCoordinate width = rect.width();
KDCoordinate height = rect.height();
// Draw rectangle around cell
ctx->fillRect(KDRect(0, 0, width, k_separatorThickness), borderColor);
ctx->fillRect(KDRect(0, k_separatorThickness, k_separatorThickness, height-k_separatorThickness), borderColor);
ctx->fillRect(KDRect(width-k_separatorThickness, k_separatorThickness, k_separatorThickness, height-k_separatorThickness), borderColor);
ctx->fillRect(KDRect(0, height-k_separatorThickness, width, k_separatorThickness), borderColor);
}
void Bordered::drawInnerRect(KDContext * ctx, KDRect rect, KDColor backgroundColor) const {
ctx->fillRect(KDRect(k_separatorThickness, k_separatorThickness, rect.width()-2*k_separatorThickness, rect.height()-2*k_separatorThickness), backgroundColor);
}

View File

@@ -24,8 +24,8 @@ View * Button::subviewAtIndex(int index) {
return &m_messageTextView;
}
void Button::layoutSubviews() {
m_messageTextView.setFrame(bounds());
void Button::layoutSubviews(bool force) {
m_messageTextView.setFrame(bounds(), force);
}
bool Button::handleEvent(Ion::Events::Event event) {

View File

@@ -49,12 +49,12 @@ View * ButtonRowController::ContentView::subviewAtIndex(int index) {
}
}
void ButtonRowController::ContentView::layoutSubviews() {
void ButtonRowController::ContentView::layoutSubviews(bool force) {
/* Position the main view */
if (numberOfButtons() == 0) {
KDCoordinate margin = m_position == Position::Top ? 1 : 0;
KDRect mainViewFrame(0, margin, bounds().width(), bounds().height()-margin);
m_mainViewController->view()->setFrame(mainViewFrame);
m_mainViewController->view()->setFrame(mainViewFrame, force);
return;
}
KDCoordinate rowHeight;
@@ -65,7 +65,7 @@ void ButtonRowController::ContentView::layoutSubviews() {
}
KDCoordinate frameOrigin = m_position == Position::Top ? rowHeight+1 : 0;
KDRect mainViewFrame(0, frameOrigin, bounds().width(), bounds().height() - rowHeight - 1);
m_mainViewController->view()->setFrame(mainViewFrame);
m_mainViewController->view()->setFrame(mainViewFrame, force);
/* Position buttons */
int nbOfButtons = numberOfButtons();
@@ -88,7 +88,7 @@ void ButtonRowController::ContentView::layoutSubviews() {
Button * button = buttonAtIndex(i);
KDCoordinate buttonWidth = button->minimalSizeForOptimalDisplay().width();
KDRect buttonFrame(currentXOrigin, yOrigin, buttonWidth, buttonHeight);
button->setFrame(buttonFrame);
button->setFrame(buttonFrame, force);
currentXOrigin += buttonWidth + widthMargin;
}
}

View File

@@ -2,12 +2,14 @@
static Clipboard s_clipboard;
static inline int minInt(int x, int y) { return x < y ? x : y; }
Clipboard * Clipboard::sharedClipboard() {
return &s_clipboard;
}
void Clipboard::store(const char * storedText) {
strlcpy(m_textBuffer, storedText, TextField::maxBufferSize());
void Clipboard::store(const char * storedText, int length) {
strlcpy(m_textBuffer, storedText, length == -1 ? TextField::maxBufferSize() : minInt(TextField::maxBufferSize(), length + 1));
}
void Clipboard::reset() {

View File

@@ -43,12 +43,13 @@ View * EditableTextCell::subviewAtIndex(int index) {
return &m_textField;
}
void EditableTextCell::layoutSubviews() {
void EditableTextCell::layoutSubviews(bool force) {
KDRect cellBounds = bounds();
m_textField.setFrame(KDRect(cellBounds.x() + m_leftMargin,
cellBounds.y() + m_topMargin,
cellBounds.width() - m_leftMargin - m_rightMargin,
cellBounds.height() - m_topMargin - m_bottomMargin));
cellBounds.height() - m_topMargin - m_bottomMargin),
force);
}
void EditableTextCell::didBecomeFirstResponder() {

View File

@@ -38,8 +38,8 @@ View * EvenOddBufferTextCell::subviewAtIndex(int index) {
return &m_bufferTextView;
}
void EvenOddBufferTextCell::layoutSubviews() {
void EvenOddBufferTextCell::layoutSubviews(bool force) {
KDRect boundsThis = bounds();
KDRect boundsBuffer = KDRect(boundsThis.left() + k_horizontalMargin, boundsThis.top(), boundsThis.width() - 2*k_horizontalMargin, boundsThis.height());
m_bufferTextView.setFrame(boundsBuffer);
m_bufferTextView.setFrame(boundsBuffer, force);
}

View File

@@ -5,6 +5,6 @@ EvenOddCellWithEllipsis::EvenOddCellWithEllipsis() :
{
}
void EvenOddCellWithEllipsis::layoutSubviews() {
m_ellipsisView.setFrame(bounds());
void EvenOddCellWithEllipsis::layoutSubviews(bool force) {
m_ellipsisView.setFrame(bounds(), force);
}

View File

@@ -32,8 +32,8 @@ View * EvenOddEditableTextCell::subviewAtIndex(int index) {
return &m_editableCell;
}
void EvenOddEditableTextCell::layoutSubviews() {
m_editableCell.setFrame(bounds());
void EvenOddEditableTextCell::layoutSubviews(bool force) {
m_editableCell.setFrame(bounds(), force);
}
void EvenOddEditableTextCell::didBecomeFirstResponder() {

View File

@@ -65,6 +65,6 @@ View * EvenOddExpressionCell::subviewAtIndex(int index) {
return &m_expressionView;
}
void EvenOddExpressionCell::layoutSubviews() {
m_expressionView.setFrame(KDRect(m_leftMargin, 0, bounds().width() - m_leftMargin - m_rightMargin, bounds().height()));
void EvenOddExpressionCell::layoutSubviews(bool force) {
m_expressionView.setFrame(KDRect(m_leftMargin, 0, bounds().width() - m_leftMargin - m_rightMargin, bounds().height()), force);
}

View File

@@ -35,7 +35,7 @@ View * EvenOddMessageTextCell::subviewAtIndex(int index) {
return &m_messageTextView;
}
void EvenOddMessageTextCell::layoutSubviews() {
void EvenOddMessageTextCell::layoutSubviews(bool force) {
KDRect boundsThis = bounds();
m_messageTextView.setFrame(KDRect(k_horizontalMargin, 0, boundsThis.width() - 2*k_horizontalMargin, boundsThis.height()));
m_messageTextView.setFrame(KDRect(k_horizontalMargin, 0, boundsThis.width() - 2*k_horizontalMargin, boundsThis.height()), force);
}

View File

@@ -8,6 +8,7 @@ static inline KDCoordinate maxCoordinate(KDCoordinate x, KDCoordinate y) { retur
ExpressionField::ExpressionField(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, TextFieldDelegate * textFieldDelegate, LayoutFieldDelegate * layoutFieldDelegate) :
Responder(parentResponder),
View(),
m_inputViewMemoizedHeight(0),
m_textField(parentResponder, nullptr, k_textFieldBufferSize, k_textFieldBufferSize, inputEventHandlerDelegate, textFieldDelegate, KDFont::LargeFont, 0.0f, 0.5f, Palette::PrimaryText, Palette::ExpressionInputBackground),
m_layoutField(parentResponder, inputEventHandlerDelegate, layoutFieldDelegate)
{
@@ -39,7 +40,7 @@ bool ExpressionField::isEditing() const {
const char * ExpressionField::text() {
if (!editionIsInTextField()) {
m_layoutField.layout().serializeParsedExpression(m_textField.draftTextBuffer(), k_textFieldBufferSize);
m_layoutField.layout().serializeParsedExpression(m_textField.draftTextBuffer(), k_textFieldBufferSize, m_layoutField.context());
}
return m_textField.draftTextBuffer();
}
@@ -62,20 +63,15 @@ View * ExpressionField::subviewAtIndex(int index) {
return &m_layoutField;
}
void ExpressionField::layoutSubviews() {
void ExpressionField::layoutSubviews(bool force) {
KDRect inputViewFrame(0, k_separatorThickness, bounds().width(), bounds().height() - k_separatorThickness);
if (editionIsInTextField()) {
m_textField.setFrame(inputViewFrame);
m_layoutField.setFrame(KDRectZero);
m_textField.setFrame(inputViewFrame, force);
m_layoutField.setFrame(KDRectZero, force);
return;
}
m_layoutField.setFrame(inputViewFrame);
m_textField.setFrame(KDRectZero);
}
void ExpressionField::reload() {
layoutSubviews();
markRectAsDirty(bounds());
m_layoutField.setFrame(inputViewFrame, force);
m_textField.setFrame(KDRectZero, force);
}
void ExpressionField::drawRect(KDContext * ctx, KDRect rect) const {
@@ -87,6 +83,10 @@ bool ExpressionField::handleEvent(Ion::Events::Event event) {
return editionIsInTextField() ? m_textField.handleEvent(event) : m_layoutField.handleEvent(event);
}
void ExpressionField::didBecomeFirstResponder() {
m_inputViewMemoizedHeight = inputViewHeight();
}
KDSize ExpressionField::minimalSizeForOptimalDisplay() const {
return KDSize(0, inputViewHeight());
}
@@ -99,8 +99,11 @@ bool ExpressionField::isEmpty() const {
return editionIsInTextField() ? (m_textField.draftTextLength() == 0) : !m_layoutField.hasText();
}
bool ExpressionField::heightIsMaximal() const {
return inputViewHeight() == k_separatorThickness + maximalHeight();
bool ExpressionField::inputViewHeightDidChange() {
KDCoordinate newHeight = inputViewHeight();
bool didChange = m_inputViewMemoizedHeight != newHeight;
m_inputViewMemoizedHeight = newHeight;
return didChange;
}
bool ExpressionField::handleEventWithText(const char * text, bool indentation, bool forceCursorRightOfText) {
@@ -113,11 +116,7 @@ bool ExpressionField::handleEventWithText(const char * text, bool indentation, b
KDCoordinate ExpressionField::inputViewHeight() const {
return k_separatorThickness
+ (editionIsInTextField() ? k_textFieldHeight :
minCoordinate(maximalHeight(),
maxCoordinate(k_textFieldHeight, m_layoutField.minimalSizeForOptimalDisplay().height())));
}
KDCoordinate ExpressionField::maximalHeight() const {
return 0.6*Ion::Display::Height;
+ (editionIsInTextField() ? k_minimalHeight :
minCoordinate(k_maximalHeight,
maxCoordinate(k_minimalHeight, m_layoutField.minimalSizeForOptimalDisplay().height())));
}

View File

@@ -1,10 +1,12 @@
#include <escher/expression_table_cell.h>
#include <escher/container.h>
#include <escher/palette.h>
#include <assert.h>
ExpressionTableCell::ExpressionTableCell(Layout layout) :
ExpressionTableCell::ExpressionTableCell(Responder * parentResponder, Layout layout) :
Responder(parentResponder),
TableCell(layout),
m_labelExpressionView(0.0f, 0.5f, Palette::PrimaryText, Palette::ListCellBackground)
m_labelExpressionView(this, k_horizontalMargin, 0, 0.0f, 0.5f, Palette::PrimaryText, Palette::ListCellBackground)
{
}
@@ -24,3 +26,7 @@ void ExpressionTableCell::setLayout(Poincare::Layout layout) {
layoutSubviews();
}
}
void ExpressionTableCell::didBecomeFirstResponder() {
Container::activeApp()->setFirstResponder(&m_labelExpressionView);
}

View File

@@ -1,10 +1,11 @@
#include <escher/expression_table_cell_with_expression.h>
#include <escher/container.h>
#include <escher/palette.h>
#include <assert.h>
ExpressionTableCellWithExpression::ExpressionTableCellWithExpression() :
ExpressionTableCell(Layout::Horizontal),
m_accessoryExpressionView(1.0f, 0.5f, Palette::SecondaryText, Palette::ListCellBackground)
ExpressionTableCellWithExpression::ExpressionTableCellWithExpression(Responder * parentResponder) :
ExpressionTableCell(parentResponder, Layout::HorizontalLeftOverlap),
m_accessoryExpressionView(this, k_horizontalMargin, 0, 1.0f, 0.5f, Palette::SecondaryText, Palette::ListCellBackground)
{}
View * ExpressionTableCellWithExpression::accessoryView() const {
@@ -21,3 +22,7 @@ void ExpressionTableCellWithExpression::setAccessoryLayout(Poincare::Layout l) {
m_accessoryExpressionView.setLayout(l);
layoutSubviews();
}
void ExpressionTableCellWithExpression::didBecomeFirstResponder() {
Container::activeApp()->setFirstResponder(&m_accessoryExpressionView);
}

View File

@@ -2,11 +2,11 @@
#include <escher/palette.h>
#include <assert.h>
ExpressionTableCellWithPointer::ExpressionTableCellWithPointer(I18n::Message accessoryMessage, Layout layout) :
ExpressionTableCell(layout),
ExpressionTableCellWithPointer::ExpressionTableCellWithPointer(Responder * parentResponder, I18n::Message accessoryMessage, Layout layout) :
ExpressionTableCell(parentResponder, layout),
m_accessoryView(KDFont::SmallFont, accessoryMessage, 0.0f, 0.5f, Palette::SecondaryText, Palette::ListCellBackground)
{
if (layout == Layout::Horizontal) {
if (layout != Layout::Vertical) {
m_accessoryView.setAlignment(1.0f, 0.5f);
}
}

View File

@@ -1,15 +1,19 @@
#include <escher/expression_view.h>
#include <escher/palette.h>
using namespace Poincare;
static inline KDCoordinate maxCoordinate(KDCoordinate x, KDCoordinate y) { return x > y ? x : y; }
ExpressionView::ExpressionView(float horizontalAlignment, float verticalAlignment,
KDColor textColor, KDColor backgroundColor) :
KDColor textColor, KDColor backgroundColor, Poincare::Layout * selectionStart, Poincare::Layout * selectionEnd ) :
m_layout(),
m_horizontalAlignment(horizontalAlignment),
m_verticalAlignment(verticalAlignment),
m_textColor(textColor),
m_backgroundColor(backgroundColor),
m_selectionStart(selectionStart),
m_selectionEnd(selectionEnd),
m_horizontalAlignment(horizontalAlignment),
m_verticalAlignment(verticalAlignment),
m_horizontalMargin(0)
{
}
@@ -71,6 +75,6 @@ KDPoint ExpressionView::absoluteDrawingOrigin() const {
void ExpressionView::drawRect(KDContext * ctx, KDRect rect) const {
ctx->fillRect(rect, m_backgroundColor);
if (!m_layout.isUninitialized()) {
m_layout.draw(ctx, drawingOrigin(), m_textColor, m_backgroundColor);
m_layout.draw(ctx, drawingOrigin(), m_textColor, m_backgroundColor, m_selectionStart, m_selectionEnd, Palette::Select);
}
}

View File

@@ -20,8 +20,7 @@ InputViewController::InputViewController(Responder * parentResponder, ViewContro
m_failureAction(Invocation(nullptr, nullptr)),
m_inputEventHandlerDelegate(inputEventHandlerDelegate),
m_textFieldDelegate(textFieldDelegate),
m_layoutFieldDelegate(layoutFieldDelegate),
m_inputViewHeightIsMaximal(false)
m_layoutFieldDelegate(layoutFieldDelegate)
{
}
@@ -86,12 +85,17 @@ bool InputViewController::layoutFieldDidAbortEditing(LayoutField * layoutField)
}
void InputViewController::layoutFieldDidChangeSize(LayoutField * layoutField) {
/* Reload the view only if the ExpressionField height actually changes, i.e.
* not if the height is already maximal and stays maximal. */
bool newInputViewHeightIsMaximal = m_expressionFieldController.expressionField()->heightIsMaximal();
if (!m_inputViewHeightIsMaximal || !newInputViewHeightIsMaximal) {
m_inputViewHeightIsMaximal = newInputViewHeightIsMaximal;
if (m_expressionFieldController.expressionField()->inputViewHeightDidChange()) {
/* Reload the whole view only if the ExpressionField's height did actually
* change. */
reloadModalViewController();
} else {
/* The input view is already at maximal size so we do not need to relayout
* the view underneath, but the view inside the input view might still need
* to be relayouted.
* We force the relayout because the frame stays the same but we need to
* propagate a relayout to the content of the field scroll view. */
m_expressionFieldController.expressionField()->layoutSubviews(true);
}
}

View File

@@ -12,8 +12,10 @@ static inline KDCoordinate minCoordinate(KDCoordinate x, KDCoordinate y) { retur
LayoutField::ContentView::ContentView() :
m_cursor(),
m_expressionView(0.0f, 0.5f, Palette::PrimaryText, Palette::BackgroundHard),
m_expressionView(0.0f, 0.5f, Palette::PrimaryText, Palette::BackgroundHard, &m_selectionStart, &m_selectionEnd),
m_cursorView(),
m_selectionStart(),
m_selectionEnd(),
m_isEditing(false)
{
clearLayout();
@@ -47,28 +49,202 @@ KDSize LayoutField::ContentView::minimalSizeForOptimalDisplay() const {
return KDSize(evSize.width() + Poincare::LayoutCursor::k_cursorWidth, evSize.height());
}
bool IsBefore(Layout& l1, Layout& l2, bool strict) {
char * node1 = reinterpret_cast<char *>(l1.node());
char * node2 = reinterpret_cast<char *>(l2.node());
return strict ? (node1 < node2) : (node1 <= node2);
}
void LayoutField::ContentView::addSelection(Layout addedLayout) {
KDRect rectBefore = selectionRect();
if (selectionIsEmpty()) {
/*
* ---------- -> +++ is the previous previous selection
* ( ) -> added selection
* ---+++++-- -> next selection
* */
m_selectionStart = addedLayout;
m_selectionEnd = addedLayout;
} else if (IsBefore(m_selectionEnd, addedLayout, true)) {
/*
* +++------- -> +++ is the previous previous selection
* ( ) -> added selection
* ++++++++++ -> next selection
* */
if (addedLayout.parent() == m_selectionStart) {
/* The previous selected layout is an horizontal layout and we remove one
* of its children. */
assert(m_selectionStart == m_selectionEnd
&& m_selectionStart.type() == LayoutNode::Type::HorizontalLayout);
m_selectionStart = m_selectionStart.childAtIndex(0);
m_selectionEnd = m_selectionEnd.childAtIndex(m_selectionEnd.numberOfChildren() - 1);
addSelection(addedLayout);
return;
}
/* The previous selected layouts and the new added selection are all
* children of a same horizontal layout. */
assert(m_selectionStart.parent() == m_selectionEnd.parent()
&& m_selectionStart.parent() == addedLayout.parent()
&& m_selectionStart.parent().type() == LayoutNode::Type::HorizontalLayout);
m_selectionEnd = addedLayout;
} else if (IsBefore(addedLayout, m_selectionStart, true)) {
/*
* -------+++ -> +++ is the previous previous selection
* ( ) -> added selection
* ++++++++++ -> next selection
* */
if (addedLayout.type() == LayoutNode::Type::HorizontalLayout
&& m_selectionStart.parent() == addedLayout)
{
/* The selection was from the first to the last child of an horizontal
* layout, we add this horizontal layout -> the selection is now empty. */
assert(m_selectionEnd.parent() == addedLayout);
assert(addedLayout.childAtIndex(0) == m_selectionStart);
assert(addedLayout.childAtIndex(addedLayout.numberOfChildren() - 1) == m_selectionEnd);
m_selectionStart = Layout();
m_selectionEnd = Layout();
} else {
if (m_selectionStart.hasAncestor(addedLayout, true)) {
// We are selecting a layout containing the current selection
m_selectionEnd = addedLayout;
}
m_selectionStart = addedLayout;
}
} else {
bool sameEnd = m_selectionEnd == addedLayout;
bool sameStart = m_selectionStart == addedLayout;
if (sameStart && sameEnd) {
/*
* -----+++++ -> +++ is the previous previous selection
* ( ) -> added selection
* ---------- -> next selection
* */
m_selectionStart = Layout();
m_selectionEnd = Layout();
} else {
assert(sameStart || sameEnd);
/*
* ++++++++++ -> +++ is the previous previous selection
* ( ) -> added selection if sameStart
* ( ) -> added selection if sameEnd
* +++++----- -> next selection
* The previous selected layouts and the new "added" selection are all
* children of a same horizontal layout. */
Layout horizontalParent = m_selectionStart.parent();
assert(!horizontalParent.isUninitialized()
&& horizontalParent == m_selectionEnd.parent()
&& horizontalParent == addedLayout.parent()
&& horizontalParent.type() == LayoutNode::Type::HorizontalLayout
&& ((sameEnd && horizontalParent.indexOfChild(m_selectionEnd) > 0)
|| (sameStart && horizontalParent.indexOfChild(m_selectionStart) < horizontalParent.numberOfChildren())));
if (sameStart) {
m_selectionStart = horizontalParent.childAtIndex(horizontalParent.indexOfChild(m_selectionStart) + 1);
} else {
m_selectionEnd = horizontalParent.childAtIndex(horizontalParent.indexOfChild(m_selectionEnd) - 1);
}
}
}
KDRect rectAfter = selectionRect();
// We need to update the background color for selected/unselected layouts
markRectAsDirty(rectBefore.unionedWith(rectAfter));
}
bool LayoutField::ContentView::resetSelection() {
if (selectionIsEmpty()) {
return false;
}
m_selectionStart = Layout();
m_selectionEnd = Layout();
return true;
}
void LayoutField::ContentView::copySelection(Context * context) {
if (selectionIsEmpty()) {
return;
}
constexpr int bufferSize = TextField::maxBufferSize();
char buffer[bufferSize];
if (m_selectionStart == m_selectionEnd) {
m_selectionStart.serializeParsedExpression(buffer, bufferSize, context);
if (buffer[0] == 0) {
int offset = 0;
if (m_selectionStart.type() == LayoutNode::Type::VerticalOffsetLayout) {
assert(bufferSize > 1);
buffer[offset++] = UCodePointEmpty;
}
m_selectionStart.serializeForParsing(buffer + offset, bufferSize - offset);
}
} else {
Layout selectionParent = m_selectionStart.parent();
assert(!selectionParent.isUninitialized());
assert(selectionParent.type() == LayoutNode::Type::HorizontalLayout);
int firstIndex = selectionParent.indexOfChild(m_selectionStart);
int lastIndex = selectionParent.indexOfChild(m_selectionEnd);
static_cast<HorizontalLayout&>(selectionParent).serializeChildren(firstIndex, lastIndex, buffer, bufferSize);
}
if (buffer[0] != 0) {
Clipboard::sharedClipboard()->store(buffer);
}
}
bool LayoutField::ContentView::selectionIsEmpty() const {
assert(!m_selectionStart.isUninitialized() || m_selectionEnd.isUninitialized());
assert(!m_selectionEnd.isUninitialized() || m_selectionStart.isUninitialized());
return m_selectionStart.isUninitialized();
}
void LayoutField::ContentView::deleteSelection() {
assert(!selectionIsEmpty());
Layout selectionParent = m_selectionStart.parent();
/* If the selected layout is the upmost layout, it must be an horizontal
* layout. Empty it. */
if (selectionParent.isUninitialized()) {
assert(m_selectionStart == m_selectionEnd);
assert(m_selectionStart.type() == LayoutNode::Type::HorizontalLayout);
clearLayout();
} else {
assert(selectionParent == m_selectionEnd.parent());
// Remove the selected children or replace it with an empty layout.
if (selectionParent.type() == LayoutNode::Type::HorizontalLayout) {
int firstIndex = selectionParent.indexOfChild(m_selectionStart);
int lastIndex = m_selectionStart == m_selectionEnd ? firstIndex : selectionParent.indexOfChild(m_selectionEnd);
for (int i = lastIndex; i >= firstIndex; i--) {
static_cast<HorizontalLayout&>(selectionParent).removeChildAtIndex(i, &m_cursor, false);
}
} else {
// Only one child can be selected
assert(m_selectionStart == m_selectionEnd);
selectionParent.replaceChildWithEmpty(m_selectionStart, &m_cursor);
}
}
resetSelection();
}
View * LayoutField::ContentView::subviewAtIndex(int index) {
assert(index >= 0 && index < 2);
assert(0 <= index && index < numberOfSubviews());
View * m_views[] = {&m_expressionView, &m_cursorView};
return m_views[index];
}
void LayoutField::ContentView::layoutSubviews() {
m_expressionView.setFrame(bounds());
layoutCursorSubview();
void LayoutField::ContentView::layoutSubviews(bool force) {
m_expressionView.setFrame(bounds(), force);
layoutCursorSubview(force);
}
void LayoutField::ContentView::layoutCursorSubview() {
void LayoutField::ContentView::layoutCursorSubview(bool force) {
if (!m_isEditing) {
m_cursorView.setFrame(KDRectZero);
m_cursorView.setFrame(KDRectZero, force);
return;
}
KDPoint expressionViewOrigin = m_expressionView.absoluteDrawingOrigin();
Layout pointedLayoutR = m_cursor.layoutReference();
Layout pointedLayoutR = m_cursor.layout();
LayoutCursor::Position cursorPosition = m_cursor.position();
LayoutCursor eqCursor = pointedLayoutR.equivalentCursor(&m_cursor);
if (eqCursor.isDefined() && pointedLayoutR.hasChild(eqCursor.layoutReference())) {
pointedLayoutR = eqCursor.layoutReference();
if (eqCursor.isDefined() && pointedLayoutR.hasChild(eqCursor.layout())) {
pointedLayoutR = eqCursor.layout();
cursorPosition = eqCursor.position();
}
KDPoint cursoredExpressionViewOrigin = pointedLayoutR.absoluteOrigin();
@@ -76,8 +252,28 @@ void LayoutField::ContentView::layoutCursorSubview() {
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()));
if (selectionIsEmpty()) {
KDPoint cursorTopLeftPosition(cursorX, expressionViewOrigin.y() + cursoredExpressionViewOrigin.y() + pointedLayoutR.baseline() - m_cursor.baselineWithoutSelection());
m_cursorView.setFrame(KDRect(cursorTopLeftPosition, LayoutCursor::k_cursorWidth, m_cursor.cursorHeightWithoutSelection()), force);
} else {
KDRect cursorRect = selectionRect();
KDPoint cursorTopLeftPosition(cursorX, expressionViewOrigin.y() + cursorRect.y());
m_cursorView.setFrame(KDRect(cursorTopLeftPosition, LayoutCursor::k_cursorWidth, cursorRect.height()), force);
}
}
KDRect LayoutField::ContentView::selectionRect() const {
if (selectionIsEmpty()) {
return KDRectZero;
}
if (m_selectionStart == m_selectionEnd) {
return KDRect(m_selectionStart.absoluteOrigin(), m_selectionStart.layoutSize());
}
Layout selectionParent = m_selectionStart.parent();
assert(m_selectionEnd.parent() == selectionParent);
assert(selectionParent.type() == LayoutNode::Type::HorizontalLayout);
KDRect selectionRectInParent = static_cast<HorizontalLayout &>(selectionParent).relativeSelectionRect(&m_selectionStart, &m_selectionEnd);
return selectionRectInParent.translatedBy(selectionParent.absoluteOrigin());
}
void LayoutField::setEditing(bool isEditing) {
@@ -87,14 +283,23 @@ void LayoutField::setEditing(bool isEditing) {
}
}
Context * LayoutField::context() const {
return (m_delegate != nullptr) ? m_delegate->context() : nullptr;
}
CodePoint LayoutField::XNTCodePoint(CodePoint defaultXNTCodePoint) {
CodePoint xnt = m_contentView.cursor()->layoutReference().XNTCodePoint();
CodePoint xnt = m_contentView.cursor()->layout().XNTCodePoint();
if (xnt != UCodePointNull) {
return xnt;
}
return defaultXNTCodePoint;
}
void LayoutField::putCursorRightOfLayout() {
m_contentView.cursor()->layout().removeGreySquaresFromAllMatrixAncestors();
m_contentView.setCursor(LayoutCursor(m_contentView.expressionView()->layout(), LayoutCursor::Position::Right));
}
void LayoutField::reload(KDSize previousSize) {
layout().invalidAllSizesPositionsAndBaselines();
KDSize newSize = minimalSizeForOptimalDisplay();
@@ -111,6 +316,12 @@ bool LayoutField::handleEventWithText(const char * text, bool indentation, bool
* - the result of a key pressed, such as "," or "cos(•)"
* - the text added after a toolbox selection
* - the result of a copy-paste. */
// Delete the selected layouts if needed
if (!m_contentView.selectionIsEmpty()) {
deleteSelection();
}
if (text[0] == 0) {
// The text is empty
return true;
@@ -142,11 +353,11 @@ bool LayoutField::handleEventWithText(const char * text, bool indentation, bool
} else if((strcmp(text, Ion::Events::Multiplication.text())) == 0){
m_contentView.cursor()->addMultiplicationPointLayout();
} else {
Expression resultExpression = Expression::Parse(text);
Expression resultExpression = Expression::Parse(text, nullptr);
if (resultExpression.isUninitialized()) {
// The text is not parsable (for instance, ",") and is added char by char.
KDSize previousLayoutSize = minimalSizeForOptimalDisplay();
m_contentView.cursor()->insertText(text);
m_contentView.cursor()->insertText(text, forceCursorRightOfText);
reload(previousLayoutSize);
return true;
}
@@ -160,6 +371,14 @@ bool LayoutField::handleEventWithText(const char * text, bool indentation, bool
return true;
}
bool LayoutField::shouldFinishEditing(Ion::Events::Event event) {
if (m_delegate->layoutFieldShouldFinishEditing(this, event)) {
resetSelection();
return true;
}
return false;
}
bool LayoutField::handleEvent(Ion::Events::Event event) {
bool didHandleEvent = false;
KDSize previousSize = minimalSizeForOptimalDisplay();
@@ -171,6 +390,23 @@ bool LayoutField::handleEvent(Ion::Events::Event event) {
}
shouldRecomputeLayout = shouldRecomputeLayout || moveEventChangedLayout;
didHandleEvent = true;
} else if (privateHandleSelectionEvent(event, &shouldRecomputeLayout)) {
didHandleEvent = true;
// Handle matrices
if (!m_contentView.selectionIsEmpty()) {
bool removedSquares = false;
Layout * selectStart = m_contentView.selectionStart();
Layout * selectEnd = m_contentView.selectionEnd();
if (*selectStart != *selectEnd) {
Layout p = selectStart->parent();
assert(p == selectEnd->parent());
assert(p.type() == LayoutNode::Type::HorizontalLayout);
removedSquares = p.removeGreySquaresFromAllMatrixChildren();
} else {
removedSquares = selectStart->removeGreySquaresFromAllMatrixChildren();
}
shouldRecomputeLayout = m_contentView.cursor()->layout().removeGreySquaresFromAllMatrixChildren() || removedSquares || shouldRecomputeLayout;
}
} else if (privateHandleEvent(event)) {
shouldRecomputeLayout = true;
didHandleEvent = true;
@@ -191,6 +427,10 @@ bool LayoutField::handleEvent(Ion::Events::Event event) {
return true;
}
void LayoutField::deleteSelection() {
m_contentView.deleteSelection();
}
bool LayoutField::privateHandleEvent(Ion::Events::Event event) {
if (m_delegate && m_delegate->layoutFieldDidReceiveEvent(this, event)) {
return true;
@@ -201,11 +441,12 @@ bool LayoutField::privateHandleEvent(Ion::Events::Event event) {
}
return true;
}
if (isEditing() && m_delegate->layoutFieldShouldFinishEditing(this, event)) { //TODO use class method?
if (isEditing() && m_delegate && m_delegate->layoutFieldShouldFinishEditing(this, event)) { //TODO use class method?
setEditing(false);
if (m_delegate->layoutFieldDidFinishEditing(this, layout(), event)) {
// Reinit layout for next use
clearLayout();
resetSelection();
} else {
setEditing(true);
}
@@ -225,6 +466,7 @@ bool LayoutField::privateHandleEvent(Ion::Events::Event event) {
}
if (event == Ion::Events::Back && isEditing()) {
clearLayout();
resetSelection();
setEditing(false);
m_delegate->layoutFieldDidAbortEditing(this);
return true;
@@ -243,40 +485,51 @@ bool LayoutField::privateHandleEvent(Ion::Events::Event event) {
handleEventWithText(Clipboard::sharedClipboard()->storedText(), false, true);
} else {
assert(event == Ion::Events::Backspace);
m_contentView.cursor()->performBackspace();
if (!m_contentView.selectionIsEmpty()) {
deleteSelection();
} else {
m_contentView.cursor()->performBackspace();
}
}
return true;
}
if (event == Ion::Events::Copy && isEditing()) {
m_contentView.copySelection(context());
return true;
}
if (event == Ion::Events::Clear && isEditing()) {
clearLayout();
resetSelection();
return true;
}
return false;
}
static inline bool IsSimpleMoveEvent(Ion::Events::Event event) {
return event == Ion::Events::Left
|| event == Ion::Events::Right
|| event == Ion::Events::Up
|| event == Ion::Events::Down;
}
bool LayoutField::privateHandleMoveEvent(Ion::Events::Event event, bool * shouldRecomputeLayout) {
if (!IsSimpleMoveEvent(event)) {
return false;
}
if (resetSelection()) {
*shouldRecomputeLayout = true;
return true;
}
LayoutCursor result;
if (event == Ion::Events::Left) {
result = m_contentView.cursor()->cursorAtDirection(LayoutCursor::MoveDirection::Left, shouldRecomputeLayout);
result = m_contentView.cursor()->cursorAtDirection(LayoutCursor::Direction::Left, shouldRecomputeLayout);
} else if (event == Ion::Events::Right) {
result = m_contentView.cursor()->cursorAtDirection(LayoutCursor::MoveDirection::Right, shouldRecomputeLayout);
result = m_contentView.cursor()->cursorAtDirection(LayoutCursor::Direction::Right, shouldRecomputeLayout);
} else if (event == Ion::Events::Up) {
result = m_contentView.cursor()->cursorAtDirection(LayoutCursor::MoveDirection::Up, shouldRecomputeLayout);
} else if (event == Ion::Events::Down) {
result = m_contentView.cursor()->cursorAtDirection(LayoutCursor::MoveDirection::Down, shouldRecomputeLayout);
} else if (event == Ion::Events::ShiftLeft) {
*shouldRecomputeLayout = true;
if (m_contentView.cursor()->layoutReference().removeGreySquaresFromAllMatrixAncestors()) {
*shouldRecomputeLayout = true;
}
result.setLayout(layout());
result.setPosition(LayoutCursor::Position::Left);
} else if (event == Ion::Events::ShiftRight) {
if (m_contentView.cursor()->layoutReference().removeGreySquaresFromAllMatrixAncestors()) {
*shouldRecomputeLayout = true;
}
result.setLayout(layout());
result.setPosition(LayoutCursor::Position::Right);
result = m_contentView.cursor()->cursorAtDirection(LayoutCursor::Direction::Up, shouldRecomputeLayout);
} else {
assert(event == Ion::Events::Down);
result = m_contentView.cursor()->cursorAtDirection(LayoutCursor::Direction::Down, shouldRecomputeLayout);
}
if (result.isDefined()) {
m_contentView.setCursor(result);
@@ -285,6 +538,29 @@ bool LayoutField::privateHandleMoveEvent(Ion::Events::Event event, bool * should
return false;
}
bool eventIsSelection(Ion::Events::Event event) {
return event == Ion::Events::ShiftLeft || event == Ion::Events::ShiftRight || event == Ion::Events::ShiftUp || event == Ion::Events::ShiftDown;
}
bool LayoutField::privateHandleSelectionEvent(Ion::Events::Event event, bool * shouldRecomputeLayout) {
if (!eventIsSelection(event)) {
return false;
}
Layout addedSelection;
LayoutCursor::Direction direction = event == Ion::Events::ShiftLeft ? LayoutCursor::Direction::Left :
(event == Ion::Events::ShiftRight ? LayoutCursor::Direction::Right :
(event == Ion::Events::ShiftUp ? LayoutCursor::Direction::Up :
LayoutCursor::Direction::Down));
LayoutCursor result = m_contentView.cursor()->selectAtDirection(direction, shouldRecomputeLayout, &addedSelection);
if (addedSelection.isUninitialized()) {
return false;
}
m_contentView.addSelection(addedSelection);
assert(result.isDefined());
m_contentView.setCursor(result);
return true;
}
void LayoutField::scrollRightOfLayout(Layout layoutR) {
KDRect layoutRect(layoutR.absoluteOrigin().translatedBy(m_contentView.expressionView()->drawingOrigin()), layoutR.layoutSize());
scrollToBaselinedRect(layoutRect, layoutR.baseline());
@@ -374,7 +650,7 @@ void LayoutField::insertLayoutAtCursor(Layout layoutR, Poincare::Expression corr
}
// Handle matrices
cursor->layoutReference().addGreySquaresToAllMatrixAncestors();
cursor->layout().addGreySquaresToAllMatrixAncestors();
// Handle empty layouts
cursor->hideEmptyLayoutIfNeeded();

View File

@@ -46,12 +46,17 @@ void MessageTableCellWithEditableText::setTextColor(KDColor color) {
MessageTableCell::setTextColor(color);
}
void MessageTableCellWithEditableText::layoutSubviews() {
TableCell::layoutSubviews();
void MessageTableCellWithEditableText::layoutSubviews(bool force) {
TableCell::layoutSubviews(force);
KDSize textFieldSize = m_textField.minimalSizeForOptimalDisplay();
KDSize labelSize = labelView()->minimalSizeForOptimalDisplay();
/* Handle textfield that has no defined width (as their width evolves with
* the length of edited text */
textFieldSize = KDSize(bounds().width() - 2*k_separatorThickness - labelSize.width()-2*k_labelMargin-k_accessoryMargin, textFieldSize.height());
m_textField.setFrame(KDRect(bounds().width() - textFieldSize.width() - k_separatorThickness-k_accessoryMargin, (bounds().height()-textFieldSize.height()-k_accessoryMargin)/2, textFieldSize.width(), textFieldSize.height()+k_accessoryMargin));
textFieldSize = KDSize(bounds().width() - 2*k_separatorThickness - labelSize.width()-2*labelMargin()-k_horizontalMargin, textFieldSize.height());
m_textField.setFrame(KDRect(
bounds().width() - textFieldSize.width() - k_separatorThickness-k_horizontalMargin,
(bounds().height()-textFieldSize.height()-k_horizontalMargin)/2,
textFieldSize.width(),
textFieldSize.height()+k_horizontalMargin),
force);
}

View File

@@ -1,13 +0,0 @@
#include <escher/message_tree.h>
int MessageTree::numberOfChildren() const {
return m_numberOfChildren;
}
I18n::Message MessageTree::label() const {
return m_label;
}
bool MessageTree::isNull() const {
return (m_label == (I18n::Message)0);
}

View File

@@ -54,15 +54,15 @@ KDRect ModalViewController::ContentView::modalViewFrame() const {
return modalViewFrame;
}
void ModalViewController::ContentView::layoutSubviews() {
void ModalViewController::ContentView::layoutSubviews(bool force) {
assert(m_regularView != nullptr);
m_regularView->setFrame(bounds());
m_regularView->setFrame(bounds(), force);
if (m_isDisplayingModal) {
assert(m_currentModalView != nullptr);
m_currentModalView->setFrame(modalViewFrame());
m_currentModalView->setFrame(modalViewFrame(), force);
} else {
if (m_currentModalView) {
m_currentModalView->setFrame(KDRectZero);
m_currentModalView->setFrame(KDRectZero, force);
}
}
}
@@ -80,9 +80,11 @@ void ModalViewController::ContentView::presentModalView(View * modalView, float
layoutSubviews();
}
void ModalViewController::ContentView::dismissModalView() {
void ModalViewController::ContentView::dismissModalView(bool willExitApp) {
m_isDisplayingModal = false;
layoutSubviews();
if (!willExitApp) {
layoutSubviews();
}
m_currentModalView->resetSuperview();
m_currentModalView = nullptr;
}
@@ -128,10 +130,12 @@ void ModalViewController::reloadModalViewController() {
m_contentView.layoutSubviews();
}
void ModalViewController::dismissModalViewController() {
void ModalViewController::dismissModalViewController(bool willExitApp) {
m_currentModalViewController->viewDidDisappear();
Container::activeApp()->setFirstResponder(m_previousResponder);
m_contentView.dismissModalView();
if (!willExitApp) {
Container::activeApp()->setFirstResponder(m_previousResponder);
}
m_contentView.dismissModalView(willExitApp);
m_currentModalViewController = nullptr;
}

View File

@@ -78,8 +78,8 @@ View * NestedMenuController::ListController::view() {
void NestedMenuController::ListController::didBecomeFirstResponder() {
m_selectableTableView->reloadData();
m_selectableTableView->selectCellAtLocation(0, m_firstSelectedRow);
Container::activeApp()->setFirstResponder(m_selectableTableView);
m_selectableTableView->selectCellAtLocation(0, m_firstSelectedRow);
}
void NestedMenuController::ListController::setFirstSelectedRow(int firstSelectedRow) {

View File

@@ -93,20 +93,20 @@ KDRect ScrollView::visibleContentRect() {
m_frame.height() - m_topMargin - m_bottomMargin);
}
void ScrollView::layoutSubviews() {
void ScrollView::layoutSubviews(bool force) {
KDRect r1 = KDRectZero;
KDRect r2 = KDRectZero;
KDRect innerFrame = decorator()->layoutIndicators(minimalSizeForOptimalDisplay(), contentOffset(), bounds(), &r1, &r2);
KDRect innerFrame = decorator()->layoutIndicators(minimalSizeForOptimalDisplay(), contentOffset(), bounds(), &r1, &r2, force);
if (!r1.isEmpty()) {
markRectAsDirty(r1);
}
if (!r2.isEmpty()) {
markRectAsDirty(r2);
}
m_innerView.setFrame(innerFrame);
m_innerView.setFrame(innerFrame, force);
KDPoint absoluteOffset = contentOffset().opposite().translatedBy(KDPoint(m_leftMargin - innerFrame.x(), m_topMargin - innerFrame.y()));
KDRect contentFrame = KDRect(absoluteOffset, contentSize());
m_contentView->setFrame(contentFrame);
m_contentView->setFrame(contentFrame, force);
}
void ScrollView::setContentOffset(KDPoint offset, bool forceRelayout) {
@@ -136,7 +136,7 @@ View * ScrollView::BarDecorator::indicatorAtIndex(int index) {
return &m_horizontalBar;
}
KDRect ScrollView::BarDecorator::layoutIndicators(KDSize content, KDPoint offset, KDRect frame, KDRect * dirtyRect1, KDRect * dirtyRect2) {
KDRect ScrollView::BarDecorator::layoutIndicators(KDSize content, KDPoint offset, KDRect frame, KDRect * dirtyRect1, KDRect * dirtyRect2, bool force) {
bool hBarWasVisible = m_horizontalBar.visible();
bool hBarIsVisible = m_horizontalBar.update(content.width(), offset.x(), frame.width());
bool vBarWasVisible = m_verticalBar.visible();
@@ -152,13 +152,13 @@ KDRect ScrollView::BarDecorator::layoutIndicators(KDSize content, KDPoint offset
/* If the two indicators are visible, we leave an empty rectangle in the right
* bottom corner. Otherwise, the only indicator uses all the height/width. */
m_verticalBar.setFrame(KDRect(
frame.width() - vBarFrameBreadth, 0,
vBarFrameBreadth, frame.height() - hBarFrameBreadth
));
frame.width() - vBarFrameBreadth, 0,
vBarFrameBreadth, frame.height() - hBarFrameBreadth),
force);
m_horizontalBar.setFrame(KDRect(
0, frame.height() - hBarFrameBreadth,
frame.width() - vBarFrameBreadth, hBarFrameBreadth
));
0, frame.height() - hBarFrameBreadth,
frame.width() - vBarFrameBreadth, hBarFrameBreadth),
force);
return frame;
}
@@ -176,7 +176,7 @@ View * ScrollView::ArrowDecorator::indicatorAtIndex(int index) {
}
}
KDRect ScrollView::ArrowDecorator::layoutIndicators(KDSize content, KDPoint offset, KDRect frame, KDRect * dirtyRect1, KDRect * dirtyRect2) {
KDRect ScrollView::ArrowDecorator::layoutIndicators(KDSize content, KDPoint offset, KDRect frame, KDRect * dirtyRect1, KDRect * dirtyRect2, bool force) {
// There is no need to dirty the rects
KDSize arrowSize = KDFont::LargeFont->glyphSize();
KDCoordinate topArrowFrameBreadth = arrowSize.height() * m_topArrow.update(0 < offset.y());
@@ -184,21 +184,21 @@ KDRect ScrollView::ArrowDecorator::layoutIndicators(KDSize content, KDPoint offs
KDCoordinate bottomArrowFrameBreadth = arrowSize.height() * m_bottomArrow.update(offset.y() + frame.height() < content.height());
KDCoordinate leftArrowFrameBreadth = arrowSize.width() * m_leftArrow.update(0 < offset.x());
m_topArrow.setFrame(KDRect(
0, 0,
frame.width(), topArrowFrameBreadth
));
0, 0,
frame.width(), topArrowFrameBreadth),
force);
m_rightArrow.setFrame(KDRect(
frame.width() - rightArrowFrameBreadth, 0,
rightArrowFrameBreadth, frame.height()
));
frame.width() - rightArrowFrameBreadth, 0,
rightArrowFrameBreadth, frame.height()),
force);
m_bottomArrow.setFrame(KDRect(
0, frame.height() - bottomArrowFrameBreadth,
frame.width(), bottomArrowFrameBreadth
));
0, frame.height() - bottomArrowFrameBreadth,
frame.width(), bottomArrowFrameBreadth),
force);
m_leftArrow.setFrame(KDRect(
0, 0,
leftArrowFrameBreadth, frame.height()
));
0, 0,
leftArrowFrameBreadth, frame.height()),
force);
return KDRect(
frame.x() + leftArrowFrameBreadth,
frame.y() + topArrowFrameBreadth,

View File

@@ -0,0 +1,35 @@
#include <escher/scrollable_expression_view.h>
#include <poincare/layout.h>
#include <escher/metric.h>
#include <assert.h>
ScrollableExpressionView::ScrollableExpressionView(Responder * parentResponder, KDCoordinate leftRightMargin, KDCoordinate topBottomMargin, float horizontalAlignment, float verticalAlignment, KDColor textColor, KDColor backgroundColor) :
ScrollableView(parentResponder, &m_expressionView, this),
m_expressionView(horizontalAlignment, verticalAlignment, textColor, backgroundColor)
{
setDecoratorType(ScrollView::Decorator::Type::Arrows);
setMargins(
topBottomMargin,
leftRightMargin,
topBottomMargin,
leftRightMargin
);
setBackgroundColor(backgroundColor);
}
Poincare::Layout ScrollableExpressionView::layout() const {
return m_expressionView.layout();
}
void ScrollableExpressionView::setLayout(Poincare::Layout layout) {
m_expressionView.setLayout(layout);
}
void ScrollableExpressionView::setBackgroundColor(KDColor backgroundColor) {
m_expressionView.setBackgroundColor(backgroundColor);
ScrollableView::setBackgroundColor(backgroundColor);
}
void ScrollableExpressionView::setExpressionBackgroundColor(KDColor backgroundColor) {
m_expressionView.setBackgroundColor(backgroundColor);
}

View File

@@ -58,7 +58,9 @@ void SelectableTableView::didEnterResponderChain(Responder * previousFirstRespon
}
void SelectableTableView::willExitResponderChain(Responder * nextFirstResponder) {
unhighlightSelectedCell();
if (nextFirstResponder != nullptr) {
unhighlightSelectedCell();
}
}
void SelectableTableView::deselectTable(bool withinTemporarySelection) {
@@ -149,7 +151,7 @@ bool SelectableTableView::handleEvent(Ion::Events::Event event) {
if (!l.isUninitialized()) {
constexpr int bufferSize = TextField::maxBufferSize();
char buffer[bufferSize];
l.serializeParsedExpression(buffer, bufferSize);
l.serializeParsedExpression(buffer, bufferSize, m_delegate == nullptr ? nullptr : m_delegate->context());
Clipboard::sharedClipboard()->store(buffer);
return true;
}

View File

@@ -1,4 +0,0 @@
#include <escher/selectable_table_view_delegate.h>
void SelectableTableViewDelegate::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) {
}

View File

@@ -1,19 +0,0 @@
#include <escher/solid_text_area.h>
static inline int minInt(int x, int y) { return x < y ? x : y; }
void SolidTextArea::ContentView::clearRect(KDContext * ctx, KDRect rect) const {
ctx->fillRect(rect, m_backgroundColor);
}
void SolidTextArea::ContentView::drawLine(KDContext * ctx, int line, const char * text, size_t length, int fromColumn, int toColumn) const {
drawStringAt(
ctx,
line,
fromColumn,
text + fromColumn,
minInt(length - fromColumn, toColumn - fromColumn),
m_textColor,
m_backgroundColor
);
}

View File

@@ -30,13 +30,11 @@ void StackView::setNamedController(ViewController * controller) {
}
void StackView::drawRect(KDContext * ctx, KDRect rect) const {
const KDFont * font = KDFont::SmallFont;
KDCoordinate height = bounds().height();
KDCoordinate width = bounds().width();
ctx->fillRect(KDRect(0, 0, width, 1), m_separatorColor);
ctx->fillRect(KDRect(0, 1, width, height-2), m_backgroundColor);
ctx->fillRect(KDRect(0, height-1, width, 1), m_separatorColor);
KDRect b = bounds();
drawBorderOfRect(ctx, b, m_separatorColor);
drawInnerRect(ctx, b, m_backgroundColor);
// Write title
const KDFont * font = KDFont::SmallFont;
KDSize textSize = font->stringSize(m_controller->title());
KDPoint origin((m_frame.width() - textSize.width())/2,(m_frame.height() - textSize.height())/2);
ctx->drawString(m_controller->title(), origin, font, m_textColor, m_backgroundColor);

View File

@@ -36,20 +36,20 @@ void StackViewController::ControllerView::popStack() {
m_numberOfStacks--;
}
void StackViewController::ControllerView::layoutSubviews() {
void StackViewController::ControllerView::layoutSubviews(bool force) {
KDCoordinate width = m_frame.width();
if (m_displayStackHeaders) {
for (int i=0; i<m_numberOfStacks; i++) {
m_stackViews[i].setFrame(KDRect(0, Metric::StackTitleHeight*i, width, Metric::StackTitleHeight + 1));
m_stackViews[i].setFrame(KDRect(0, Metric::StackTitleHeight*i, width, Metric::StackTitleHeight + 1), force);
}
}
if (m_contentView) {
KDCoordinate separatorHeight = m_numberOfStacks > 0 ? 1 : 0;
KDRect contentViewFrame = KDRect( 0,
KDRect contentViewFrame = KDRect(0,
m_displayStackHeaders * (m_numberOfStacks * Metric::StackTitleHeight + separatorHeight),
width,
m_frame.height() - m_displayStackHeaders * m_numberOfStacks * Metric::StackTitleHeight);
m_contentView->setFrame(contentViewFrame);
m_contentView->setFrame(contentViewFrame, force);
}
}

View File

@@ -66,7 +66,7 @@ View * TabView::subviewAtIndex(int index) {
return &m_cells[index];
}
void TabView::layoutSubviews() {
void TabView::layoutSubviews(bool force) {
KDCoordinate emptyWidth = bounds().width();
for (int i=0; i<m_numberOfTabs; i++) {
emptyWidth -= m_cells[i].minimalSizeForOptimalDisplay().width();
@@ -82,7 +82,7 @@ void TabView::layoutSubviews() {
widthUsed, 0,
tabWidth, m_frame.height() - k_activeTabHeight
);
m_cells[i].setFrame(cellFrame);
m_cells[i].setFrame(cellFrame, force);
widthUsed += tabWidth;
}
}

View File

@@ -17,19 +17,19 @@ void TabViewController::ContentView::setActiveView(View * view) {
markRectAsDirty(bounds());
}
void TabViewController::ContentView::layoutSubviews() {
void TabViewController::ContentView::layoutSubviews(bool force) {
KDRect tabViewFrame = KDRect(
0, 0,
m_frame.width(), Metric::TabHeight
);
m_tabView.setFrame(tabViewFrame);
m_tabView.setFrame(tabViewFrame, force);
if (m_activeView) {
KDRect activeViewFrame = KDRect(
0, Metric::TabHeight,
m_frame.width(),
m_frame.height() - Metric::TabHeight
);
m_activeView->setFrame(activeViewFrame);
m_activeView->setFrame(activeViewFrame, force);
}
}

View File

@@ -2,7 +2,11 @@
#include <escher/palette.h>
#include <escher/metric.h>
static inline KDCoordinate minCoordinate(KDCoordinate x, KDCoordinate y) { return x < y ? x : y; }
static inline KDCoordinate maxCoordinate(KDCoordinate x, KDCoordinate y) { return x > y ? x : y; }
TableCell::TableCell(Layout layout) :
Bordered(),
HighlightCell(),
m_layout(layout)
{
@@ -38,77 +42,131 @@ View * TableCell::subviewAtIndex(int index) {
* margins (like ExpressionView), sometimes the subview has no margins (like
* MessageView) which prevents us to handle margins only here. */
void TableCell::layoutSubviews() {
KDCoordinate withMargin(KDCoordinate length, KDCoordinate margin) {
return length == 0 ? 0 : length + margin;
}
void TableCell::layoutSubviews(bool force) {
/* TODO: this code is awful. However, this should handle multiples cases
* (subviews are not defined, margins are overriden...) */
KDCoordinate width = bounds().width();
KDCoordinate height = bounds().height();
View * label = labelView();
KDSize labelSize = label ? label->minimalSizeForOptimalDisplay() : KDSizeZero;
if (label) {
switch (m_layout) {
case Layout::Vertical:
label->setFrame(KDRect(
k_separatorThickness+k_labelMargin,
k_separatorThickness+Metric::TableCellLabelTopMargin,
width-2*k_separatorThickness-k_labelMargin,
labelSize.height()));
break;
default:
label->setFrame(KDRect(
k_separatorThickness+k_labelMargin,
k_separatorThickness,
labelSize.width(),
height - 2*k_separatorThickness));
break;
}
}
View * accessory = accessoryView();
if (accessory) {
KDSize accessorySize = accessory->minimalSizeForOptimalDisplay();
switch (m_layout) {
case Layout::Vertical:
accessory->setFrame(KDRect(
k_separatorThickness+k_accessoryMargin,
height-k_separatorThickness-accessorySize.height()-k_accessoryBottomMargin,
width-2*k_separatorThickness - k_accessoryMargin,
accessorySize.height()));
break;
default:
// In some cases, the accessory view cannot take all the size it can
KDCoordinate wantedX = width-accessorySize.width()-k_separatorThickness-k_accessoryMargin;
KDCoordinate minX = label ? label->bounds().x()+labelSize.width()+k_labelMargin+k_separatorThickness+k_accessoryMargin : k_accessoryMargin;
if (minX < wantedX) {
accessory->setFrame(KDRect(
wantedX,
k_separatorThickness,
accessorySize.width(),
height-2*k_separatorThickness));
} else {
accessory->setFrame(KDRect(
minX,
k_separatorThickness,
accessorySize.width(),
height-2*k_separatorThickness));
}
break;
}
}
View * subAccessory = subAccessoryView();
if (subAccessory && accessory) {
KDSize accessorySize = accessory->minimalSizeForOptimalDisplay();
KDSize subAccessorySize = subAccessory->minimalSizeForOptimalDisplay();
subAccessory->setFrame(KDRect(width-k_separatorThickness-k_accessoryMargin-accessorySize.width()-subAccessorySize.width(), k_separatorThickness,
subAccessorySize.width(), height-2*k_separatorThickness));
KDSize labelSize = label ? label->minimalSizeForOptimalDisplay() : KDSizeZero;
KDSize accessorySize = accessory ? accessory->minimalSizeForOptimalDisplay() : KDSizeZero;
KDSize subAccessorySize = subAccessory ? subAccessory->minimalSizeForOptimalDisplay() : KDSizeZero;
if (m_layout == Layout::Vertical) {
/*
* Vertically:
* ----------------
* ----------------
* Line separator
* ----------------
* k_verticalMargin
* ----------------
* LABEL
* ----------------
* k_verticalMargin
* ----------------
* .
* . [White space if possible, otherwise LABEL overlaps SUBACCESSORY and so on]
* .
* ----------------
* SUBACCESSORY
* ----------------
* ACCESSORY
* ----------------
* k_verticalMargin
* ----------------
* Line separator
* ----------------
* ----------------
*
*
* Horizontally:
* || Line separator | margin* | SUBVIEW | margin* | Line separator ||
*
* * = margin can either be labelMargin(), accessoryMargin() or k_horizontalMargin depending on the subview
*
* */
KDCoordinate horizontalMargin = k_separatorThickness + labelMargin();
KDCoordinate y = k_separatorThickness;
if (label) {
y += k_verticalMargin;
KDCoordinate labelHeight = minCoordinate(labelSize.height(), height - y - k_separatorThickness - k_verticalMargin);
label->setFrame(KDRect(horizontalMargin, y, width-2*horizontalMargin, labelHeight), force);
y += labelHeight + k_verticalMargin;
}
horizontalMargin = k_separatorThickness + k_horizontalMargin;
y = maxCoordinate(y, height - k_separatorThickness - withMargin(accessorySize.height(), Metric::TableCellVerticalMargin) - withMargin(subAccessorySize.height(), 0));
if (subAccessory) {
KDCoordinate subAccessoryHeight = minCoordinate(subAccessorySize.height(), height - y - k_separatorThickness - Metric::TableCellVerticalMargin);
accessory->setFrame(KDRect(horizontalMargin, y, width - 2*horizontalMargin, subAccessoryHeight), force);
y += subAccessoryHeight;
}
horizontalMargin = k_separatorThickness + accessoryMargin();
y = maxCoordinate(y, height - k_separatorThickness - withMargin(accessorySize.height(), Metric::TableCellVerticalMargin));
if (accessory) {
KDCoordinate accessoryHeight = minCoordinate(accessorySize.height(), height - y - k_separatorThickness - Metric::TableCellVerticalMargin);
accessory->setFrame(KDRect(horizontalMargin, y, width - 2*horizontalMargin, accessoryHeight), force);
}
} else {
/*
* Vertically:
* ----------------
* ----------------
* Line separator
* ----------------
* SUBVIEW
* ----------------
* Line separator
* ----------------
* ----------------
*
* Horizontally:
* || Line separator | Label margin | LABEL | Label margin | ...
* [ White space if possible otherwise the overlap can be from left to
* right subviews or the contrary ]
*
* ... | SUBACCESSORY | ACCESSORY | Accessory margin | Line separator ||
*
* */
KDCoordinate verticalMargin = k_separatorThickness;
KDCoordinate x = 0;
KDCoordinate labelX = k_separatorThickness + labelMargin();
KDCoordinate subAccessoryX = maxCoordinate(k_separatorThickness + k_horizontalMargin, width - k_separatorThickness - withMargin(accessorySize.width(), accessoryMargin()) - withMargin(subAccessorySize.width(), 0));
KDCoordinate accessoryX = maxCoordinate(k_separatorThickness + accessoryMargin(), width - k_separatorThickness - withMargin(accessorySize.width(), accessoryMargin()));
if (label) {
x = labelX;
KDCoordinate labelWidth = minCoordinate(labelSize.width(), width - x - k_separatorThickness - labelMargin());
if (m_layout == Layout::HorizontalRightOverlap) {
labelWidth = minCoordinate(labelWidth, subAccessoryX - x - labelMargin());
}
label->setFrame(KDRect(x, verticalMargin, labelWidth, height-2*verticalMargin), force);
x += labelWidth + labelMargin();
}
if (subAccessory) {
x = maxCoordinate(x, subAccessoryX);
KDCoordinate subAccessoryWidth = minCoordinate(subAccessorySize.width(), width - x - k_separatorThickness - k_horizontalMargin);
if (m_layout == Layout::HorizontalRightOverlap) {
subAccessoryWidth = minCoordinate(subAccessoryWidth, accessoryX - x);
}
subAccessory->setFrame(KDRect(x, verticalMargin, subAccessoryWidth, height-2*verticalMargin), force);
x += subAccessoryWidth;
}
if (accessory) {
x = maxCoordinate(x, accessoryX);
KDCoordinate accessoryWidth = minCoordinate(accessorySize.width(), width - x - k_separatorThickness - accessoryMargin());
accessory->setFrame(KDRect(x, verticalMargin, accessoryWidth, height-2*verticalMargin), force);
}
}
}
void TableCell::drawRect(KDContext * ctx, KDRect rect) const {
KDCoordinate width = bounds().width();
KDCoordinate height = bounds().height();
KDColor backgroundColor = isHighlighted() ? Palette::ListCellBackgroundSelected : Palette::ListCellBackground;
ctx->fillRect(KDRect(k_separatorThickness, k_separatorThickness, width-2*k_separatorThickness, height-k_separatorThickness), backgroundColor);
// Draw rectangle around cell
ctx->fillRect(KDRect(0, 0, width, k_separatorThickness), Palette::ListCellBorder);
ctx->fillRect(KDRect(0, k_separatorThickness, k_separatorThickness, height-k_separatorThickness), Palette::ListCellBorder);
ctx->fillRect(KDRect(width-k_separatorThickness, k_separatorThickness, k_separatorThickness, height-k_separatorThickness), Palette::ListCellBorder);
ctx->fillRect(KDRect(0, height-k_separatorThickness, width, k_separatorThickness), Palette::ListCellBorder);
}
drawInnerRect(ctx, bounds(), backgroundColor);
drawBorderOfRect(ctx, bounds(), Palette::ListCellBorder);
}

View File

@@ -33,7 +33,7 @@ const char * TableView::className() const {
}
#endif
void TableView::layoutSubviews() {
void TableView::layoutSubviews(bool force) {
/* On the one hand, ScrollView::layoutSubviews()
* calls setFrame(...) over m_contentView,
* which typically calls layoutSubviews() over m_contentView.
@@ -49,8 +49,8 @@ void TableView::layoutSubviews() {
* FIXME:
* Finally, this solution is not optimal at all since
* layoutSubviews is called twice over m_contentView. */
m_contentView.layoutSubviews();
ScrollView::layoutSubviews();
m_contentView.layoutSubviews(force);
ScrollView::layoutSubviews(force);
}
void TableView::reloadCellAtLocation(int i, int j) {
@@ -166,7 +166,7 @@ View * TableView::ContentView::subviewAtIndex(int index) {
return m_dataSource->reusableCell(typeIndex, type);
}
void TableView::ContentView::layoutSubviews() {
void TableView::ContentView::layoutSubviews(bool force) {
/* The number of subviews might change during the layouting so it needs to be
* recomputed at each step of the for loop. */
for (int index = 0; index < numberOfSubviews(); index++) {
@@ -174,7 +174,7 @@ void TableView::ContentView::layoutSubviews() {
int i = absoluteColumnNumberFromSubviewIndex(index);
int j = absoluteRowNumberFromSubviewIndex(index);
m_dataSource->willDisplayCellAtLocation((HighlightCell *)cell, i, j);
cell->setFrame(cellFrame(i,j));
cell->setFrame(cellFrame(i,j), force);
}
}

View File

@@ -3,12 +3,15 @@
#include <escher/text_input_helpers.h>
#include <ion/unicode/utf8_decoder.h>
#include <ion/unicode/utf8_helper.h>
#include <poincare/serialization_helper.h>
#include <stddef.h>
#include <assert.h>
#include <limits.h>
static inline const char * maxPointer(const char * x, const char * y) { return x > y ? x : y; }
static inline const char * minPointer(const char * x, const char * y) { return x < y ? x : y; }
static inline size_t minSizeT(size_t x, size_t y) { return x < y ? x : y; }
/* TextArea */
@@ -37,6 +40,12 @@ bool TextArea::handleEventWithText(const char * text, bool indentation, bool for
if (*text == 0) {
return false;
}
// Delete the selected text if needed
if (!contentView()->selectionIsEmpty()) {
deleteSelection();
}
/* Compute the indentation. If the text cannot be inserted with the
* indentation, stop here. */
int spacesCount = 0;
@@ -96,7 +105,16 @@ bool TextArea::handleEventWithText(const char * text, bool indentation, bool for
bool TextArea::handleEvent(Ion::Events::Event event) {
if (m_delegate != nullptr && m_delegate->textAreaDidReceiveEvent(this, event)) {
return true;
} else if (handleBoxEvent(event)) {
}
if (handleBoxEvent(event)) {
return true;
}
if (event == Ion::Events::ShiftLeft || event == Ion::Events::ShiftRight) {
selectLeftRight(event == Ion::Events::ShiftLeft, false);
return true;
}
if (event == Ion::Events::ShiftUp || event == Ion::Events::ShiftDown) {
selectUpDown(event == Ion::Events::ShiftUp);
return true;
} else if (event == Ion::Events::ShiftLeft) {
contentView()->moveCursorGeo(-INT_MAX/2, 0);
@@ -107,21 +125,70 @@ bool TextArea::handleEvent(Ion::Events::Event event) {
} else if (event == Ion::Events::ShiftDown) {
contentView()->moveCursorGeo(0, INT_MAX/2);
} else if (event == Ion::Events::Left) {
if (contentView()->resetSelection()) {
return true;
}
return TextInput::moveCursorLeft();
} else if (event == Ion::Events::Right) {
}
if (event == Ion::Events::Right) {
if (contentView()->resetSelection()) {
return true;
}
return TextInput::moveCursorRight();
}
if (event.hasText()) {
return handleEventWithText(event.text());
}
if (event == Ion::Events::EXE) {
return handleEventWithText("\n");
}
if (event == Ion::Events::Copy || event == Ion::Events::Cut) {
if (contentView()->selectionIsEmpty()) {
return false;
}
const char * start = contentView()->selectionStart();
Clipboard::sharedClipboard()->store(start, contentView()->selectionEnd() - start);
if (event == Ion::Events::Cut) {
deleteSelection();
}
return true;
}
if (event == Ion::Events::Paste) {
return handleEventWithText(Clipboard::sharedClipboard()->storedText(), false, true);
}
// The following events need a scrollToCursor and return true
if (event == Ion::Events::Backspace) {
if (contentView()->selectionIsEmpty()) {
if (!removePreviousGlyph()) {
return false;
}
} else {
deleteSelection();
return true;
}
} else if (event == Ion::Events::Up) {
contentView()->resetSelection();
contentView()->moveCursorGeo(0, -1);
} else if (event == Ion::Events::Down) {
contentView()->resetSelection();
contentView()->moveCursorGeo(0, 1);
<<<<<<< HEAD
} else if (event == Ion::Events::Backspace) {
return removePreviousGlyph();
} else if (event == Ion::Events::EXE) {
return handleEventWithText("\n");
=======
>>>>>>> upstream/master
} else if (event == Ion::Events::Clear) {
if (!contentView()->removeEndOfLine()) {
if (!contentView()->selectionIsEmpty()) {
deleteSelection();
return true;
} else if (!contentView()->removeEndOfLine()) {
contentView()->removeStartOfLine();
}
<<<<<<< HEAD
} else if (event == Ion::Events::Paste) {
return handleEventWithText(Clipboard::sharedClipboard()->storedText());
} else if (event == Ion::Events::Percent) {
@@ -132,6 +199,8 @@ bool TextArea::handleEvent(Ion::Events::Event event) {
} else {
return handleEventWithText(event.text());
}
=======
>>>>>>> upstream/master
} else {
return false;
}
@@ -151,11 +220,11 @@ int TextArea::indentationBeforeCursor() const {
* another code point, until reaching the beginning of the line. */
UTF8Helper::PerformAtCodePoints(const_cast<TextArea *>(this)->contentView()->text(), ' ',
[](int codePointOffset, void * indentationSize, int context1, int context2){
int * castedSize = (int *) indentationSize;
*castedSize = *castedSize + 1;
int * castedSize = (int *) indentationSize;
*castedSize = *castedSize + 1;
},
[](int codePointOffset, void * indentationSize, int context1, int context2){
*((int *) indentationSize) = 0;
*((int *) indentationSize) = 0;
},
&indentationSize, 0, -1, '\n', false, cursorLocation());
return indentationSize;
@@ -233,6 +302,31 @@ CodePoint TextArea::Text::removePreviousGlyph(char * * position) {
return removedCodePoint;
}
size_t TextArea::Text::removeText(const char * start, const char * end) {
assert(start <= end);
assert(start >= m_buffer && end <= m_buffer + m_bufferSize);
char * dst = const_cast<char* >(start);
char * src = const_cast<char* >(end);
size_t delta = src - dst;
if (delta == 0) {
return 0;
}
for (size_t index = src - m_buffer; index < m_bufferSize; index++) {
*dst = *src;
if (*src == 0) {
assert(delta > 0);
return delta;
}
dst++;
src++;
}
assert(false);
return 0;
}
size_t TextArea::Text::removeRemainingLine(const char * location, int direction) {
assert(m_buffer != nullptr);
assert(location >= m_buffer && location <= m_buffer + m_bufferSize);
@@ -258,24 +352,7 @@ size_t TextArea::Text::removeRemainingLine(const char * location, int direction)
}
}
char * dst = const_cast<char* >(direction > 0 ? location : codePointPosition);
char * src = const_cast<char* >(direction > 0 ? codePointPosition : location);
assert(src >= dst);
size_t delta = src - dst;
if (delta == 0) {
return 0;
}
for (size_t index = src - m_buffer; index < m_bufferSize; index++) {
*dst = *src;
if (*src == 0) {
return delta;
}
dst++;
src++;
}
assert(false);
return 0;
return removeText(direction > 0 ? location : codePointPosition, direction > 0 ? codePointPosition : location);
}
/* TextArea::Text::Line */
@@ -351,22 +428,49 @@ void TextArea::ContentView::drawRect(KDContext * ctx, KDRect rect) const {
for (Text::Line line : m_text) {
KDCoordinate width = line.glyphWidth(m_font);
if (y >= topLeft.line() && y <= bottomRight.line() && topLeft.column() < (int)width) {
drawLine(ctx, y, line.text(), line.charLength(), topLeft.column(), bottomRight.column());
drawLine(ctx, y, line.text(), line.charLength(), topLeft.column(), bottomRight.column(), m_selectionStart, m_selectionEnd);
}
y++;
}
}
void TextArea::ContentView::drawStringAt(KDContext * ctx, int line, int column, const char * text, size_t length, KDColor textColor, KDColor backgroundColor) const {
void TextArea::ContentView::drawStringAt(KDContext * ctx, int line, int column, const char * text, int length, KDColor textColor, KDColor backgroundColor, const char * selectionStart, const char * selectionEnd, KDColor backgroundHighlightColor) const {
if (length < 0) {
return;
}
KDSize glyphSize = m_font->glyphSize();
ctx->drawString(
bool drawSelection = selectionStart != nullptr && selectionEnd > text && selectionStart < text + length;
KDPoint nextPoint = ctx->drawString(
text,
KDPoint(column*glyphSize.width(), line*glyphSize.height()),
m_font,
textColor,
backgroundColor,
length
drawSelection ? (selectionStart >= text ? minSizeT(length, selectionStart - text) : 0) : length
);
if (!drawSelection) {
return;
}
const char * highlightedDrawStart = maxPointer(selectionStart, text);
size_t highlightedDrawLength = minSizeT(selectionEnd - highlightedDrawStart, length - (highlightedDrawStart - text));
nextPoint = ctx->drawString(
highlightedDrawStart,
nextPoint,
m_font,
textColor,
backgroundHighlightColor,
highlightedDrawLength);
const char * notHighlightedDrawStart = highlightedDrawStart + highlightedDrawLength;
ctx->drawString(
notHighlightedDrawStart,
nextPoint,
m_font,
textColor,
backgroundColor,
length - (notHighlightedDrawStart - text));
}
KDSize TextArea::ContentView::minimalSizeForOptimalDisplay() const {
@@ -379,12 +483,12 @@ KDSize TextArea::ContentView::minimalSizeForOptimalDisplay() const {
);
}
void TextArea::TextArea::ContentView::setText(char * textBuffer, size_t textBufferSize) {
void TextArea::ContentView::setText(char * textBuffer, size_t textBufferSize) {
m_text.setText(textBuffer, textBufferSize);
m_cursorLocation = text();
}
bool TextArea::TextArea::ContentView::insertTextAtLocation(const char * text, const char * location) {
bool TextArea::ContentView::insertTextAtLocation(const char * text, char * location) {
int textSize = strlen(text);
if (m_text.textLength() + textSize >= m_text.bufferSize() || textSize == 0) {
return false;
@@ -401,12 +505,14 @@ bool TextArea::TextArea::ContentView::insertTextAtLocation(const char * text, co
&lineBreak, 0);
assert(UTF8Helper::CodePointIs(nullLocation, 0));
m_text.insertText(text, nullLocation - text, const_cast<char *>(location));
m_text.insertText(text, nullLocation - text, location);
// Replace System parentheses (used to keep layout tree structure) by normal parentheses
Poincare::SerializationHelper::ReplaceSystemParenthesesByUserParentheses(location, nullLocation - text);
reloadRectFromPosition(location, lineBreak);
return true;
}
bool TextArea::TextArea::ContentView::removePreviousGlyph() {
bool TextArea::ContentView::removePreviousGlyph() {
if (cursorLocation() <= text()) {
assert(cursorLocation() == text());
return false;
@@ -445,6 +551,16 @@ bool TextArea::ContentView::removeStartOfLine() {
return false;
}
size_t TextArea::ContentView::deleteSelection() {
assert(!selectionIsEmpty());
size_t removedLength = m_text.removeText(m_selectionStart, m_selectionEnd);
/* We cannot call resetSelection() because m_selectionStart and m_selectionEnd
* are invalid */
m_selectionStart = nullptr;
m_selectionEnd = nullptr;
return removedLength;
}
KDRect TextArea::ContentView::glyphFrameAtPosition(const char * text, const char * position) const {
assert(text == m_text.text());
KDSize glyphSize = m_font->glyphSize();
@@ -476,3 +592,11 @@ void TextArea::ContentView::moveCursorGeo(int deltaX, int deltaY) {
Text::Position p = m_text.positionAtPointer(cursorLocation());
setCursorLocation(m_text.pointerAtPosition(Text::Position(p.column() + deltaX, p.line() + deltaY)));
}
void TextArea::selectUpDown(bool up) {
const char * previousCursorLocation = contentView()->cursorLocation();
contentView()->moveCursorGeo(0, up ? -1 : 1);
const char * newCursorLocation = contentView()->cursorLocation();
contentView()->addSelection(up ? newCursorLocation : previousCursorLocation, up ? previousCursorLocation : newCursorLocation);
scrollToCursor();
}

View File

@@ -12,14 +12,12 @@ static char s_draftTextBuffer[TextField::maxBufferSize()];
/* TextField::ContentView */
TextField::ContentView::ContentView(char * textBuffer, size_t textBufferSize, size_t draftTextBufferSize, const KDFont * font, float horizontalAlignment, float verticalAlignment, KDColor textColor, KDColor backgroundColor) :
TextInput::ContentView(font),
TextInput::ContentView(font, horizontalAlignment, verticalAlignment),
m_isEditing(false),
m_textBuffer(textBuffer),
m_textBufferSize(textBufferSize),
m_draftTextBufferSize(draftTextBufferSize),
m_currentDraftTextLength(0),
m_horizontalAlignment(horizontalAlignment),
m_verticalAlignment(verticalAlignment),
m_textColor(textColor),
m_backgroundColor(backgroundColor)
{
@@ -47,7 +45,21 @@ void TextField::ContentView::drawRect(KDContext * ctx, KDRect rect) const {
backgroundColor = Palette::BackgroundHard;
}*/
ctx->fillRect(bounds(), backgroundColor);
ctx->drawString(text(), glyphFrameAtPosition(text(), text()).origin(), m_font, m_textColor, backgroundColor);
if (selectionIsEmpty()) {
ctx->drawString(text(), glyphFrameAtPosition(text(), text()).origin(), m_font, m_textColor, backgroundColor);
} else {
int selectionOffset = m_selectionStart - s_draftTextBuffer;
const char * textToDraw = text();
// Draw the non selected text on the left of the selection
ctx->drawString(textToDraw, glyphFrameAtPosition(text(), text()).origin(), m_font, m_textColor, backgroundColor, selectionOffset);
int selectionLength = m_selectionEnd - m_selectionStart;
textToDraw += selectionOffset;
// Draw the selected text
ctx->drawString(text() + selectionOffset, glyphFrameAtPosition(text(), textToDraw).origin(), m_font, m_textColor, Palette::Select, selectionLength);
textToDraw += selectionLength;
// Draw the non selected text on the right of the selection
ctx->drawString(text() + selectionOffset + selectionLength, glyphFrameAtPosition(text(), textToDraw).origin(), m_font, m_textColor, backgroundColor);
}
}
const char * TextField::ContentView::text() const {
@@ -76,16 +88,11 @@ void TextField::ContentView::setText(const char * text) {
markRectAsDirty(bounds());
}
void TextField::ContentView::setAlignment(float horizontalAlignment, float verticalAlignment) {
m_horizontalAlignment = horizontalAlignment;
m_verticalAlignment = verticalAlignment;
markRectAsDirty(bounds());
}
void TextField::ContentView::setEditing(bool isEditing) {
if (m_isEditing == isEditing) {
return;
}
resetSelection();
m_isEditing = isEditing;
m_currentDraftTextLength = strlen(s_draftTextBuffer);
if (m_cursorLocation < s_draftTextBuffer
@@ -106,39 +113,23 @@ void TextField::ContentView::reinitDraftTextBuffer() {
setCursorLocation(s_draftTextBuffer);
}
bool TextField::ContentView::insertTextAtLocation(const char * text, const char * location) {
bool TextField::ContentView::insertTextAtLocation(const char * text, char * location) {
assert(m_isEditing);
int textSize = strlen(text);
if (m_currentDraftTextLength + textSize >= m_draftTextBufferSize || textSize == 0) {
int textLength = strlen(text);
if (m_currentDraftTextLength + textLength >= m_draftTextBufferSize || textLength == 0) {
return false;
}
memmove(const_cast<char *>(location + textSize), location, (s_draftTextBuffer + m_currentDraftTextLength + 1) - location);
memmove(location + textLength, location, (s_draftTextBuffer + m_currentDraftTextLength + 1) - location);
// Caution! One byte will be overridden by the null-terminating char of strlcpy
char * overridenByteLocation = const_cast<char *>(location + strlen(text));
char * overridenByteLocation = location + textLength;
char overridenByte = *overridenByteLocation;
strlcpy(const_cast<char *>(location), text, (s_draftTextBuffer + m_draftTextBufferSize) - location);
strlcpy(location, text, (s_draftTextBuffer + m_draftTextBufferSize) - location);
assert(overridenByteLocation < s_draftTextBuffer + m_draftTextBufferSize);
*overridenByteLocation = overridenByte;
m_currentDraftTextLength += textSize;
UTF8Decoder decoder(s_draftTextBuffer);
const char * codePointPointer = decoder.stringPosition();
CodePoint codePoint = decoder.nextCodePoint();
assert(!codePoint.isCombining());
while (codePoint != UCodePointNull) {
assert(codePointPointer < s_draftTextBuffer + m_draftTextBufferSize);
if (codePoint == '\n') {
assert(UTF8Decoder::CharSizeOfCodePoint('\n') == 1);
*(const_cast<char *>(codePointPointer)) = 0;
m_currentDraftTextLength = codePointPointer - s_draftTextBuffer;
break;
}
codePointPointer = decoder.stringPosition();
codePoint = decoder.nextCodePoint();
}
m_currentDraftTextLength += textLength;
reloadRectFromPosition(m_horizontalAlignment == 0.0f ? location : s_draftTextBuffer);
return true;
@@ -162,19 +153,19 @@ bool TextField::ContentView::removePreviousGlyph() {
reloadRectFromPosition(s_draftTextBuffer);
}
// Remove the glyph if possible
int removedSize = UTF8Helper::RemovePreviousGlyph(s_draftTextBuffer, const_cast<char *>(cursorLocation()));
if (removedSize == 0) {
int removedLength = UTF8Helper::RemovePreviousGlyph(s_draftTextBuffer, const_cast<char *>(cursorLocation()));
if (removedLength == 0) {
assert(cursorLocation() == s_draftTextBuffer);
return false;
}
// Update the draft buffer length
m_currentDraftTextLength-= removedSize;
m_currentDraftTextLength-= removedLength;
assert(s_draftTextBuffer[m_currentDraftTextLength] == 0);
// Set the cursor location and reload the view
assert(cursorLocation() - removedSize >= s_draftTextBuffer);
setCursorLocation(cursorLocation() - removedSize);
assert(cursorLocation() - removedLength >= s_draftTextBuffer);
setCursorLocation(cursorLocation() - removedLength);
if (m_horizontalAlignment == 0.0f) {
reloadRectFromPosition(cursorLocation());
}
@@ -209,12 +200,26 @@ void TextField::ContentView::didModifyTextBuffer() {
layoutSubviews();
}
void TextField::ContentView::layoutSubviews() {
size_t TextField::ContentView::deleteSelection() {
assert(!selectionIsEmpty());
assert(m_isEditing);
size_t removedLength = m_selectionEnd - m_selectionStart;
strlcpy(const_cast<char *>(m_selectionStart), m_selectionEnd, m_draftTextBufferSize - (m_selectionStart - s_draftTextBuffer));
/* We cannot call resetSelection() because m_selectionStart and m_selectionEnd
* are invalid */
m_selectionStart = nullptr;
m_selectionEnd = nullptr;
assert(removedLength <= m_currentDraftTextLength);
m_currentDraftTextLength -= removedLength;
return removedLength;
}
void TextField::ContentView::layoutSubviews(bool force) {
if (!m_isEditing) {
m_cursorView.setFrame(KDRectZero);
m_cursorView.setFrame(KDRectZero, force);
return;
}
TextInput::ContentView::layoutSubviews();
TextInput::ContentView::layoutSubviews(force);
}
KDRect TextField::ContentView::glyphFrameAtPosition(const char * buffer, const char * position) const {
@@ -268,10 +273,6 @@ void TextField::setText(const char * text) {
setCursorLocation(m_contentView.editedText()+strlen(text));
}
void TextField::setAlignment(float horizontalAlignment, float verticalAlignment) {
m_contentView.setAlignment(horizontalAlignment, verticalAlignment);
}
bool TextField::privateHandleEvent(Ion::Events::Event event) {
// Handle Toolbox or Var event
if (handleBoxEvent(event)) {
@@ -298,6 +299,7 @@ bool TextField::privateHandleEvent(Ion::Events::Event event) {
if (m_delegate->textFieldDidFinishEditing(this, m_contentView.editedText(), event)) {
// Clean draft text for next use
reinitDraftTextBuffer();
resetSelection();
/* We allow overscroll to avoid calling layoutSubviews twice because the
* content might have changed. */
reloadScroll(true);
@@ -317,29 +319,36 @@ bool TextField::privateHandleEvent(Ion::Events::Event event) {
return true;
}
if (event == Ion::Events::Backspace && isEditing()) {
return removePreviousGlyph();
if (m_contentView.selectionIsEmpty()) {
return removePreviousGlyph();
}
deleteSelection();
return true;
}
if (event == Ion::Events::Back && isEditing()) {
reinitDraftTextBuffer();
resetSelection();
setEditing(false);
m_delegate->textFieldDidAbortEditing(this);
reloadScroll(true);
return true;
}
if (event == Ion::Events::Clear && isEditing()) {
if (!removeEndOfLine()) {
if (!m_contentView.selectionIsEmpty()) {
deleteSelection();
} else if (!removeEndOfLine()) {
removeWholeText();
}
return true;
}
if (event == Ion::Events::Copy && !isEditing()) {
Clipboard::sharedClipboard()->store(text());
return true;
}
if (event == Ion::Events::Cut && !isEditing()) {
Clipboard::sharedClipboard()->store(text());
reinitDraftTextBuffer();
setEditing(true);
if (event == Ion::Events::Copy || event == Ion::Events::Cut) {
if (storeInClipboard() && event == Ion::Events::Cut) {
if (!m_contentView.selectionIsEmpty()) {
deleteSelection();
} else {
removeWholeText();
}
}
return true;
}
return false;
@@ -404,12 +413,14 @@ bool TextField::handleEvent(Ion::Events::Event event) {
bool didHandleEvent = false;
if (privateHandleMoveEvent(event)) {
didHandleEvent = true;
} else if (privateHandleSelectEvent(event)) {
didHandleEvent = true;
} else if (m_delegate->textFieldDidReceiveEvent(this, event)) {
return true;
} else if (event.hasText()) {
return handleEventWithText(event.text());
} else if (event == Ion::Events::Paste) {
return handleEventWithText(Clipboard::sharedClipboard()->storedText());
return handleEventWithText(Clipboard::sharedClipboard()->storedText(), false, true);
} else if ((event == Ion::Events::OK || event == Ion::Events::EXE) && !isEditing()) {
const char * previousText = m_contentView.text();
setEditing(true);
@@ -429,22 +440,41 @@ void TextField::scrollToCursor() {
return TextInput::scrollToCursor();
}
bool TextField::shouldFinishEditing(Ion::Events::Event event) {
if (m_delegate->textFieldShouldFinishEditing(this, event)) {
resetSelection();
return true;
}
return false;
}
bool TextField::privateHandleMoveEvent(Ion::Events::Event event) {
if (!isEditing()) {
return false;
}
const char * draftBuffer = m_contentView.editedText();
if (event == Ion::Events::Left && cursorLocation() > draftBuffer) {
return TextInput::moveCursorLeft();
if (event == Ion::Events::Left || event == Ion::Events::Right) {
if (!m_contentView.selectionIsEmpty()) {
resetSelection();
return true;
}
if (event == Ion::Events::Left && cursorLocation() > draftBuffer) {
return TextInput::moveCursorLeft();
}
if (event == Ion::Events::Right && cursorLocation() < draftBuffer + draftTextLength()) {
return TextInput::moveCursorRight();
}
}
if (event == Ion::Events::Right && cursorLocation() < draftBuffer + draftTextLength()) {
return TextInput::moveCursorRight();
return false;
}
bool TextField::privateHandleSelectEvent(Ion::Events::Event event) {
if (!isEditing()) {
return false;
}
if (event == Ion::Events::ShiftLeft) {
return setCursorLocation(draftBuffer);
}
if (event == Ion::Events::ShiftRight) {
return setCursorLocation(draftBuffer + draftTextLength());
if (event == Ion::Events::ShiftLeft || event == Ion::Events::ShiftRight || event == Ion::Events::ShiftUp || event == Ion::Events::ShiftDown) {
selectLeftRight(event == Ion::Events::ShiftLeft || event == Ion::Events::ShiftUp, event == Ion::Events::ShiftUp || event == Ion::Events::ShiftDown);
return true;
}
return false;
}
@@ -459,39 +489,60 @@ bool TextField::handleEventWithText(const char * eventText, bool indentation, bo
assert(isEditing());
if (eventText[0] == 0) {
/* For instance, the event might be EXE on a non-editing text field to start
* edition. */
setCursorLocation(m_contentView.editedText());
return m_delegate->textFieldDidHandleEvent(this, true, previousTextLength != 0);
// Delete the selected text if needed
if (!contentView()->selectionIsEmpty()) {
deleteSelection();
}
// Remove the Empty code points
constexpr int bufferSize = TextField::maxBufferSize();
char buffer[bufferSize];
UTF8Helper::CopyAndRemoveCodePoint(buffer, bufferSize, eventText, UCodePointEmpty);
if (eventText[0] != 0) {
// Remove the Empty code points
constexpr int bufferSize = TextField::maxBufferSize();
char buffer[bufferSize];
{
CodePoint c[] = {UCodePointEmpty, '\n'};
bool complete = UTF8Helper::CopyAndRemoveCodePoints(buffer, bufferSize, eventText, c, 2);
/* If the text is too long to be stored in buffer, we do not insert any
* text. This behaviour is consistent with 'insertTextAtLocation'
* behaviour. */
if (!complete) {
return false;
}
}
// Replace System parentheses (used to keep layout tree structure) by normal parentheses
Poincare::SerializationHelper::ReplaceSystemParenthesesByUserParentheses(buffer);
// Replace System parentheses (used to keep layout tree structure) by normal parentheses
Poincare::SerializationHelper::ReplaceSystemParenthesesByUserParentheses(buffer);
const char * nextCursorLocation = m_contentView.editedText() + draftTextLength();
if (insertTextAtLocation(buffer, cursorLocation())) {
/* The cursor position depends on the text as we sometimes want to position
* the cursor at the end of the text and sometimes after the first
* parenthesis. */
nextCursorLocation = cursorLocation();
if (forceCursorRightOfText) {
nextCursorLocation+= strlen(buffer);
} else {
nextCursorLocation+= TextInputHelpers::CursorPositionInCommand(eventText) - eventText;
if (insertTextAtLocation(buffer, const_cast<char *>(cursorLocation()))) {
/* The cursor position depends on the text as we sometimes want to position
* the cursor at the end of the text and sometimes after the first
* parenthesis. */
const char * nextCursorLocation = cursorLocation();
if (forceCursorRightOfText) {
nextCursorLocation+= strlen(buffer);
} else {
nextCursorLocation+= TextInputHelpers::CursorPositionInCommand(eventText) - eventText;
}
setCursorLocation(nextCursorLocation);
}
}
setCursorLocation(nextCursorLocation);
return m_delegate->textFieldDidHandleEvent(this, true, strlen(text()) != previousTextLength);
}
void TextField::removeWholeText() {
reinitDraftTextBuffer();
setEditing(true);
resetSelection();
markRectAsDirty(bounds());
layoutSubviews();
reloadScroll();
}
bool TextField::storeInClipboard() const {
if (!isEditing()) {
Clipboard::sharedClipboard()->store(text());
return true;
} else if (!m_contentView.selectionIsEmpty()) {
const char * start = m_contentView.selectionStart();
Clipboard::sharedClipboard()->store(start, m_contentView.selectionEnd() - start);
return true;
}
return false;
}

View File

@@ -8,6 +8,11 @@
static inline const char * minCharPointer(const char * x, const char * y) { return x < y ? x : y; }
static inline const char * maxCharPointer(const char * x, const char * y) { return x > y ? x : y; }
void TextInput::ContentView::setFont(const KDFont * font) {
m_font = font;
markRectAsDirty(bounds());
}
void TextInput::ContentView::setCursorLocation(const char * location) {
assert(location != nullptr);
assert(location >= editedText());
@@ -16,37 +21,103 @@ void TextInput::ContentView::setCursorLocation(const char * location) {
layoutSubviews();
}
void TextInput::ContentView::setFont(const KDFont * font) {
m_font = font;
markRectAsDirty(bounds());
}
KDRect TextInput::ContentView::cursorRect() {
return glyphFrameAtPosition(editedText(), m_cursorLocation);
}
void TextInput::ContentView::layoutSubviews() {
m_cursorView.setFrame(cursorRect());
}
void TextInput::ContentView::reloadRectFromPosition(const char * position, bool lineBreak) {
markRectAsDirty(dirtyRectFromPosition(position, lineBreak));
}
KDRect TextInput::ContentView::dirtyRectFromPosition(const char * position, bool lineBreak) const {
KDRect glyphRect = glyphFrameAtPosition(text(), position);
KDRect dirtyRect = KDRect(
glyphRect.x(),
glyphRect.y(),
bounds().width() - glyphRect.x(),
glyphRect.height());
if (lineBreak) {
dirtyRect = dirtyRect.unionedWith(
KDRect(0,
glyphRect.bottom() + 1,
bounds().width(),
bounds().height() - glyphRect.bottom() - 1));
void TextInput::ContentView::addSelection(const char * left, const char * right) {
bool emptySelection = selectionIsEmpty();
if (emptySelection) {
m_selectionStart = left;
m_selectionEnd = right;
} else if (left == m_selectionEnd) {
m_selectionEnd = right;
} else if (right == m_selectionStart) {
m_selectionStart = left;
} else if (right == m_selectionEnd) {
if (left >= m_selectionStart) {
m_selectionEnd = left;
} else {
m_selectionEnd = m_selectionStart;
m_selectionStart = left;
}
} else {
assert(left == m_selectionStart);
if (right <= m_selectionEnd) {
m_selectionStart = right;
} else {
m_selectionStart = m_selectionEnd;
m_selectionEnd = right;
}
}
reloadRectFromAndToPositions(left, right);
if (m_selectionStart == m_selectionEnd) {
m_selectionStart = nullptr;
m_selectionEnd = nullptr;
}
}
bool TextInput::ContentView::resetSelection() {
if (selectionIsEmpty()) {
return false;
}
const char * previousStart = m_selectionStart;
const char * previousEnd = m_selectionEnd;
m_selectionStart = nullptr;
m_selectionEnd = nullptr;
reloadRectFromAndToPositions(previousStart, previousEnd);
return true;
}
bool TextInput::ContentView::selectionIsEmpty() const {
assert(m_selectionStart != nullptr || m_selectionEnd == nullptr);
assert(m_selectionEnd != nullptr || m_selectionStart == nullptr);
return m_selectionStart == nullptr;
}
void TextInput::ContentView::setAlignment(float horizontalAlignment, float verticalAlignment) {
m_horizontalAlignment = horizontalAlignment;
m_verticalAlignment = verticalAlignment;
markRectAsDirty(bounds());
}
void TextInput::ContentView::reloadRectFromPosition(const char * position, bool includeFollowingLines) {
markRectAsDirty(dirtyRectFromPosition(position, includeFollowingLines));
}
void TextInput::ContentView::layoutSubviews(bool force) {
m_cursorView.setFrame(cursorRect(), force);
}
void TextInput::ContentView::reloadRectFromAndToPositions(const char * start, const char * end) {
if (start == end) {
return;
}
KDRect startFrame = glyphFrameAtPosition(text(), start);
KDRect endFrame = glyphFrameAtPosition(text(), end);
bool onSameLine = startFrame.y() == endFrame.y();
markRectAsDirty(KDRect(
onSameLine ? startFrame.x() : 0,
startFrame.y(),
onSameLine ? endFrame.right() - startFrame.left() : bounds().width(),
endFrame.bottom() - startFrame.top() + 1));
}
KDRect TextInput::ContentView::dirtyRectFromPosition(const char * position, bool includeFollowingLines) const {
KDRect glyphRect = glyphFrameAtPosition(text(), position);
if (!includeFollowingLines) {
KDRect dirtyRect = KDRect(
glyphRect.x(),
glyphRect.y(),
bounds().width() - glyphRect.x(),
glyphRect.height());
return dirtyRect;
}
KDRect dirtyRect = KDRect(
0,
glyphRect.y(),
bounds().width(),
bounds().height() - glyphRect.y());
return dirtyRect;
}
@@ -58,6 +129,15 @@ bool TextInput::removePreviousGlyph() {
return true;
}
bool TextInput::setCursorLocation(const char * location) {
assert(location != nullptr);
const char * adjustedLocation = maxCharPointer(location, text());
willSetCursorLocation(&adjustedLocation);
contentView()->setCursorLocation(adjustedLocation);
scrollToCursor();
return true;
}
void TextInput::scrollToCursor() {
/* Technically, we do not need to overscroll in text input. However, we should
* layout the scroll view before calling scrollToContentRect (in case the size
@@ -70,16 +150,27 @@ void TextInput::scrollToCursor() {
scrollToContentRect(contentView()->cursorRect(), true);
}
bool TextInput::setCursorLocation(const char * location) {
assert(location != nullptr);
const char * adjustedLocation = maxCharPointer(location, text());
willSetCursorLocation(&adjustedLocation);
contentView()->setCursorLocation(adjustedLocation);
void TextInput::deleteSelection() {
ContentView * cv = contentView();
assert(!cv->selectionIsEmpty());
const float horizontalAlignment = cv->horizontalAlignment();
if (horizontalAlignment == 0.0f) {
cv->reloadRectFromPosition(cv->selectionStart(), true);
}
bool cursorIsAtEndOfSelection = cv->selectionEnd() == cv->cursorLocation();
size_t removedLength = cv->deleteSelection();
if (cursorIsAtEndOfSelection) {
setCursorLocation(cv->cursorLocation() - removedLength);
}
layoutSubviews(true); // Set the cursor frame by calling the subviews relayouting
scrollToCursor();
return true;
}
bool TextInput::insertTextAtLocation(const char * text, const char * location) {
void TextInput::setAlignment(float horizontalAlignment, float verticalAlignment) {
contentView()->setAlignment(horizontalAlignment, verticalAlignment);
}
bool TextInput::insertTextAtLocation(const char * text, char * location) {
if (contentView()->insertTextAtLocation(text, location)) {
/* We layout the scrollable view before scrolling to cursor because the
* content size might have changed. */
@@ -115,6 +206,28 @@ bool TextInput::moveCursorRight() {
return setCursorLocation(decoder.nextGlyphPosition());
}
bool TextInput::selectLeftRight(bool left, bool all) {
const char * cursorLoc = cursorLocation();
const char * nextCursorLoc = nullptr;
if (!all) {
bool moved = left ? moveCursorLeft() : moveCursorRight();
if (!moved) {
return false;
}
nextCursorLoc = cursorLocation();
} else {
const char * t = text();
nextCursorLoc = left ? t : t + strlen(t);
willSetCursorLocation(&nextCursorLoc);
if (cursorLoc == nextCursorLoc) {
return false;
}
setCursorLocation(nextCursorLoc);
}
contentView()->addSelection(left ? nextCursorLoc : cursorLoc, left ? cursorLoc : nextCursorLoc);
return true;
}
bool TextInput::privateRemoveEndOfLine() {
return contentView()->removeEndOfLine();
}

View File

@@ -3,18 +3,6 @@ extern "C" {
}
#include <escher/view.h>
View::View() :
m_frame(KDRectZero),
m_superview(nullptr),
m_dirtyRect(KDRectZero)
{
}
void View::drawRect(KDContext * ctx, KDRect rect) const {
// By default, a view doesn't do anything
// It's transparent!
}
const Window * View::window() const {
if (m_superview == nullptr) {
return nullptr;
@@ -105,11 +93,11 @@ View * View::subview(int index) {
}
void View::setSize(KDSize size) {
setFrame(KDRect(m_frame.origin(), size));
setFrame(KDRect(m_frame.origin(), size), false);
}
void View::setFrame(KDRect frame) {
if (frame == m_frame) {
void View::setFrame(KDRect frame, bool force) {
if (frame == m_frame && !force) {
return;
}
/* CAUTION: This code is not resilient to multiple consecutive setFrame()
@@ -133,7 +121,7 @@ void View::setFrame(KDRect frame) {
// FIXME: m_dirtyRect = bounds(); would be more correct (in case the view is being shrinked)
if (!m_frame.isEmpty()) {
layoutSubviews();
layoutSubviews(force);
}
}
@@ -167,14 +155,6 @@ KDRect View::absoluteVisibleFrame() const {
}
}
KDSize View::minimalSizeForOptimalDisplay() const {
return KDSizeZero;
}
void View::layoutSubviews() {
}
#if ESCHER_VIEW_LOGGING
const char * View::className() const {
return "View";

View File

@@ -25,18 +25,18 @@ View * WarningController::ContentView::subviewAtIndex(int index) {
return views[index];
}
void WarningController::ContentView::layoutSubviews() {
void WarningController::ContentView::layoutSubviews(bool force) {
if (numberOfSubviews() == 1) {
m_textView1.setFrame(bounds());
m_textView1.setFrame(bounds(), force);
m_textView1.setAlignment(k_middleAlignment, k_middleAlignment);
return;
}
assert(numberOfSubviews() == 2);
KDRect fullBounds = bounds();
KDCoordinate halfHeight = fullBounds.height()/2;
m_textView1.setFrame(KDRect(fullBounds.topLeft(), fullBounds.width(), halfHeight));
m_textView1.setFrame(KDRect(fullBounds.topLeft(), fullBounds.width(), halfHeight), force);
m_textView1.setAlignment(k_middleAlignment, k_shiftedAlignment);
m_textView2.setFrame(KDRect(fullBounds.left(), fullBounds.top()+halfHeight, fullBounds.width(), halfHeight));
m_textView2.setFrame(KDRect(fullBounds.left(), fullBounds.top()+halfHeight, fullBounds.width(), halfHeight), force);
}
KDSize WarningController::ContentView::minimalSizeForOptimalDisplay() const {

View File

@@ -31,9 +31,9 @@ View * Window::subviewAtIndex(int index) {
return m_contentView;
}
void Window::layoutSubviews() {
void Window::layoutSubviews(bool force) {
if (m_contentView != nullptr) {
m_contentView->setFrame(bounds());
m_contentView->setFrame(bounds(), force);
}
}

View File

@@ -0,0 +1,235 @@
#include <quiz.h>
#include <escher.h>
#include <assert.h>
void assert_events_lead_to_selection(const Ion::Events::Event * events, int numberOfEvents, const char * selectedParsedAndSerializedText) {
LayoutField field = LayoutField(nullptr, nullptr);
field.setEditing(true);
for (int i = 0; i < numberOfEvents; i++) {
field.handleEvent(events[i]);
}
Clipboard::sharedClipboard()->reset();
field.handleEvent(Ion::Events::Copy);
quiz_assert(strcmp(Clipboard::sharedClipboard()->storedText(), selectedParsedAndSerializedText) == 0);
}
QUIZ_CASE(escher_layout_field_select_left_right) {
// 123
{
// Select in a simple horizontal layout towards the left
const int eventsCount = 6;
Ion::Events::Event events[eventsCount] = {
Ion::Events::One, Ion::Events::Two, Ion::Events::Three,
Ion::Events::ShiftLeft, Ion::Events::ShiftLeft, Ion::Events::ShiftLeft};
assert_events_lead_to_selection(events, eventsCount - 2, "3");
assert_events_lead_to_selection(events, eventsCount - 1, "23");
assert_events_lead_to_selection(events, eventsCount, "123");
}
{
// Select in a simple horizontal layout towards the right
const int eventsCount = 9;
Ion::Events::Event events[eventsCount] = {
Ion::Events::One, Ion::Events::Two, Ion::Events::Three,
Ion::Events::Left, Ion::Events::Left, Ion::Events::Left,
Ion::Events::ShiftRight, Ion::Events::ShiftRight, Ion::Events::ShiftRight};
assert_events_lead_to_selection(events, eventsCount - 2, "1");
assert_events_lead_to_selection(events, eventsCount - 1, "12");
assert_events_lead_to_selection(events, eventsCount, "123");
}
{
// Select in a simple horizontal layout left then right
const int eventsCount = 7;
Ion::Events::Event events[eventsCount] = {
Ion::Events::One, Ion::Events::Two, Ion::Events::Three,
Ion::Events::Left, Ion::Events::ShiftLeft, Ion::Events::ShiftLeft,
Ion::Events::ShiftRight};
assert_events_lead_to_selection(events, eventsCount, "2");
}
{
/* Select in a simple horizontal layout towards the right and hit the end of
* layout. */
const int eventsCount = 11;
Ion::Events::Event events[eventsCount] = {
Ion::Events::One, Ion::Events::Two, Ion::Events::Three,
Ion::Events::Left, Ion::Events::Left, Ion::Events::Left,
Ion::Events::ShiftRight, Ion::Events::ShiftRight, Ion::Events::ShiftRight,
Ion::Events::ShiftRight, Ion::Events::ShiftRight};
assert_events_lead_to_selection(events, eventsCount, "123");
}
{
/* Select in a simple horizontal layout towards the right, hit the end of
* layout and select left. */
const int eventsCount = 11;
Ion::Events::Event events[eventsCount] = {
Ion::Events::One, Ion::Events::Two, Ion::Events::Three,
Ion::Events::Left, Ion::Events::Left, Ion::Events::Left,
Ion::Events::ShiftRight, Ion::Events::ShiftRight, Ion::Events::ShiftRight,
Ion::Events::ShiftRight, Ion::Events::ShiftLeft};
assert_events_lead_to_selection(events, eventsCount, "12");
}
/* g34
* 1+---+78
* h56 */
{
/* Select in a horizontal layout with a fraction, starting outside the
* fraction. */
const int eventsCount = 17;
Ion::Events::Event events[eventsCount] = {
Ion::Events::One, Ion::Events::Plus, Ion::Events::LowerG,
Ion::Events::Three, Ion::Events::Four, Ion::Events::Division,
Ion::Events::LowerH, Ion::Events::Five, Ion::Events::Six,
Ion::Events::Right, Ion::Events::Plus, Ion::Events::Seven,
Ion::Events::Eight, Ion::Events::ShiftLeft, Ion::Events::ShiftLeft,
Ion::Events::ShiftLeft, Ion::Events::ShiftLeft};
assert_events_lead_to_selection(events, eventsCount, "\x12\x12g34\x13/\x12h56\x13\x13+78");
}
{
/* Select in a horizontal layout with a fraction, starting inside the
* fraction. */
const int eventsCount = 21;
Ion::Events::Event events[eventsCount] = {
Ion::Events::One, Ion::Events::Plus, Ion::Events::LowerG,
Ion::Events::Three, Ion::Events::Four, Ion::Events::Division,
Ion::Events::LowerH, Ion::Events::Five, Ion::Events::Six,
Ion::Events::Right, Ion::Events::Plus, Ion::Events::Seven,
Ion::Events::Eight, Ion::Events::Left, Ion::Events::Left,
Ion::Events::Left, Ion::Events::Left, Ion::Events::Left,
Ion::Events::ShiftLeft, Ion::Events::ShiftLeft, Ion::Events::ShiftLeft,
};
assert_events_lead_to_selection(events, eventsCount, "g34/h56");
}
}
QUIZ_CASE(escher_layout_field_select_up_down) {
// 12
{
// Select in a simple horizontal layout up
const int eventsCount = 5;
Ion::Events::Event events[eventsCount] = {
Ion::Events::One, Ion::Events::Two, Ion::Events::ShiftLeft,
Ion::Events::ShiftUp};
assert_events_lead_to_selection(events, eventsCount, "2");
}
{
// Select in a simple horizontal layout up
const int eventsCount = 5;
Ion::Events::Event events[eventsCount] = {
Ion::Events::One, Ion::Events::Two, Ion::Events::ShiftLeft,
Ion::Events::ShiftDown};
assert_events_lead_to_selection(events, eventsCount, "2");
}
/* 123
* ---
* 456 */
{
// Select up in a fraction
const int eventsCount = 11;
Ion::Events::Event events[eventsCount] = {
Ion::Events::One, Ion::Events::Two, Ion::Events::Three,
Ion::Events::Division, Ion::Events::Four, Ion::Events::Five,
Ion::Events::Six, Ion::Events::Left, Ion::Events::ShiftLeft,
Ion::Events::ShiftUp, Ion::Events::ShiftRight};
assert_events_lead_to_selection(events, eventsCount - 1, "123/456");
assert_events_lead_to_selection(events, eventsCount, "");
}
{
// Select down in a fraction
const int eventsCount = 12;
Ion::Events::Event events[eventsCount] = {
Ion::Events::One, Ion::Events::Two, Ion::Events::Three,
Ion::Events::Division, Ion::Events::Four, Ion::Events::Five,
Ion::Events::Six, Ion::Events::Left, Ion::Events::Up,
Ion::Events::ShiftLeft, Ion::Events::ShiftDown, Ion::Events::ShiftLeft};
assert_events_lead_to_selection(events, eventsCount - 1, "123/456");
assert_events_lead_to_selection(events, eventsCount, "");
}
/* 123
* 89+---
* 456 */
{
// Select up next to a fraction (cursor on fraction)
const int eventsCount = 15;
Ion::Events::Event events[eventsCount] = {
Ion::Events::Eight, Ion::Events::Nine, Ion::Events::Plus,
Ion::Events::One, Ion::Events::Two, Ion::Events::Three,
Ion::Events::Division, Ion::Events::Four, Ion::Events::Five,
Ion::Events::Six, Ion::Events::Left, Ion::Events::Left,
Ion::Events::Left, Ion::Events::Left, Ion::Events::ShiftUp};
assert_events_lead_to_selection(events, eventsCount, "");
}
{
// Select down next to a fraction (cursor not on the fraction)
const int eventsCount = 17;
Ion::Events::Event events[eventsCount] = {
Ion::Events::Eight, Ion::Events::Nine, Ion::Events::Plus,
Ion::Events::One, Ion::Events::Two, Ion::Events::Three,
Ion::Events::Division, Ion::Events::Four, Ion::Events::Five,
Ion::Events::Six, Ion::Events::Left, Ion::Events::Left,
Ion::Events::Left, Ion::Events::Left, Ion::Events::Left,
Ion::Events::Right, Ion::Events::ShiftDown};
assert_events_lead_to_selection(events, eventsCount, "");
}
/* 12345
* -----
* ghi
* ----
* k|lm */
{
// Select up next to a fraction (cursor on fraction)
const int eventsCount = 17;
Ion::Events::Event events[eventsCount] = {
Ion::Events::One, Ion::Events::Two, Ion::Events::Three,
Ion::Events::Four, Ion::Events::Five, Ion::Events::Division,
Ion::Events::LowerG, Ion::Events::LowerH, Ion::Events::LowerI,
Ion::Events::Division, Ion::Events::LowerK, Ion::Events::LowerL,
Ion::Events::LowerM, Ion::Events::Left, Ion::Events::Left,
Ion::Events::ShiftUp, Ion::Events::ShiftUp
};
assert_events_lead_to_selection(events, eventsCount - 1, "ghi/klm");
assert_events_lead_to_selection(events, eventsCount, "12345/\x12ghi/klm\x13");
}
}
QUIZ_CASE(escher_layout_field_unselect) {
// 1234
{
// Move left after selecting
const int eventsCount = 8;
Ion::Events::Event events[eventsCount] = {
Ion::Events::One, Ion::Events::Two, Ion::Events::Three,
Ion::Events::Four, Ion::Events::ShiftLeft, Ion::Events::Left,
Ion::Events::Left, Ion::Events::ShiftRight};
assert_events_lead_to_selection(events, eventsCount, "3");
}
}
QUIZ_CASE(escher_layout_field_delete) {
// 1234
{
// Delete the selection
const int eventsCount = 17;
Ion::Events::Event events[eventsCount] = {
Ion::Events::One, Ion::Events::Two, Ion::Events::Three,
Ion::Events::Four, Ion::Events::Left, Ion::Events::ShiftLeft,
Ion::Events::ShiftLeft, Ion::Events::Backspace, Ion::Events::Left,
Ion::Events::ShiftRight, Ion::Events::ShiftRight
};
assert_events_lead_to_selection(events, eventsCount, "14");
}
{
// Delete the by adding text
const int eventsCount = 19;
Ion::Events::Event events[eventsCount] = {
Ion::Events::One, Ion::Events::Two, Ion::Events::Three,
Ion::Events::Four, Ion::Events::Left, Ion::Events::ShiftLeft,
Ion::Events::ShiftLeft, Ion::Events::Zero, Ion::Events::Left,
Ion::Events::Left, Ion::Events::ShiftRight, Ion::Events::ShiftRight,
Ion::Events::ShiftRight
};
assert_events_lead_to_selection(events, eventsCount, "104");
}
}