Add Cell, CellModel
diff --git a/ide/Cell.cpp b/ide/Cell.cpp
new file mode 100644
index 0000000..2fd964b
--- /dev/null
+++ b/ide/Cell.cpp
@@ -0,0 +1,49 @@
+#include "Cell.h"
+
+Cell::Cell(QObject *parent) : QObject(parent)
+{
+
+}
+
+Cell::Cell(const Cell &copy, QObject *parent)
+    : Cell(parent)
+{
+    *this = copy;
+}
+
+Cell::Cell(QString code, QString result, QObject *parent)
+    : Cell(parent)
+{
+    setCode(code);
+    setResult(result);
+}
+
+Cell &Cell::operator =(const Cell &copy)
+{
+    setCode(copy.code());
+    setResult(copy.result());
+
+    return *this;
+}
+
+QString Cell::code() const
+{
+    return _code;
+}
+
+QString Cell::result() const
+{
+    return _result;
+}
+
+void Cell::setCode(QString code)
+{
+    _code = code;
+    emit codeChanged(code);
+}
+
+void Cell::setResult(QString result)
+{
+    _result = result;
+    emit resultChanged(result);
+}
diff --git a/ide/Cell.h b/ide/Cell.h
new file mode 100644
index 0000000..e3d1f9d
--- /dev/null
+++ b/ide/Cell.h
@@ -0,0 +1,35 @@
+#pragma once
+
+#include <QObject>
+#include <qqml.h>
+
+class Cell : public QObject
+{
+    Q_OBJECT
+    QML_ELEMENT
+
+    Q_PROPERTY(QString code READ code WRITE setCode NOTIFY codeChanged)
+    Q_PROPERTY(QString result READ result WRITE setResult NOTIFY resultChanged)
+
+public:
+    explicit Cell(QObject *parent = nullptr);
+    Cell(const Cell &copy, QObject *parent = nullptr);
+    Cell(QString code, QString result, QObject *parent = nullptr);
+
+    Cell &operator =(const Cell &copy);
+
+    QString code() const;
+    QString result() const;
+
+    void setCode(QString code);
+    void setResult(QString result);
+
+signals:
+    void codeChanged(QString code);
+    void resultChanged(QString result);
+
+private:
+    QString _code, _result;
+};
+
+Q_DECLARE_METATYPE(Cell)
diff --git a/ide/CellModel.cpp b/ide/CellModel.cpp
new file mode 100644
index 0000000..911c9a9
--- /dev/null
+++ b/ide/CellModel.cpp
@@ -0,0 +1,117 @@
+#include "CellModel.h"
+
+CellModel::CellModel(QObject *parent)
+    : QAbstractListModel(parent)
+{
+}
+
+CellModel::CellModel(const CellModel &model, QObject *parent)
+    : CellModel(parent)
+{
+    _cells = model._cells;
+}
+
+int CellModel::rowCount(const QModelIndex &parent) const
+{
+    // For list models only the root node (an invalid parent) should return the list's size. For all
+    // other (valid) parents, rowCount() should return 0 so that it does not become a tree model.
+    if (parent.isValid())
+        return 0;
+
+    return _cells.size();
+}
+
+QVariant CellModel::data(const QModelIndex &index, int role) const
+{
+    if (!index.isValid())
+        return QVariant();
+
+    switch (role)
+    {
+    case CodeRole:
+        return _cells[index.row()].code();
+    case ResultRole:
+        return _cells[index.row()].result();
+    default:
+        return QVariant();
+    }
+}
+
+bool CellModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+    if (data(index, role) != value)
+    {
+        switch (role)
+        {
+        case CodeRole:
+            _cells[index.row()].setCode(value.toString());
+            break;
+        case ResultRole:
+            _cells[index.row()].setResult(value.toString());
+            break;
+        default:
+            return false;
+        }
+
+        emit dataChanged(index, index, QVector<int>() << role);
+
+        return true;
+    }
+
+    return false;
+}
+
+Qt::ItemFlags CellModel::flags(const QModelIndex &index) const
+{
+    if (!index.isValid())
+        return Qt::NoItemFlags;
+
+    return Qt::ItemIsEditable; // FIXME: Implement me!
+}
+
+bool CellModel::insertRows(int row, int count, const QModelIndex &parent)
+{
+    beginInsertRows(parent, row, row + count - 1);
+
+    for (int i = 0; i < count; i++)
+        _cells.insert(row, Cell());
+
+    endInsertRows();
+
+    return false;
+}
+
+bool CellModel::removeRows(int row, int count, const QModelIndex &parent)
+{
+    beginRemoveRows(parent, row, row + count - 1);
+
+    for (int i = 0; i < count; i++)
+        _cells.removeAt(row);
+
+    endRemoveRows();
+
+    return true;
+}
+
+QHash<int, QByteArray> CellModel::roleNames() const
+{
+    return {
+        {CodeRole, "code"},
+        {ResultRole, "result"},
+    };
+}
+
+void CellModel::addCell(const Cell &cell)
+{
+    int i = _cells.size();
+
+    insertRows(i, 1, QModelIndex());
+
+    _cells[i] = cell;
+    emit dataChanged(index(i), index(i), {CodeRole, ResultRole});
+}
+
+void CellModel::addCell(QString code, QString result)
+{
+    addCell(Cell(code, result));
+}
diff --git a/ide/CellModel.h b/ide/CellModel.h
new file mode 100644
index 0000000..3920e38
--- /dev/null
+++ b/ide/CellModel.h
@@ -0,0 +1,49 @@
+#pragma once
+
+#include <QAbstractListModel>
+#include <qqml.h>
+
+#include "Cell.h"
+
+class CellModel : public QAbstractListModel
+{
+    Q_OBJECT
+    QML_ELEMENT
+
+public:
+    explicit CellModel(QObject *parent = nullptr);
+    CellModel(const CellModel &model, QObject *parent = nullptr);
+
+    enum CellRoles
+    {
+        CodeRole = Qt::UserRole + 1,
+        ResultRole
+    };
+
+    // Basic functionality:
+    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+
+    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+
+    // Editable:
+    Q_INVOKABLE bool setData(const QModelIndex &index, const QVariant &value,
+                             int role = Qt::EditRole) override;
+
+    Qt::ItemFlags flags(const QModelIndex& index) const override;
+
+    // Add data:
+    Q_INVOKABLE bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
+
+    // Remove data:
+    Q_INVOKABLE bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
+
+    QHash<int, QByteArray> roleNames() const override;
+
+    Q_INVOKABLE void addCell(const Cell &cell);
+    Q_INVOKABLE void addCell(QString code, QString result);
+
+private:
+    QList<Cell> _cells;
+};
+
+Q_DECLARE_METATYPE(CellModel)
diff --git a/ide/IDE.pri b/ide/IDE.pri
index e430568..906b3cb 100644
--- a/ide/IDE.pri
+++ b/ide/IDE.pri
@@ -1,7 +1,7 @@
 QT += quick quickcontrols2
 QT -= core
 
-CONFIG +=
+CONFIG += qmltypes
 
 QML_IMPORT_NAME = sh.swisschili.REFAL
 QML_IMPORT_MAJOR_VERSION = 1
@@ -14,10 +14,16 @@
 QML_DESIGNER_IMPORT_PATH =
 
 SOURCES += \
+    $$PWD/Cell.cpp \
+    $$PWD/CellModel.cpp \
     $$PWD/IdeMain.cpp
 
 HEADERS += \
+    $$PWD/Cell.h \
+    $$PWD/CellModel.h \
     $$PWD/IdeMain.h
 
 RESOURCES += \
     $$PWD/resources.qrc
+
+INCLUDEPATH += $$PWD
diff --git a/ide/qml/InsertRow.qml b/ide/qml/InsertRow.qml
index b12896a..00bad8b 100644
--- a/ide/qml/InsertRow.qml
+++ b/ide/qml/InsertRow.qml
@@ -6,7 +6,7 @@
 Rectangle {
     id: root
 
-    color: Material.color(Material.Grey, Material.Shade800)
+    color: Material.color(Material.Grey, Material.theme == Material.Dark ? Material.Shade800 : Material.Shade300)
     height: 2
     Layout.fillWidth: true
     Layout.topMargin: 14
@@ -55,7 +55,7 @@
             id: addButton
             anchors.centerIn: parent
             icon.source: "qrc:///icons/add.svg"
-            icon.color: Material.color(Material.Grey, Material.Shade400)
+            icon.color: Material.color(Material.Grey, Material.theme == Material.Dark ? Material.Shade400 : Material.Shade600)
             flat: true
         }
     }
diff --git a/ide/qml/Cell.qml b/ide/qml/NotebookCell.qml
similarity index 74%
rename from ide/qml/Cell.qml
rename to ide/qml/NotebookCell.qml
index d060dee..a441b73 100644
--- a/ide/qml/Cell.qml
+++ b/ide/qml/NotebookCell.qml
@@ -6,6 +6,9 @@
 ColumnLayout {
     id: root
 
+    required property string code
+    required property string result
+
     RowLayout {
         Layout.fillWidth: true
 
@@ -25,15 +28,21 @@
                 Layout.fillHeight: true
                 id: code
                 font.family: "monospace"
-                text: "Hello"
+                text: root.code
                 selectByMouse: true
                 wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere
+
+                Keys.onTabPressed: {
+                    var pos = cursorPosition + 4
+                    text += "    ";
+                    cursorPosition = pos
+                }
             }
 
             Label {
                 Layout.fillWidth: true
                 font.family: "monospace"
-                text: "Result\nasdfasdf\nasdad"
+                text: root.result
             }
         }
     }
diff --git a/ide/qml/main.qml b/ide/qml/main.qml
index d3bca25..73a4581 100644
--- a/ide/qml/main.qml
+++ b/ide/qml/main.qml
@@ -3,6 +3,8 @@
 import QtQuick.Controls.Material 2.0
 import QtQuick.Layouts 1.3
 
+import sh.swisschili.REFAL 1.0
+
 ApplicationWindow {
     id: root
     width: 1080
@@ -10,9 +12,19 @@
     title: "Notebook"
     visible: true
 
-    Material.theme: Material.Dark
+    Material.theme: Material.Light
     Material.accent: Material.Orange
 
+    CellModel {
+        id: model
+    }
+
+    Component.onCompleted: {
+        model.addCell("Refal { = Hi!; }", "");
+        model.addCell("<Refal>", "Hi!");
+        model.addCell("Hello there", "Hello there");
+    }
+
     ColumnLayout {
         id: column
         anchors.fill: parent
@@ -45,10 +57,10 @@
                 id: codeEditor
                 SplitView.fillWidth: true
                 SplitView.minimumWidth: 400
-                model: 3
+                model: model
                 clip: true
 
-                delegate: Cell {
+                delegate: NotebookCell {
                     width: codeEditor.width - 5
                 }
             }
diff --git a/ide/resources.qrc b/ide/resources.qrc
index bab3643..f48925a 100644
--- a/ide/resources.qrc
+++ b/ide/resources.qrc
@@ -1,7 +1,7 @@
 <RCC>
     <qresource prefix="/">
         <file>qml/main.qml</file>
-        <file>qml/Cell.qml</file>
+        <file>qml/NotebookCell.qml</file>
         <file>icons/play-circle.svg</file>
         <file>icons/add.svg</file>
         <file>qml/InsertRow.qml</file>