Add recent file view, implement runtime options
diff --git a/ide/IDE.pri b/ide/IDE.pri
index 69856d7..d086a61 100644
--- a/ide/IDE.pri
+++ b/ide/IDE.pri
@@ -18,14 +18,16 @@
     $$PWD/CellModel.cpp \
     $$PWD/IdeMain.cpp \
     $$PWD/NbRuntime.cpp \
-    $$PWD/Notebook.cpp
+    $$PWD/Notebook.cpp \
+    $$PWD/RecentModel.cpp
 
 HEADERS += \
     $$PWD/Cell.h \
     $$PWD/CellModel.h \
     $$PWD/IdeMain.h \
     $$PWD/NbRuntime.h \
-    $$PWD/Notebook.h
+    $$PWD/Notebook.h \
+    $$PWD/RecentModel.h
 
 RESOURCES += \
     $$PWD/resources.qrc
diff --git a/ide/IdeMain.cpp b/ide/IdeMain.cpp
index ff8025f..7efe5fa 100644
--- a/ide/IdeMain.cpp
+++ b/ide/IdeMain.cpp
@@ -5,6 +5,7 @@
 #include <QTranslator>
 
 #include "CellModel.h"
+#include "RecentModel.h"
 
 int ideMain(Application *app)
 {
@@ -14,11 +15,17 @@
 
     qRegisterMetaType<CellModel>();
     qRegisterMetaType<CellModel *>();
+    qRegisterMetaType<RecentModel>();
+    qRegisterMetaType<RecentModel *>();
 
     QTranslator translator;
     qInfo() << "loading translations" << translator.load(QLocale(), "refal", "_", ":/ts/", ".qm");
     app->installTranslator(&translator);
 
+    app->setOrganizationName("swissChili");
+    app->setOrganizationDomain("swisschili.sh");
+    app->setApplicationName("REFAL Studio");
+
     const QUrl url(QStringLiteral("qrc:/qml/main.qml"));
     QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                      app, [url](QObject *obj, const QUrl &objUrl)
diff --git a/ide/NbRuntime.cpp b/ide/NbRuntime.cpp
index a2111a7..debda5b 100644
--- a/ide/NbRuntime.cpp
+++ b/ide/NbRuntime.cpp
@@ -10,6 +10,12 @@
     StdLib().load(_eval);
 }
 
+void NbRuntime::reset()
+{
+    _eval.reset();
+    _ctx = {};
+}
+
 void NbRuntime::queueCell(Cell *cell)
 {
     if (!_cells.contains(cell))
diff --git a/ide/NbRuntime.h b/ide/NbRuntime.h
index e7a4b3d..d98daa3 100644
--- a/ide/NbRuntime.h
+++ b/ide/NbRuntime.h
@@ -16,6 +16,8 @@
 public:
     explicit NbRuntime(QObject *parent = nullptr);
 
+    void reset();
+
 public slots:
     void queueCell(Cell *cell);
     void unqueueCell(Cell *cell);
diff --git a/ide/Notebook.cpp b/ide/Notebook.cpp
index 36cc0a3..a5baa0c 100644
--- a/ide/Notebook.cpp
+++ b/ide/Notebook.cpp
@@ -56,6 +56,21 @@
 void Notebook::quitCell(QUuid uuid)
 {
     _rt->unqueueCell(Cell::cellFromUuid(uuid));
+    _runningAll = false;
+}
+
+void Notebook::runAll()
+{
+    if (_cells.size() > 0)
+    {
+        _rt->queueCell(_cells.first());
+        _runningAll = true;
+    }
+}
+
+void Notebook::reset()
+{
+    _rt->reset();
 }
 
 void Notebook::fromJson(QJsonDocument doc)
@@ -118,7 +133,7 @@
 {
     if (_savePath == "")
     {
-        setSavePath(QFileDialog::getSaveFileName(nullptr, "Open Refal Notebook", "", "Refal Notebooks (*.refnb)"));
+        setSavePath(QFileDialog::getSaveFileName(nullptr, "Open REFAL Notebook", "", "REFAL Notebook (*.refnb)"));
     }
 
     QJsonDocument doc = toJson();
@@ -133,6 +148,8 @@
 
     save.write(doc.toJson(QJsonDocument::Indented));
     save.close();
+
+    emit saved();
 }
 
 bool Notebook::savePathSet()
@@ -157,6 +174,21 @@
     cell->setResult(pprint(result));
     cell->setStatus(Cell::IDLE);
     cell->setResultType(Cell::EXPRESSION);
+
+    if (_runningAll)
+    {
+        int index = _cells.indexOf(cell);
+
+        // not last
+        if (index < _cells.size() - 1)
+        {
+            _rt->queueCell(_cells[index + 1]);
+        }
+        else
+        {
+            _runningAll = false;
+        }
+    }
 }
 
 void Notebook::cellFailedToParse(Cell *cell, ParseResult result, Parser parser)
@@ -165,6 +197,8 @@
     cell->setResult(pprint(result, parser, PPrint::HTML));
     cell->setStatus(Cell::IDLE);
     cell->setResultType(Cell::DIAGNOSTIC);
+
+    _runningAll = false;
 }
 
 void Notebook::cellWaiting(Cell *cell)
@@ -184,4 +218,6 @@
     qInfo() << "cellQuit" << cell->uuid();
     cell->setResult("");
     cell->setStatus(Cell::IDLE);
+
+    _runningAll = false;
 }
diff --git a/ide/Notebook.h b/ide/Notebook.h
index 6a6541d..83f9fc7 100644
--- a/ide/Notebook.h
+++ b/ide/Notebook.h
@@ -25,6 +25,8 @@
 
     Q_INVOKABLE void runCell(QUuid uuid);
     Q_INVOKABLE void quitCell(QUuid uuid);
+    Q_INVOKABLE void runAll();
+    Q_INVOKABLE void reset();
 
     Q_INVOKABLE void fromJson(QJsonDocument doc);
     Q_INVOKABLE void open(QString path);
@@ -41,6 +43,7 @@
     void cellModelChanged();
     void saveError(QString message);
     void savePathChanged(QString savePath);
+    void saved();
 
 protected slots:
     void cellFinishedRunning(Cell *cell, RuntimeResult result);
@@ -57,6 +60,7 @@
     QThread *_rtThread;
     NbRuntime *_rt;
     QString _savePath = "";
+    bool _runningAll = false;
 };
 
 Q_DECLARE_METATYPE(Notebook)
diff --git a/ide/RecentModel.cpp b/ide/RecentModel.cpp
new file mode 100644
index 0000000..30824ed
--- /dev/null
+++ b/ide/RecentModel.cpp
@@ -0,0 +1,62 @@
+#include "RecentModel.h"
+#include <QSettings>
+
+RecentModel::RecentModel(QObject *parent)
+    : QAbstractListModel(parent)
+{
+    _recents = _settings.value("recents").toStringList();
+}
+
+RecentModel::RecentModel(const RecentModel &other, QObject *parent)
+    : RecentModel(parent)
+{
+    _recents = other._recents;
+}
+
+int RecentModel::rowCount(const QModelIndex &parent) const
+{
+    if (parent.isValid())
+        return 0;
+
+    return _recents.size();
+}
+
+QVariant RecentModel::data(const QModelIndex &index, int role) const
+{
+    if (!index.isValid())
+        return QVariant();
+
+    if (role == PathRole)
+        return _recents[index.row()];
+
+    return QVariant();
+}
+
+QHash<int, QByteArray> RecentModel::roleNames() const
+{
+    return {{PathRole, "path"}};
+}
+
+void RecentModel::add(QString path)
+{
+    remove(path);
+
+    beginInsertRows(QModelIndex(), 0, 0);
+    _recents.prepend(path);
+    endInsertRows();
+
+    _settings.setValue("recents", _recents);
+}
+
+void RecentModel::remove(QString path)
+{
+    if (_recents.contains(path))
+    {
+        int index = _recents.indexOf(path);
+        beginRemoveRows(QModelIndex(), index, index);
+        _recents.removeAt(index);
+        endRemoveRows();
+
+        _settings.setValue("recents", _recents);
+    }
+}
diff --git a/ide/RecentModel.h b/ide/RecentModel.h
new file mode 100644
index 0000000..76cd461
--- /dev/null
+++ b/ide/RecentModel.h
@@ -0,0 +1,36 @@
+#pragma once
+
+#include <QAbstractListModel>
+#include <qqml.h>
+#include <QSettings>
+
+class RecentModel : public QAbstractListModel
+{
+    Q_OBJECT
+    QML_ELEMENT
+
+public:
+    explicit RecentModel(QObject *parent = nullptr);
+    RecentModel(const RecentModel &other, QObject *parent = nullptr);
+
+    enum
+    {
+        PathRole = Qt::UserRole + 1
+    };
+
+    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+
+    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+
+    QHash<int, QByteArray> roleNames() const override;
+
+    Q_INVOKABLE void add(QString path);
+    Q_INVOKABLE void remove(QString path);
+
+private:
+    QStringList _recents;
+    QSettings _settings;
+};
+
+Q_DECLARE_METATYPE(RecentModel)
+Q_DECLARE_METATYPE(RecentModel *)
diff --git a/ide/qml/NbWindow.qml b/ide/qml/NbWindow.qml
index 5710c9e..858fb5e 100644
--- a/ide/qml/NbWindow.qml
+++ b/ide/qml/NbWindow.qml
@@ -15,7 +15,8 @@
     Material.theme: Material.Light
     Material.accent: Material.Orange
 
-    minimumWidth: column.implicitWidth
+    minimumWidth: 600
+    minimumHeight: 400
 
     required property ApplicationWindow welcomeWindow
 
@@ -92,10 +93,14 @@
 
             Action {
                 text: qsTr("Run &All")
+
+                onTriggered: notebook.runAll();
             }
 
             Action {
                 text: qsTr("&Reset Runtime State")
+
+                onTriggered: notebook.reset();
             }
         }
     }
@@ -107,6 +112,8 @@
         {
             console.error(message)
         }
+
+        onSaved: welcomeWindow.recentModel.add(notebook.savePath)
     }
 
     ColumnLayout {
@@ -137,7 +144,7 @@
                         ColumnLayout {
                             Label {
                                 font.pointSize: 18
-                                text: qsTr("Notebook")
+                                text: notebook.savePath != "" ? notebook.savePath.split("/").pop().split(".").slice(0,-1).join('.') : qsTr("Notebook")
                             }
 
                             Label {
diff --git a/ide/qml/RecentNotebook.qml b/ide/qml/RecentNotebook.qml
index d0b1120..194424c 100644
--- a/ide/qml/RecentNotebook.qml
+++ b/ide/qml/RecentNotebook.qml
@@ -8,6 +8,7 @@
     property string name: "Hello.refnb"
     property alias containsMouse: mouseArea.containsMouse
     signal clicked()
+    signal removeClicked()
 
     Image {
         id: nbIcon
@@ -27,7 +28,24 @@
             anchors.fill: parent
             hoverEnabled: true
 
-            onClicked: root.clicked()
+            acceptedButtons: Qt.LeftButton | Qt.RightButton
+
+            onClicked: {
+                if (mouse.button == Qt.LeftButton)
+                    root.clicked();
+                else if (mouse.button == Qt.RightButton)
+                    contextMenu.popup();
+            }
+        }
+
+        Menu {
+            id: contextMenu
+
+            MenuItem {
+                text: qsTr("Remove")
+
+                onClicked: root.removeClicked()
+            }
         }
     }
 }
diff --git a/ide/qml/main.qml b/ide/qml/main.qml
index 23f8926..210640c 100644
--- a/ide/qml/main.qml
+++ b/ide/qml/main.qml
@@ -2,6 +2,9 @@
 import QtQuick.Controls 2.15
 import QtQuick.Controls.Material 2.0
 import QtQuick.Layouts 1.11
+import Qt.labs.settings 1.0
+
+import sh.swisschili.REFAL 1.0
 
 ApplicationWindow {
     id: root
@@ -19,6 +22,8 @@
 
     visible: true
 
+    property alias recentModel: recents
+
     function openNotebook(path=null) {
         let NbWindow = Qt.createComponent("qrc:///qml/NbWindow.qml");
         let window = NbWindow.createObject(null, {welcomeWindow: root});
@@ -36,6 +41,10 @@
             show();
     }
 
+    RecentModel {
+        id: recents
+    }
+
     Label {
         id: textRefal
         text: qsTr("REFAL")
@@ -125,21 +134,20 @@
                 Layout.fillWidth: true
                 Layout.fillHeight: false
 
-                model: [
-                    // "~/Documents/Hello.refnb", "~/Downloads/stuff/Goodbye.refnb", "/home/ch/dev/REFAL/build/test.refnb"
-                ]
+                model: recents
 
                 delegate: RecentNotebook {
                     Layout.leftMargin: 8
                     Layout.rightMargin: 8
 
-                    name: modelData.split("/").pop()
+                    name: path.split("/").pop()
 
-                    ToolTip.text: modelData
+                    ToolTip.text: path
                     ToolTip.visible: containsMouse
                     ToolTip.delay: 1000
 
-                    onClicked: root.openNotebook(modelData)
+                    onClicked: root.openNotebook(path)
+                    onRemoveClicked: recents.remove(path)
                 }
             }