diff --git a/apps/calculation/calculation_store.cpp b/apps/calculation/calculation_store.cpp index 55a87653b..5293474c9 100644 --- a/apps/calculation/calculation_store.cpp +++ b/apps/calculation/calculation_store.cpp @@ -180,6 +180,7 @@ size_t CalculationStore::deleteOldestCalculation() { // Delete all calculations void CalculationStore::deleteAll() { + m_trashIndex = -1; m_calculationAreaEnd = m_buffer; m_numberOfCalculations = 0; } diff --git a/apps/code/menu_controller.cpp b/apps/code/menu_controller.cpp index a857fc539..63a50c0a8 100644 --- a/apps/code/menu_controller.cpp +++ b/apps/code/menu_controller.cpp @@ -90,6 +90,11 @@ bool MenuController::handleEvent(Ion::Events::Event event) { footer()->setSelectedButton(0); return true; } + if (event == Ion::Events::ShiftBack) { + Ion::Storage::sharedStorage()->reinsertTrash("py"); + m_selectableTableView.reloadData(); + return true; + } if (event == Ion::Events::Up) { if (footer()->selectedButton() == 0) { footer()->setSelectedButton(-1); diff --git a/apps/graph/list/list_controller.h b/apps/graph/list/list_controller.h index 12deaf33b..b116f34a1 100644 --- a/apps/graph/list/list_controller.h +++ b/apps/graph/list/list_controller.h @@ -21,6 +21,8 @@ public: bool textFieldDidAbortEditing(TextField * textField) override; bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) override; bool textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) override; +protected: + virtual const char * recordExtension() const override { return Ion::Storage::funcExtension; } private: constexpr static int k_maxNumberOfDisplayableRows = 5; Shared::ListParameterController * parameterController() override; diff --git a/apps/sequence/list/list_controller.h b/apps/sequence/list/list_controller.h index d16af18d9..9aef7c4ad 100644 --- a/apps/sequence/list/list_controller.h +++ b/apps/sequence/list/list_controller.h @@ -25,6 +25,8 @@ public: Toolbox * toolboxForInputEventHandler(InputEventHandler * handler) override; void selectPreviousNewSequenceCell(); void editExpression(int sequenceDefinitionIndex, Ion::Events::Event event); +protected: + virtual const char * recordExtension() const override { return Ion::Storage::seqExtension; } private: static constexpr KDCoordinate k_expressionCellVerticalMargin = 3; bool editInitialConditionOfSelectedRecordWithText(const char * text, bool firstInitialCondition); diff --git a/apps/shared/expression_model_handle.h b/apps/shared/expression_model_handle.h index 8d6229a18..812f218a6 100644 --- a/apps/shared/expression_model_handle.h +++ b/apps/shared/expression_model_handle.h @@ -23,7 +23,6 @@ public: * not defined. We thus have to keep both methods. */ virtual bool isDefined(); virtual bool isEmpty(); - virtual bool shouldBeClearedBeforeRemove() { return !isEmpty(); } /* tidy is responsible to tidy the whole model whereas tidyExpressionModel * tidies only the members associated with the ExpressionModel. In * ExpressionModel, tidy and tidyExpressionModel trigger the same diff --git a/apps/shared/expression_model_list_controller.cpp b/apps/shared/expression_model_list_controller.cpp index dc1c18d0f..3defcde2e 100644 --- a/apps/shared/expression_model_list_controller.cpp +++ b/apps/shared/expression_model_list_controller.cpp @@ -184,18 +184,19 @@ bool ExpressionModelListController::handleEventOnExpression(Ion::Events::Event e } if (event == Ion::Events::Backspace && !isAddEmptyRow(selectedRow())) { Ion::Storage::Record record = modelStore()->recordAtIndex(modelIndexForRow(selectedRow())); - ExpiringPointer model = modelStore()->modelForRecord(record); - if (model->shouldBeClearedBeforeRemove()) { - reinitSelectedExpression(model); - } else { - if (removeModelRow(record)) { - int newSelectedRow = selectedRow() >= numberOfExpressionRows() ? numberOfExpressionRows()-1 : selectedRow(); - selectCellAtLocation(selectedColumn(), newSelectedRow); - selectableTableView()->reloadData(); - } + ExpiringPointer model = modelStore()->modelForRecord(record); + if (removeModelRow(record)) { + int newSelectedRow = selectedRow() >= numberOfExpressionRows() ? numberOfExpressionRows()-1 : selectedRow(); + selectCellAtLocation(selectedColumn(), newSelectedRow); + selectableTableView()->reloadData(); } return true; } + if (event == Ion::Events::ShiftBack) { + Ion::Storage::sharedStorage()->reinsertTrash(recordExtension()); + selectableTableView()->reloadData(); + return true; + } if ((event.hasText() || event == Ion::Events::XNT || event == Ion::Events::Paste || event == Ion::Events::Toolbox || event == Ion::Events::Var) && !isAddEmptyRow(selectedRow())) { editExpression(event); diff --git a/apps/shared/expression_model_list_controller.h b/apps/shared/expression_model_list_controller.h index 45162107c..9ced3980a 100644 --- a/apps/shared/expression_model_list_controller.h +++ b/apps/shared/expression_model_list_controller.h @@ -11,6 +11,7 @@ class ExpressionModelListController : public ViewController, public SelectableTa public: ExpressionModelListController(Responder * parentResponder, I18n::Message text); protected: + virtual const char * recordExtension() const = 0; static constexpr KDCoordinate k_expressionMargin = 5; // SelectableTableViewDelegate void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) override; diff --git a/apps/shared/function_list_controller.cpp b/apps/shared/function_list_controller.cpp index d8dbf2ad7..4083ab955 100644 --- a/apps/shared/function_list_controller.cpp +++ b/apps/shared/function_list_controller.cpp @@ -200,6 +200,11 @@ bool FunctionListController::handleEvent(Ion::Events::Event event) { } return true; } + if (event == Ion::Events::ShiftBack) { + Ion::Storage::sharedStorage()->reinsertTrash(recordExtension()); + selectableTableView()->reloadData(); + return true; + } return false; } diff --git a/apps/shared/function_list_controller.h b/apps/shared/function_list_controller.h index da6a7036a..be62a8435 100644 --- a/apps/shared/function_list_controller.h +++ b/apps/shared/function_list_controller.h @@ -7,6 +7,7 @@ #include "list_parameter_controller.h" #include "expression_model_list_controller.h" #include +#include namespace Shared { diff --git a/apps/solver/equation.h b/apps/solver/equation.h index a43db56c8..cae60c7ce 100644 --- a/apps/solver/equation.h +++ b/apps/solver/equation.h @@ -8,9 +8,6 @@ namespace Solver { class Equation : public Shared::ExpressionModelHandle { public: Equation(Ion::Storage::Record record = Record()) : ExpressionModelHandle(record) {} - bool shouldBeClearedBeforeRemove() override { - return false; - } Poincare::Expression standardForm(Poincare::Context * context, bool replaceFunctionsButNotSymbols, Poincare::ExpressionNode::ReductionTarget reductionTarget) const { return m_model.standardForm(this, context, replaceFunctionsButNotSymbols, reductionTarget); } bool containsIComplex(Poincare::Context * context) const; diff --git a/apps/solver/list_controller.h b/apps/solver/list_controller.h index 93e9173fd..a64cc6ec0 100644 --- a/apps/solver/list_controller.h +++ b/apps/solver/list_controller.h @@ -9,6 +9,7 @@ #include "equation_list_view.h" #include "equation_models_parameter_controller.h" #include +#include namespace Solver { @@ -45,6 +46,8 @@ public: bool layoutFieldDidFinishEditing(LayoutField * layoutField, Poincare::Layout layout, Ion::Events::Event event) override; /* Specific to Solver */ void resolveEquations(); +protected: + virtual const char * recordExtension() const override { return Ion::Storage::eqExtension; } private: constexpr static int k_maxNumberOfRows = 5; // Ion::Display::Height / Metric::StoreRowHeight = 4.8; SelectableTableView * selectableTableView() override; diff --git a/ion/include/ion/storage.h b/ion/include/ion/storage.h index 56ee0fc26..3cca0c4ca 100644 --- a/ion/include/ion/storage.h +++ b/ion/include/ion/storage.h @@ -125,7 +125,10 @@ public: // Used by Python OS module int numberOfRecords(); - Record recordAtIndex(int index); + Record recordAtIndex(int index); // Unlike realRecordAtIndex, this ignore trash + + // Trash + void reinsertTrash(const char * extension); private: constexpr static uint32_t Magic = 0xEE0BDDBA; @@ -140,6 +143,7 @@ private: Record::Data valueOfRecord(const Record record); Record::ErrorStatus setValueOfRecord(const Record record, Record::Data data); void destroyRecord(const Record record); + void realDestroyRecord(const Record record); /* Getters on address in buffer */ char * pointerOfRecord(const Record record) const; @@ -147,12 +151,18 @@ private: const char * fullNameOfRecordStarting(char * start) const; const void * valueOfRecordStarting(char * start) const; + /* Trash */ + void emptyTrash(); + Storage::Record realRecordAtIndex(int index); + int realNumberOfRecords(); + /* Overriders */ size_t overrideSizeAtPosition(char * position, record_size_t size); size_t overrideFullNameAtPosition(char * position, const char * fullName); size_t overrideBaseNameWithExtensionAtPosition(char * position, const char * baseName, const char * extension); size_t overrideValueAtPosition(char * position, const void * data, record_size_t size); + size_t realAvailableSize(); bool isFullNameTaken(const char * fullName, const Record * recordToExclude = nullptr); bool isBaseNameWithExtensionTaken(const char * baseName, const char * extension, Record * recordToExclude = nullptr); bool isNameOfRecordTaken(Record r, const Record * recordToExclude); @@ -182,6 +192,7 @@ private: uint32_t m_magicHeader; char m_buffer[k_storageSize]; + Record m_trashRecord; uint32_t m_magicFooter; StorageDelegate * m_delegate; mutable Record m_lastRecordRetrieved; diff --git a/ion/src/shared/storage.cpp b/ion/src/shared/storage.cpp index 3419e3d8c..200b41865 100644 --- a/ion/src/shared/storage.cpp +++ b/ion/src/shared/storage.cpp @@ -97,6 +97,14 @@ void Storage::log() { #endif size_t Storage::availableSize() { + if (m_trashRecord != NULL) { + return realAvailableSize() + sizeof(record_size_t) + m_trashRecord.value().size; + } else { + return realAvailableSize(); + } +} + +size_t Storage::realAvailableSize() { /* TODO maybe do: availableSize(char ** endBuffer) to get the endBuffer if it * is needed after calling availableSize */ assert(k_storageSize >= (endBuffer() - m_buffer) + sizeof(record_size_t)); @@ -106,7 +114,7 @@ size_t Storage::availableSize() { size_t Storage::putAvailableSpaceAtEndOfRecord(Storage::Record r) { char * p = pointerOfRecord(r); size_t previousRecordSize = sizeOfRecordStarting(p); - size_t availableStorageSize = availableSize(); + size_t availableStorageSize = realAvailableSize(); char * nextRecord = p + previousRecordSize; memmove(nextRecord + availableStorageSize, nextRecord, @@ -146,6 +154,7 @@ Storage::Record::ErrorStatus Storage::notifyFullnessToDelegate() const { } Storage::Record::ErrorStatus Storage::createRecordWithFullName(const char * fullName, const void * data, size_t size) { + emptyTrash(); size_t recordSize = sizeOfRecordWithFullName(fullName, size); if (recordSize >= k_maxRecordSize || recordSize > availableSize()) { return notifyFullnessToDelegate(); @@ -172,6 +181,7 @@ Storage::Record::ErrorStatus Storage::createRecordWithFullName(const char * full } Storage::Record::ErrorStatus Storage::createRecordWithExtension(const char * baseName, const char * extension, const void * data, size_t size) { + emptyTrash(); size_t recordSize = sizeOfRecordWithBaseNameAndExtension(baseName, extension, size); if (recordSize >= k_maxRecordSize || recordSize > availableSize()) { return notifyFullnessToDelegate(); @@ -202,7 +212,7 @@ int Storage::numberOfRecordsWithExtension(const char * extension) { size_t extensionLength = strlen(extension); for (char * p : *this) { const char * name = fullNameOfRecordStarting(p); - if (FullNameHasExtension(name, extension, extensionLength)) { + if (FullNameHasExtension(name, extension, extensionLength) && Record(name) != m_trashRecord) { count++; } } @@ -210,6 +220,10 @@ int Storage::numberOfRecordsWithExtension(const char * extension) { } int Storage::numberOfRecords() { + return realNumberOfRecords() - (m_trashRecord == NULL ? 0 : 1); +} + +int Storage::realNumberOfRecords() { int count = 0; for (char * p : *this) { const char * name = fullNameOfRecordStarting(p); @@ -218,7 +232,7 @@ int Storage::numberOfRecords() { return count; } -Storage::Record Storage::recordAtIndex(int index) { +Storage::Record Storage::realRecordAtIndex(int index) { int currentIndex = -1; const char * name = nullptr; char * recordAddress = nullptr; @@ -240,6 +254,40 @@ Storage::Record Storage::recordAtIndex(int index) { return Record(name); } + +Storage::Record Storage::recordAtIndex(int index) { + int currentIndex = -1; + const char * name = nullptr; + char * recordAddress = nullptr; + for (char * p : *this) { + const char * currentName = fullNameOfRecordStarting(p); + Record r = Record(currentName); + if (r == m_trashRecord) { + continue; + } + currentIndex++; + if (currentIndex == index) { + recordAddress = p; + name = currentName; + break; + } + } + if (name == nullptr) { + return Record(); + } + Record r = Record(name); + m_lastRecordRetrieved = r; + m_lastRecordRetrievedPointer = recordAddress; + return Record(name); +} + +void Storage::emptyTrash() { + if (m_trashRecord != NULL) { + realDestroyRecord(m_trashRecord); + m_trashRecord = NULL; + } +} + Storage::Record Storage::recordWithExtensionAtIndex(const char * extension, int index) { int currentIndex = -1; const char * name = nullptr; @@ -247,13 +295,13 @@ Storage::Record Storage::recordWithExtensionAtIndex(const char * extension, int char * recordAddress = nullptr; for (char * p : *this) { const char * currentName = fullNameOfRecordStarting(p); - if (FullNameHasExtension(currentName, extension, extensionLength)) { + if (FullNameHasExtension(currentName, extension, extensionLength) && Record(currentName) != m_trashRecord) { currentIndex++; - } - if (currentIndex == index) { - recordAddress = p; - name = currentName; - break; + if (currentIndex == index) { + recordAddress = p; + name = currentName; + break; + } } } if (name == nullptr) { @@ -328,7 +376,8 @@ Storage::Storage() : m_magicFooter(Magic), m_delegate(nullptr), m_lastRecordRetrieved(nullptr), - m_lastRecordRetrievedPointer(nullptr) + m_lastRecordRetrievedPointer(nullptr), + m_trashRecord(NULL) { assert(m_magicHeader == Magic); assert(m_magicFooter == Magic); @@ -430,7 +479,24 @@ Storage::Record::ErrorStatus Storage::setValueOfRecord(Record record, Record::Da return Record::ErrorStatus::RecordDoesNotExist; } + +void Storage::reinsertTrash(const char * extension) { + if (m_trashRecord != NULL) { + char * p = pointerOfRecord(m_trashRecord); + const char * fullName = fullNameOfRecordStarting(p); + if (FullNameHasExtension(fullName, extension, strlen(extension))) { + m_trashRecord = NULL; + } + } +} + + void Storage::destroyRecord(Record record) { + emptyTrash(); + m_trashRecord = record; +} + +void Storage::realDestroyRecord(Record record) { if (record.isNull()) { return; } @@ -520,7 +586,7 @@ bool Storage::isNameOfRecordTaken(Record r, const Record * recordToExclude) { if (recordToExclude && s == *recordToExclude) { continue; } - if (s == r) { + if (s == r && s != m_trashRecord) { return true; } } @@ -576,8 +642,11 @@ size_t Storage::sizeOfRecordWithFullName(const char * fullName, size_t dataSize) } bool Storage::slideBuffer(char * position, int delta) { - if (delta > (int)availableSize()) { - return false; + if (delta > (int)realAvailableSize()) { + emptyTrash(); + if (delta > (int)realAvailableSize()) { + return false; + } } memmove(position+delta, position, endBuffer()+sizeof(record_size_t)-position); return true; @@ -587,7 +656,7 @@ Storage::Record Storage::privateRecordAndExtensionOfRecordBaseNamedWithExtension size_t nameLength = baseNameLength < 0 ? strlen(baseName) : baseNameLength; { const char * lastRetrievedRecordFullName = fullNameOfRecordStarting(m_lastRecordRetrievedPointer); - if (m_lastRecordRetrievedPointer != nullptr && strncmp(baseName, lastRetrievedRecordFullName, nameLength) == 0) { + if (m_lastRecordRetrievedPointer != nullptr && strncmp(baseName, lastRetrievedRecordFullName, nameLength) == 0 && Record(lastRetrievedRecordFullName) == m_trashRecord) { for (size_t i = 0; i < numberOfExtensions; i++) { if (strcmp(lastRetrievedRecordFullName+nameLength+1 /*+1 to pass the dot*/, extensions[i]) == 0) { assert(UTF8Helper::CodePointIs(lastRetrievedRecordFullName + nameLength, '.')); @@ -602,6 +671,9 @@ Storage::Record Storage::privateRecordAndExtensionOfRecordBaseNamedWithExtension for (char * p : *this) { const char * currentName = fullNameOfRecordStarting(p); if (strncmp(baseName, currentName, nameLength) == 0) { + if (Record(currentName) == m_trashRecord) { + continue; + } for (size_t i = 0; i < numberOfExtensions; i++) { if (strcmp(currentName+nameLength+1 /*+1 to pass the dot*/, extensions[i]) == 0) { assert(UTF8Helper::CodePointIs(currentName + nameLength, '.'));