From 6c46eaf7f6d66a4bc66cc4da3b2440d1f462b610 Mon Sep 17 00:00:00 2001 From: Joris Goosen Date: Fri, 21 May 2021 18:58:55 +0200 Subject: [PATCH 01/38] Implements a data mode - can enter data manually - copy/paste to itself and to from spreadsheet editors - Arrow + shift now also changes selection - Allow for changing columnnames - Make sure a "New Data"-set can be closed - Make sure handle can't be seen when in datamode (like results+analyses) - When switching to back the analysesmode, last edit should be absorbed - Make ribbonbuttons behave - First attempts at restoring external data editing but commented it out. Im not sure if and how this should be done - Some general cleaning up - select all and cut and include columnnames in all that - Also add a rightclick menu - Don't show edit data etc in modules menu - Adds support for more context-menu options: -- insert. delete and select columns & rows - Turn BOM workaround on by default for SKF - Fix after rebase/merge before release 0.15 --- Common/utils.cpp | 9 - Common/utils.h | 5 +- CommonData/column.cpp | 81 ++- CommonData/columns.cpp | 19 +- CommonData/columns.h | 1 + CommonData/columnutils.cpp | 70 ++- CommonData/columnutils.h | 5 + CommonData/labels.cpp | 4 +- Desktop/analysis/analyses.cpp | 20 +- Desktop/analysis/analyses.h | 1 + .../components/JASP/Widgets/CustomMenu.qml | 69 ++- Desktop/components/JASP/Widgets/DataPanel.qml | 73 +-- .../components/JASP/Widgets/DataTableView.qml | 323 ++++++++++- .../components/JASP/Widgets/JASPDataView.qml | 177 ++++-- .../JASP/Widgets/JASPMouseAreaToolTipped.qml | 9 +- Desktop/components/JASP/Widgets/MainPage.qml | 5 +- .../components/JASP/Widgets/MainWindow.qml | 11 +- .../JASP/Widgets/RenameColumnDialog.qml | 150 +++++ .../JASP/Widgets/ResizeDataDialog.qml | 186 +++++++ .../JASP/Widgets/Ribbon/RibbonBar.qml | 4 +- .../JASP/Widgets/Ribbon/RibbonButton.qml | 1 + .../JASP/Widgets/Ribbon/Ribbons.qml | 12 +- .../JASP/Widgets/VariablesWindow.qml | 26 +- Desktop/components/JASP/Widgets/qmldir | 2 + Desktop/data/computedcolumnsmodel.cpp | 1 + Desktop/data/datasetpackage.cpp | 383 ++++++++++++- Desktop/data/datasetpackage.h | 78 ++- Desktop/data/datasettablemodel.cpp | 42 ++ Desktop/data/datasettablemodel.h | 12 + Desktop/data/importers/importcolumn.cpp | 57 -- Desktop/data/importers/importcolumn.h | 3 - Desktop/data/importers/importer.cpp | 28 - Desktop/data/importers/importer.h | 2 +- .../readstat/readstatimportcolumn.cpp | 10 +- Desktop/data/labelmodel.cpp | 16 +- Desktop/data/labelmodel.h | 5 +- Desktop/engine/enginesync.cpp | 19 +- Desktop/engine/enginesync.h | 5 +- Desktop/mainwindow.cpp | 18 +- Desktop/mainwindow.h | 1 + Desktop/modules/analysisentry.cpp | 33 ++ Desktop/modules/analysisentry.h | 38 +- Desktop/modules/dynamicmodule.cpp | 10 - Desktop/modules/dynamicmodule.h | 2 +- .../{analysismenumodel.cpp => menumodel.cpp} | 28 +- .../{analysismenumodel.h => menumodel.h} | 28 +- Desktop/modules/ribbonbutton.cpp | 63 ++- Desktop/modules/ribbonbutton.h | 45 +- Desktop/modules/ribbonmodel.cpp | 138 +++-- Desktop/modules/ribbonmodel.h | 49 +- Desktop/modules/ribbonmodeluncommon.cpp | 7 +- Desktop/qquick/datasetview.cpp | 524 +++++++++++++++++- Desktop/qquick/datasetview.h | 376 +++++++------ Desktop/resources/icons/Rlogo.svg | 14 - Desktop/resources/icons/darkTheme/Rlogo.svg | 103 ++++ .../icons/darkTheme/data-button-erase.svg | 255 +++++++++ .../icons/darkTheme/data-button-external.svg | 123 ++++ .../icons/darkTheme/data-button-insert.svg | 262 +++++++++ .../icons/darkTheme/data-button-internal.svg | 132 +++++ .../icons/darkTheme/data-button-new.svg | 135 +++++ .../icons/darkTheme/data-button-resize.svg | 112 ++++ .../resources/icons/darkTheme/data-button.svg | 105 ++++ Desktop/resources/icons/lightTheme/Rlogo.svg | 101 ++++ .../icons/lightTheme/data-button-erase.svg | 255 +++++++++ .../icons/lightTheme/data-button-external.svg | 118 ++++ .../icons/lightTheme/data-button-insert.svg | 263 +++++++++ .../icons/lightTheme/data-button-new.svg | 135 +++++ .../icons/lightTheme/data-button-resize.svg | 112 ++++ .../icons/lightTheme/data-button.svg | 106 ++++ Desktop/utilities/languagemodel.cpp | 2 +- Desktop/utilities/settings.cpp | 21 +- Desktop/widgets/filemenu/filemenu.cpp | 19 +- Desktop/widgets/filemenu/filemenu.h | 1 + .../JASP/Controls/JASPScrollBar.qml | 42 +- .../components/JASP/Controls/TextField.qml | 21 +- 75 files changed, 5009 insertions(+), 712 deletions(-) create mode 100644 Desktop/components/JASP/Widgets/RenameColumnDialog.qml create mode 100644 Desktop/components/JASP/Widgets/ResizeDataDialog.qml rename Desktop/modules/{analysismenumodel.cpp => menumodel.cpp} (73%) rename Desktop/modules/{analysismenumodel.h => menumodel.h} (72%) delete mode 100644 Desktop/resources/icons/Rlogo.svg create mode 100644 Desktop/resources/icons/darkTheme/Rlogo.svg create mode 100644 Desktop/resources/icons/darkTheme/data-button-erase.svg create mode 100644 Desktop/resources/icons/darkTheme/data-button-external.svg create mode 100644 Desktop/resources/icons/darkTheme/data-button-insert.svg create mode 100644 Desktop/resources/icons/darkTheme/data-button-internal.svg create mode 100644 Desktop/resources/icons/darkTheme/data-button-new.svg create mode 100644 Desktop/resources/icons/darkTheme/data-button-resize.svg create mode 100644 Desktop/resources/icons/darkTheme/data-button.svg create mode 100644 Desktop/resources/icons/lightTheme/Rlogo.svg create mode 100644 Desktop/resources/icons/lightTheme/data-button-erase.svg create mode 100644 Desktop/resources/icons/lightTheme/data-button-external.svg create mode 100644 Desktop/resources/icons/lightTheme/data-button-insert.svg create mode 100644 Desktop/resources/icons/lightTheme/data-button-new.svg create mode 100644 Desktop/resources/icons/lightTheme/data-button-resize.svg create mode 100644 Desktop/resources/icons/lightTheme/data-button.svg diff --git a/Common/utils.cpp b/Common/utils.cpp index 051ffd9f33..9ee7be950a 100644 --- a/Common/utils.cpp +++ b/Common/utils.cpp @@ -36,14 +36,6 @@ using namespace std; -std::string Utils::doubleToString(double dbl, int precision) -{ - std::stringstream conv; //Use this instead of std::to_string to make sure there are no trailing zeroes (and to get full precision) - conv << std::setprecision(precision); - conv << dbl; - return conv.str(); -} - Utils::FileType Utils::getTypeFromFileName(const std::string &path) { @@ -229,7 +221,6 @@ void Utils::sleep(int ms) #endif } - bool Utils::isEqual(const double a, const double b) { if (isnan(a) || isnan(b)) return false; diff --git a/Common/utils.h b/Common/utils.h index 0e83ebb5ce..2852081e58 100644 --- a/Common/utils.h +++ b/Common/utils.h @@ -22,6 +22,8 @@ #include #include #include +#include +#include #include "timers.h" enum class FileTypeBase; @@ -44,15 +46,12 @@ class Utils static bool renameOverwrite( const std::string &oldName, const std::string &newName); static bool removeFile( const std::string &path); - static std::string doubleToString(double dbl, int precision = 10); - static std::filesystem::path osPath(const std::string &path); static std::string osPath(const std::filesystem::path &path); static void remove(std::vector &target, const std::vector &toRemove); static void sleep(int ms); - static bool isEqual(const float a, const float b); static bool isEqual(const double a, const double b); diff --git a/CommonData/column.cpp b/CommonData/column.cpp index cd6f5c3b75..6bdd45521d 100644 --- a/CommonData/column.cpp +++ b/CommonData/column.cpp @@ -180,7 +180,7 @@ bool Column::_resetEmptyValuesForScale(std::map &emptyValuesMap) // This value is now considered as empty *doubles = NAN; hasChanged = true; - emptyValuesMap.insert(make_pair(row, Utils::doubleToString(doubleValue))); + emptyValuesMap.insert(make_pair(row, ColumnUtils::doubleToString(doubleValue))); } row++; } @@ -525,7 +525,7 @@ columnTypeChangeResult Column::_changeColumnToNominalOrOrdinal(enum columnType n for (double doubleValue : AsDoubles) if (std::isnan(doubleValue)) values.push_back(""); - else values.push_back(Utils::doubleToString(doubleValue)); + else values.push_back(ColumnUtils::doubleToString(doubleValue)); setColumnAsNominalText(values); return columnTypeChangeResult::changed; @@ -1019,7 +1019,7 @@ string Column::_getScaleValue(int row, bool forDisplay) if (v > std::numeric_limits::max()) return "∞"; else if (v < std::numeric_limits::lowest()) return "-∞"; else if (ColumnUtils::isEmptyValue(v)) return forDisplay ? ColumnUtils::emptyValue : ""; - else return Utils::doubleToString(v); + else return ColumnUtils::doubleToString(v); } string Column::getOriginalValue(int row) @@ -1071,6 +1071,8 @@ void Column::append(int rows) if (rows == 0) return; + size_t originalRowCount = _rowCount; + BlockMap::reverse_iterator itr = _blocks.rbegin(); if (itr == _blocks.rend()) // no blocks @@ -1092,39 +1094,64 @@ void Column::append(int rows) { block->insert(rows); _rowCount += rows; - return; + goto finishingUp; //yeah yeah this is a goto, no problem right? } - block->insert(room); - _rowCount += room; - - int newBlocksRequired = rowsLeft / DataBlock::capacity(); - if (rowsLeft % DataBlock::capacity()) - newBlocksRequired++; - - for (int i = 0; i < newBlocksRequired; i++) + //Extra scope to avoid var init between goto and label { - try { + block->insert(room); + _rowCount += room; - DataBlock *newBlock = _mem->construct(anonymous_instance)(); + int newBlocksRequired = rowsLeft / DataBlock::capacity(); + if (rowsLeft % DataBlock::capacity()) + newBlocksRequired++; + + for (int i = 0; i < newBlocksRequired; i++) + { + try { + + DataBlock *newBlock = _mem->construct(anonymous_instance)(); + + int toInsert = min(rowsLeft, DataBlock::capacity()); + newBlock->insert(toInsert); + rowsLeft -= toInsert; + + id += DataBlock::capacity(); + _blocks.insert(BlockEntry(id, newBlock)); + + _rowCount += toInsert; + } + catch (boost::interprocess::bad_alloc &e) + { + std::cout << e.what() << " "; + std::cout << "append column " << name() << ", append: " << rows << ", rowCount: " << _rowCount << std::endl; + throw e; + } + } + } - int toInsert = min(rowsLeft, DataBlock::capacity()); - newBlock->insert(toInsert); - rowsLeft -= toInsert; +finishingUp: - id += DataBlock::capacity(); - _blocks.insert(BlockEntry(id, newBlock)); + //Make sure the new rows are empty looking + switch(_columnType) + { + case columnType::unknown: + case columnType::scale: + for(size_t r=originalRowCount; r<_rowCount; r++) + AsDoubles[r] = NAN; + break; - _rowCount += toInsert; + case columnType::ordinal: + case columnType::nominal: + for(size_t r=originalRowCount; r<_rowCount; r++) + AsInts[r] = INT_MIN; + break; - } - catch (boost::interprocess::bad_alloc &e) - { - std::cout << e.what() << " "; - std::cout << "append column " << name() << ", append: " << rows << ", rowCount: " << _rowCount << std::endl; - throw e; - } + case columnType::nominalText: + break; } + + } void Column::truncate(int rows) diff --git a/CommonData/columns.cpp b/CommonData/columns.cpp index 9788c37354..237a233748 100644 --- a/CommonData/columns.cpp +++ b/CommonData/columns.cpp @@ -63,9 +63,15 @@ void Columns::setRowCount(size_t rowCount) void Columns::setColumnCount(size_t columnCount) { - _columnStore.reserve(columnCount); - for (size_t i = _columnStore.size(); i < columnCount; i++) - _columnStore.push_back(Column(_mem)); + if(columnCount > _columnStore.size()) + { + _columnStore.reserve(columnCount); + for (size_t i = _columnStore.size(); i < columnCount; i++) + _columnStore.push_back(Column(_mem)); + } + else + while(_columnStore.size() > columnCount) + _columnStore.erase(--_columnStore.end()); } @@ -84,6 +90,13 @@ void Columns::removeColumn(std::string name) } } +void Columns::insertColumn(size_t index) +{ + auto c = Column(_mem); + c._setRowCount(maxRowCount()); + _columnStore.insert(_columnStore.begin() + index, c); +} + void Columns::setSharedMemory(boost::interprocess::managed_shared_memory *mem) { diff --git a/CommonData/columns.h b/CommonData/columns.h index 10d34575ba..aad6208bb8 100644 --- a/CommonData/columns.h +++ b/CommonData/columns.h @@ -56,6 +56,7 @@ class Columns void removeColumn(size_t index); void removeColumn(std::string name); + void insertColumn(size_t index); ColumnVector _columnStore; diff --git a/CommonData/columnutils.cpp b/CommonData/columnutils.cpp index 8e5a380752..f4a130e1b3 100644 --- a/CommonData/columnutils.cpp +++ b/CommonData/columnutils.cpp @@ -31,10 +31,10 @@ void ColumnUtils::processEmptyValues() { _currentDoubleEmptyValues.clear(); - for (vector::const_iterator it = _currentEmptyValues.begin(); it != _currentEmptyValues.end(); ++it) + for (const std::string & curEmptyVal : _currentEmptyValues) { double doubleValue; - if (ColumnUtils::getDoubleValue(*it, doubleValue)) + if (ColumnUtils::getDoubleValue(curEmptyVal, doubleValue)) _currentDoubleEmptyValues.push_back(doubleValue); } } @@ -167,6 +167,72 @@ bool ColumnUtils::convertValueToDoubleForImport(const std::string &strValue, dou return true; } +std::string ColumnUtils::doubleToString(double dbl, int precision) +{ + std::stringstream conv; //Use this instead of std::to_string to make sure there are no trailing zeroes (and to get full precision) + conv << std::setprecision(precision); + conv << dbl; + return conv.str(); +} + + +bool ColumnUtils::convertVecToInt(const std::vector &values, std::vector &intValues, std::set &uniqueValues, std::map &emptyValuesMap) +{ + emptyValuesMap.clear(); + uniqueValues.clear(); + intValues.clear(); + intValues.reserve(values.size()); + + int row = 0; + + for (const std::string &value : values) + { + int intValue = std::numeric_limits::lowest(); + + if (ColumnUtils::convertValueToIntForImport(value, intValue)) + { + if (intValue != std::numeric_limits::lowest()) uniqueValues.insert(intValue); + else if (!value.empty()) emptyValuesMap.insert(make_pair(row, value)); + + intValues.push_back(intValue); + } + else + return false; + + row++; + } + + return true; +} + + +bool ColumnUtils::convertVecToDouble(const std::vector &values, std::vector &doubleValues, std::map &emptyValuesMap) +{ + emptyValuesMap.clear(); + doubleValues.clear(); + doubleValues.reserve(values.size()); + + int row = 0; + for (const std::string &value : values) + { + double doubleValue = static_cast(NAN); + + if (ColumnUtils::convertValueToDoubleForImport(value, doubleValue)) + { + doubleValues.push_back(doubleValue); + + if (std::isnan(doubleValue) && value != ColumnUtils::emptyValue) + emptyValuesMap.insert(std::make_pair(row, value)); + } + else + return false; + + row++; + } + + return true; +} + // hex should be 4 hexadecimals characters std::string ColumnUtils::_convertEscapedUnicodeToUTF8(std::string hex) { diff --git a/CommonData/columnutils.h b/CommonData/columnutils.h index c23d20232a..4a27465118 100644 --- a/CommonData/columnutils.h +++ b/CommonData/columnutils.h @@ -28,6 +28,11 @@ class ColumnUtils static bool convertValueToDoubleForImport( const std::string &strValue, double &doubleValue); static void convertEscapedUnicodeToUTF8( std::string &inputStr); + static bool convertVecToInt(const std::vector &values, std::vector &intValues, std::set &uniqueValues, std::map &emptyValuesMap); + static bool convertVecToDouble(const std::vector &values, std::vector &doubleValues, std::map &emptyValuesMap); + + static std::string doubleToString(double dbl, int precision = 10); + private: static std::string _deEuropeaniseForImport( const std::string &value); static std::string _convertEscapedUnicodeToUTF8( std::string hex); diff --git a/CommonData/labels.cpp b/CommonData/labels.cpp index 5663aae5aa..75eaa01b59 100644 --- a/CommonData/labels.cpp +++ b/CommonData/labels.cpp @@ -271,11 +271,11 @@ const Label &Labels::getLabelObjectFromKey(int index) const return label; } - Log::log() << "Cannot find entry " << index << std::endl; + /*Log::log() << "Cannot find entry " << index << std::endl; for(const Label &label: _labels) { Log::log() << "Label Value: " << label.value() << ", Text: " << label.text() << std::endl; - } + }*/ throw labelNotFound("Cannot find this entry"); } diff --git a/Desktop/analysis/analyses.cpp b/Desktop/analysis/analyses.cpp index 8678906868..c9f1878106 100644 --- a/Desktop/analysis/analyses.cpp +++ b/Desktop/analysis/analyses.cpp @@ -769,7 +769,9 @@ void Analyses::prepareForLanguageChange() { a->setBeingTranslated(true); a->setRefreshBlocked(true); - a->abort(); + + if(!a->isFinished()) + a->abort(); }); } @@ -785,6 +787,22 @@ void Analyses::languageChangedHandler() emit setResultsMeta(tq(_resultsMeta.toStyledString())); } +void Analyses::dataModeChanged(bool dataMode) +{ + Log::log() << "Data mode turned " << (dataMode ? "on so blocking" : "off so unblocking") << " refresh of analyses." << std::endl; + + applyToAll([&](Analysis * a) + { + a->setRefreshBlocked(dataMode); + + if(dataMode && !a->isFinished()) + a->abort(); + }); + + if(!dataMode) + refreshAllAnalyses(); +} + void Analyses::resultsMetaChanged(QString json) { Json::Reader().parse(fq(json), _resultsMeta); diff --git a/Desktop/analysis/analyses.h b/Desktop/analysis/analyses.h index efb20c31ff..67aad89421 100644 --- a/Desktop/analysis/analyses.h +++ b/Desktop/analysis/analyses.h @@ -137,6 +137,7 @@ public slots: void allUserDataChanged(QString json); void moveAnalysesResults(Analysis* fromAnalysis, int index); void showRSyntaxInResults(bool show); + void dataModeChanged(bool dataMode); signals: void analysesUnselected(); diff --git a/Desktop/components/JASP/Widgets/CustomMenu.qml b/Desktop/components/JASP/Widgets/CustomMenu.qml index 478ce976bc..36f6aa504c 100644 --- a/Desktop/components/JASP/Widgets/CustomMenu.qml +++ b/Desktop/components/JASP/Widgets/CustomMenu.qml @@ -95,24 +95,28 @@ FocusScope resultsJsInterface.runJavaScript("window.setSelection(false);") } - function toggle(item, props, x_offset, y_offset) + function toggle(item, props, x_offset = 0, y_offset = 0) { if (item === menu.sourceItem && menu.visible) hide() else - { - menu.sourceItem = item; - menu.menuMaxPos.x = Qt.binding(function() { return mainWindowRoot.width; }); - menu.menuMaxPos.y = Qt.binding(function() { return mainWindowRoot.height; }); - menu.menuMinPos = item.mapToItem(null, 1, 1); - menu.props = props; - menu.menuOffset.x = x_offset; - menu.menuOffset.y = y_offset; - menu.menuScroll = "0,0"; - menu.showMe = true; - - menu.forceActiveFocus(); - } + show(item, props, x_offset, y_offset); + } + + function show(item, props, x_offset = 0, y_offset = 0) + { + menu.sourceItem = item; + menu.menuMaxPos.x = Qt.binding(function() { return mainWindowRoot.width; }); + menu.menuMaxPos.y = Qt.binding(function() { return mainWindowRoot.height; }); + menu.menuMinPos = item.mapToItem(null, 1, 1); + menu.props = props; + menu.menuOffset.x = x_offset; + menu.menuOffset.y = y_offset; + menu.menuScroll = "0,0"; + menu.showMe = true; + + menu.forceActiveFocus(); + } function hide() @@ -242,6 +246,16 @@ FocusScope { sourceComponent : { + if(model.modelData !== undefined) + { + if(model.modelData.startsWith("---")) + { + if(model.modelData == "---") return menuSeparator; + else return menuGroupTitle; + } + return menuDelegate; + } + if (model.isSeparator !== undefined && model.isSeparator) return menuSeparator; else if (model.isGroupTitle !== undefined && model.isGroupTitle) return menuGroupTitle; @@ -257,13 +271,13 @@ FocusScope id: menuItem width: initWidth height: jaspTheme.menuItemHeight - color: !model.isEnabled - ? "transparent" - : mouseArea.pressed || index == currentIndex - ? jaspTheme.buttonColorPressed - : mouseArea.containsMouse - ? jaspTheme.buttonColorHovered - : "transparent" + color: (model.modelData === undefined) && !model.isEnabled + ? "transparent" + : mouseArea.pressed || index == currentIndex + ? jaspTheme.buttonColorPressed + : mouseArea.containsMouse + ? jaspTheme.buttonColorHovered + : "transparent" property double initWidth: (menu.hasIcons ? menuItemImage.width : 0) + menuItemText.implicitWidth + (menu.hasIcons ? menu._iconPad * 5 : menu._iconPad * 4) @@ -273,7 +287,7 @@ FocusScope height : menuItem.height - (2 * menu._iconPad) width : menuItem.height - menu._iconPad - source : menuImageSource + source : model.modelData !== undefined ? "" : menuImageSource smooth : true mipmap : true fillMode : Image.PreserveAspectFit @@ -286,9 +300,9 @@ FocusScope Text { id : menuItemText - text : displayText + text : model.modelData !== undefined ? model.modelData : displayText font : jaspTheme.font - color : isEnabled ? jaspTheme.black : jaspTheme.gray + color : model.modelData !== undefined || isEnabled ? jaspTheme.black : jaspTheme.gray anchors { left : menu.hasIcons ? menuItemImage.right : parent.left @@ -296,7 +310,6 @@ FocusScope rightMargin : menu._iconPad * 2 verticalCenter : parent.verticalCenter } - } MouseArea @@ -305,7 +318,7 @@ FocusScope hoverEnabled : true anchors.fill : parent onClicked : menu.props['functionCall'](index) - enabled : isEnabled + enabled : model.modelData !== undefined || isEnabled } } } @@ -328,7 +341,7 @@ FocusScope height : parent.height - (menu._iconPad * 2) width : height - source : menuImageSource + source : model.modelData !== undefined ? "" : menuImageSource smooth : true mipmap : true fillMode : Image.PreserveAspectFit @@ -346,7 +359,7 @@ FocusScope Text { id : menuItemText - text : displayText + text : model.modelData !== undefined ? model.modelData.substring(3) : displayText font : jaspTheme.fontGroupTitle color : jaspTheme.textEnabled anchors diff --git a/Desktop/components/JASP/Widgets/DataPanel.qml b/Desktop/components/JASP/Widgets/DataPanel.qml index 8726205487..7367274e5c 100644 --- a/Desktop/components/JASP/Widgets/DataPanel.qml +++ b/Desktop/components/JASP/Widgets/DataPanel.qml @@ -1,6 +1,6 @@ -import QtQuick 2.12 import QtQuick.Controls 6.0 import QtQuick.Layouts 1.0 +import QtQuick 2.15 Rectangle @@ -16,7 +16,42 @@ Rectangle anchors.fill: parent anchors.leftMargin: rootDataset.leftHandSpace orientation: Qt.Vertical - handle: Rectangle + + FilterWindow + { + id: filterWindow + objectName: "filterWindow" + SplitView.minimumHeight: desiredMinimumHeight + SplitView.preferredHeight: rootDataset.height * 0.25 + SplitView.maximumHeight: rootDataset.height * 0.8 + + } + + ComputeColumnWindow + { + id: computeColumnWindow + objectName: "computeColumnWindow" + SplitView.minimumHeight: desiredMinimumHeight + SplitView.preferredHeight: rootDataset.height * 0.25 + SplitView.maximumHeight: rootDataset.height * 0.8 + } + + VariablesWindow + { + id: variablesWindow + SplitView.minimumHeight: calculatedMinimumHeight + SplitView.preferredHeight: rootDataset.height * 0.25 + SplitView.maximumHeight: rootDataset.height * 0.8 + } + + DataTableView + { + objectName: "dataSetTableView" + SplitView.fillHeight: true + onDoubleClicked: mainWindow.startDataEditorHandler() + } + + handle: Rectangle { implicitHeight: jaspTheme.splitHandleWidth * 0.8; color: SplitHandle.hovered || SplitHandle.pressed ? jaspTheme.grayLighter : jaspTheme.uiBackground @@ -71,39 +106,5 @@ Rectangle } } } - - FilterWindow - { - id: filterWindow - objectName: "filterWindow" - SplitView.minimumHeight: desiredMinimumHeight - SplitView.preferredHeight: desiredMinimumHeight * 1.5 - SplitView.maximumHeight: rootDataset.height * 0.8 - - } - - ComputeColumnWindow - { - id: computeColumnWindow - objectName: "computeColumnWindow" - SplitView.minimumHeight: desiredMinimumHeight - SplitView.preferredHeight: desiredMinimumHeight * 1.5 - SplitView.maximumHeight: rootDataset.height * 0.8 - } - - VariablesWindow - { - id: variablesWindow - SplitView.minimumHeight: calculatedMinimumHeight - SplitView.preferredHeight: rootDataset.height * 0.25 - SplitView.maximumHeight: rootDataset.height * 0.8 - } - - DataTableView - { - objectName: "dataSetTableView" - SplitView.fillHeight: true - onDoubleClicked: mainWindow.startDataEditorHandler() - } } } diff --git a/Desktop/components/JASP/Widgets/DataTableView.qml b/Desktop/components/JASP/Widgets/DataTableView.qml index 3bae487745..17ad0f075f 100644 --- a/Desktop/components/JASP/Widgets/DataTableView.qml +++ b/Desktop/components/JASP/Widgets/DataTableView.qml @@ -1,7 +1,9 @@ -import QtQuick 2.9 -//import QtQuick.Controls 1.4 as Old -import QtQuick.Controls 2.2 -import JASP.Controls 1.0 as JaspControls + +import QtQuick +import QtQuick.Controls +import JASP.Controls as JaspControls +import QtQml.Models +import QtGraphicalEffects FocusScope @@ -20,19 +22,259 @@ FocusScope JASPDataView { - focus: __myRoot.focus + focus: __myRoot.focus - id: dataTableView - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: dataStatusBar.top + id: dataTableView + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: dataStatusBar.top itemHorizontalPadding: 8 * jaspTheme.uiScale itemVerticalPadding: 8 * jaspTheme.uiScale - model: dataSetModel - onDoubleClicked: __myRoot.doubleClicked() + model: dataSetModel + cacheItems: !ribbonModel.dataMode + + doubleClickWorkaround: !ribbonModel.dataMode + //flickableInteractive: !ribbonModel.dataMode + onDoubleClicked: __myRoot.doubleClicked() + + function showCopyPasteMenu(fromItem, globalPos, indexClicked) + { + console.log("showCopyPasteMenu!") + + view.contextMenuClickedAtIndex(indexClicked); + + var ctrlCmd = MACOS ? qsTr("Cmd") : qsTr("Ctrl"); + + var copyPasteMenuModel = + [ + qsTr("Select All"), + + "---", + + qsTr("Cut (%1+X)").arg(ctrlCmd), + qsTr("Copy (%1+C)").arg(ctrlCmd), + qsTr("Paste (%1+V)").arg(ctrlCmd), + + "---", //Works but is ugly: + qsTr("Including header:"), + + qsTr("Header cut (%1+Shift+X)").arg(ctrlCmd), + qsTr("Header copy (%1+Shift+C)").arg(ctrlCmd), + qsTr("Header paste (%1+Shift+V)").arg(ctrlCmd), + + "---", + + qsTr("Select column"), + qsTr("Insert column before"), + qsTr("Insert column after"), + qsTr("Delete column"), + + "---", + + qsTr("Select row"), + qsTr("Insert row before"), + qsTr("Insert row after"), + qsTr("Delete row"), + + ] + + var copyPasteMenuFunctions = + [ + function() { dataTableView.view.selectAll() }, + + function(){}, + + function() { dataTableView.view.cut( false)}, + function() { dataTableView.view.copy( false)}, + function() { dataTableView.view.paste(false)}, + + function (){}, + + function() { dataTableView.view.cut( true )}, + function() { dataTableView.view.copy( true )}, + function() { dataTableView.view.paste(true )}, + + function (){}, + + function() { dataTableView.view.columnSelect( indexClicked.column) }, + function() { dataTableView.view.columnInsertBefore( indexClicked.column) }, + function() { dataTableView.view.columnInsertAfter( indexClicked.column) }, + function() { dataTableView.view.columnDelete( indexClicked.column) }, + + function (){}, + + function() { dataTableView.view.rowSelect( indexClicked.row) }, + function() { dataTableView.view.rowInsertBefore( indexClicked.row) }, + function() { dataTableView.view.rowInsertAfter( indexClicked.row) }, + function() { dataTableView.view.rowDelete( indexClicked.row) } + + ] + + var props = { + "model": copyPasteMenuModel, + "functionCall": function (index) + { + var chosenElement = copyPasteMenuModel[index]; + + // console.log("Option " + chosenElement + " chosen, running function."); + + copyPasteMenuFunctions[index](); + + customMenu.hide() + } + }; + + //customMenu.scrollOri.x = __JASPDataViewRoot.contentX; + //customMenu.scrollOri.y = 0; + + var fromItemPos = fromItem.mapFromGlobal(globalPos.x, globalPos.y) + + customMenu.show(fromItem, props, fromItemPos.x, fromItemPos.y); + + //customMenu.menuScroll.x = Qt.binding(function() { return -1 * (__JASPDataViewRoot.contentX - customMenu.scrollOri.x); }); + //customMenu.menuScroll.y = 0; + //customMenu.menuMinIsMin = true + //customMenu.menuMaxPos.x = __JASPDataViewRoot.width + __JASPDataViewRoot.x + } + + editDelegate: + TextInput + { + id: editItem + text: itemText + color: itemActive ? jaspTheme.textEnabled : jaspTheme.textDisabled + font: jaspTheme.font + verticalAlignment: Text.AlignVCenter + onEditingFinished: finishEdit(); + z: 10 + + + Component.onCompleted: focusTimer.start(); + Timer + { + id: focusTimer + interval: 10 + repeat: false + onTriggered: + { + editItem.forceActiveFocus() + dataTableView.moveItemIntoView(editItem); + } + } + + property bool alreadyFinished: false + + Connections + { + target: ribbonModel + onFinishCurrentEdit: finishEdit(); + } + + function finishEdit() + { + if(!alreadyFinished) + dataTableView.view.editFinished(index, text); + alreadyFinished = true; + } + + Keys.onPressed: + { + var controlPressed = Boolean(event.modifiers & Qt.ControlModifier); + var shiftPressed = Boolean(event.modifiers & Qt.ShiftModifier ); + var arrowPressed = false; + var arrowIndex; + + switch(event.key) + { + case Qt.Key_C: + if(controlPressed) + { + theView.copy(shiftPressed); + event.accepted = true; + } + break; + + case Qt.Key_X: + if(controlPressed) + { + theView.cut(shiftPressed); + event.accepted = true; + } + break; + + case Qt.Key_V: + if(controlPressed) + { + theView.paste(shiftPressed); + event.accepted = true; + } + break; + + case Qt.Key_A: + if(controlPressed) + { + theView.selectAll(); + event.accepted = true; + } + break; + + case Qt.Key_Home: mainWindowRoot.changeFocusToFileMenu(); break; + + case Qt.Key_Up: if(rowIndex > 0) { arrowPressed = true; arrowIndex = dataSetModel.index(rowIndex - 1, columnIndex); } break; + case Qt.Key_Down: if(rowIndex < dataSetModel.rowCount() - 1) { arrowPressed = true; arrowIndex = dataSetModel.index(rowIndex + 1, columnIndex); } break; + case Qt.Key_Left: if(columnIndex > 0) { arrowPressed = true; arrowIndex = dataSetModel.index(rowIndex, columnIndex - 1); } break; + case Qt.Key_Right: if(columnIndex < dataSetModel.columnCount() - 1) { arrowPressed = true; arrowIndex = dataSetModel.index(rowIndex, columnIndex + 1); } break; + } + + if(arrowPressed) + { + finishEdit(); + + if(!shiftPressed) + dataTableView.view.selectionStart = arrowIndex; + else + { + dataTableView.view.selectionEnd = arrowIndex; + dataTableView.view.edit(arrowIndex); + } + + event.accepted = true; + } + + } + + Rectangle + { + id: highlighter + color: jaspTheme.itemHighlight + z: -1 + visible: ribbonModel.dataMode + anchors + { + fill: parent + topMargin: -dataTableView.itemVerticalPadding + leftMargin: -dataTableView.itemHorizontalPadding + rightMargin: -dataTableView.itemHorizontalPadding + bottomMargin: -dataTableView.itemVerticalPadding + } + + MouseArea + { + z: 1234 + anchors.fill: parent + acceptedButtons: Qt.RightButton + + onPressed: + if(mouse.buttons & Qt.RightButton) + { + finishEdit() + dataTableView.showCopyPasteMenu(editItem, mapToGlobal(mouse.x, mouse.y), dataSetModel.index(rowIndex, columnIndex)); + } + } + } + } itemDelegate: Text @@ -42,6 +284,61 @@ FocusScope color: itemActive ? jaspTheme.textEnabled : jaspTheme.textDisabled font: jaspTheme.font verticalAlignment: Text.AlignVCenter + + MouseArea + { + z: 1234 + hoverEnabled: true + anchors.fill: itemHighlight + acceptedButtons: Qt.LeftButton | Qt.RightButton + + onPressed: + if(ribbonModel.dataMode) + { + var shiftPressed = Boolean(mouse.modifiers & Qt.ShiftModifier); + + if(Boolean(mouse.buttons & Qt.RightButton)) + { + forceActiveFocus(); + dataTableView.showCopyPasteMenu(itemHighlight, mapToGlobal(mouse.x, mouse.y), dataSetModel.index(rowIndex, columnIndex)); + } + else + { + if(!shiftPressed) dataTableView.view.selectionStart = dataTableView.view.model.index(rowIndex, columnIndex); + else dataTableView.view.selectionEnd = dataTableView.view.model.index(rowIndex, columnIndex); + + + forceActiveFocus(); + } + } + + onPositionChanged: if(ribbonModel.dataMode && Boolean(mouse.modifiers & Qt.ShiftModifier)) + { + var idx = dataTableView.view.model.index(rowIndex, columnIndex) + dataTableView.view.pollSelectScroll(idx) + dataTableView.view.selectionEnd = idx + } + + } + + Rectangle + { + id: itemHighlight + visible: ribbonModel.dataMode && (dataTableView.selection.hasSelection, dataTableView.selection.isSelected(dataTableView.view.model.index(rowIndex, columnIndex))) + + color: jaspTheme.itemHighlight + opacity: 1.0 + z: -1 + + anchors + { + fill: parent + topMargin: -dataTableView.itemVerticalPadding + leftMargin: -dataTableView.itemHorizontalPadding + rightMargin: -dataTableView.itemHorizontalPadding + bottomMargin: -dataTableView.itemVerticalPadding + } + } } leftTopCornerItem: @@ -279,6 +576,8 @@ FocusScope labelModel.chosenColumn = columnIndex; labelModel.visible = changedIndex ? true : !labelModel.visible; } + else + dataSetModel.renameColumnDialog(columnIndex); if(dataSetModel.columnUsedInEasyFilter(columnIndex)) { diff --git a/Desktop/components/JASP/Widgets/JASPDataView.qml b/Desktop/components/JASP/Widgets/JASPDataView.qml index a987388be9..116a5d34f4 100644 --- a/Desktop/components/JASP/Widgets/JASPDataView.qml +++ b/Desktop/components/JASP/Widgets/JASPDataView.qml @@ -8,11 +8,12 @@ import JASP 1.0 FocusScope { id: __JASPDataViewRoot - + property alias view: theView property alias model: theView.model - property alias toolTip: datasetMouseArea.toolTipText - property alias cursorShape: datasetMouseArea.cursorShape - property alias mouseArea: datasetMouseArea + property alias selection: theView.selection + property string toolTip: "" + property alias cursorShape: wheelCatcher.cursorShape + property alias mouseArea: wheelCatcher property bool doubleClickWorkaround: true property alias itemDelegate: theView.itemDelegate @@ -20,37 +21,154 @@ FocusScope property alias columnHeaderDelegate: theView.columnHeaderDelegate property alias leftTopCornerItem: theView.leftTopCornerItem property alias extraColumnItem: theView.extraColumnItem + property alias editDelegate: theView.editDelegate property alias cacheItems: theView.cacheItems property alias itemHorizontalPadding: theView.itemHorizontalPadding property alias itemVerticalPadding: theView.itemVerticalPadding readonly property alias rowNumberWidth: theView.rowNumberWidth + readonly property alias headerHeight: theView.headerHeight readonly property alias contentX: myFlickable.contentX readonly property alias contentY: myFlickable.contentY readonly property alias contentWidth: myFlickable.contentWidth readonly property alias contentHeight: myFlickable.contentHeight + readonly property alias contentX0: myFlickable.contentX + readonly property alias contentY0: myFlickable.contentY + readonly property real contentX1: contentX + myFlickable.width + readonly property real contentY1: contentY + myFlickable.height readonly property alias verticalScrollWidth: vertiScroller.width readonly property alias horizontalScrollHeight: horiScroller.height - ///Aka without a scrollbar + + property alias horiScroller: horiScroller + property alias vertiScroller: vertiScroller + readonly property real flickableWidth: myFlickable.width - ///Aka without a scrollbar readonly property real flickableHeight: myFlickable.height - property alias flickableInteractive: myFlickable.interactive + property real contentFlickSize: 100 + + Keys.onUpPressed: { budgeUp(); event.accepted = true; } + Keys.onLeftPressed: { budgeLeft(); event.accepted = true; } + Keys.onDownPressed: { budgeDown(); event.accepted = true; } + Keys.onRightPressed: { budgeRight(); event.accepted = true; } + + function budgeUp() { if(myFlickable.contentY0 > 0) myFlickable.contentY = Math.max(0, myFlickable.contentY - contentFlickSize) } + function budgeDown() { if(myFlickable.contentY1 < myFlickable.contentHeight) myFlickable.contentY = Math.min(myFlickable.contentHeight - myFlickable.height, myFlickable.contentY + contentFlickSize) } + function budgeLeft() { if(myFlickable.contentX0 > 0) myFlickable.contentX = Math.max(0, myFlickable.contentX - contentFlickSize) } + function budgeRight() { if(myFlickable.contentX1 < myFlickable.contentWidth) myFlickable.contentX = Math.min(myFlickable.contentWidth - myFlickable.width, myFlickable.contentX + contentFlickSize) } + + + function moveItemIntoView(item) + { + var x0 = item.x - itemHorizontalPadding; + var x1 = item.x + itemHorizontalPadding + item.width; + var y0 = item.y - itemHorizontalPadding; + var y1 = item.y + itemHorizontalPadding + item.height; + + if ( x1 > contentX1 ) myFlickable.contentX = Math.max(rowNumberWidth, x1 - myFlickable.width) ; + else if ( x0 < contentX0 + rowNumberWidth) myFlickable.contentX = x0 - rowNumberWidth ; + if ( y1 > contentY1) myFlickable.contentY = Math.max(headerHeight, y1 - myFlickable.height); + else if ( y0 < contentY0 + headerHeight) myFlickable.contentY = y0 - headerHeight ; + } + + + Keys.onPressed: + { + var controlPressed = Boolean(event.modifiers & Qt.ControlModifier); + var shiftPressed = Boolean(event.modifiers & Qt.ShiftModifier ); + + if(controlPressed) + switch(event.key) + { + case Qt.Key_C: + theView.copy(shiftPressed); + event.accepted = true; + break; + + case Qt.Key_X: + theView.cut(shiftPressed); + event.accepted = true; + break; + + case Qt.Key_V: + theView.paste(shiftPressed); + event.accepted = true; + break; + + case Qt.Key_A: + theView.selectAll(); + event.accepted = true; + break; + } + } + + Flickable + { + id: myFlickable + z: -1 + clip: true + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: vertiScroller.left + anchors.bottom: horiScroller.top + + contentHeight: theView.height + contentWidth: theView.width + + DataSetView + { + z: -1 + id: theView + model: null + + /* voor Item + x: -myFlickable.contentX + y: -myFlickable.contentY + viewportX: myFlickable.contentX + viewportY: myFlickable.contentY + viewportW: myFlickable.width //myFlickable.visibleArea.widthRatio * width + viewportH: myFlickable.height //myFlickable.visibleArea.heightRatio * height + */ + + viewportX: myFlickable.contentX + viewportY: myFlickable.contentY + viewportW: myFlickable.visibleArea.widthRatio * width + viewportH: myFlickable.visibleArea.heightRatio * height + + onSelectionBudgesUp: __JASPDataViewRoot.budgeUp() + onSelectionBudgesDown: __JASPDataViewRoot.budgeDown() + onSelectionBudgesLeft: __JASPDataViewRoot.budgeLeft() + onSelectionBudgesRight: __JASPDataViewRoot.budgeRight() + } + } + /* + MouseArea + { + id: wheelCatcher + anchors.fill: myFlickable + acceptedButtons: Qt.NoButton + cursorShape: Qt.PointingHandCursor + z: 1000 + + } + */ + + signal doubleClicked() JASPMouseAreaToolTipped { - id: datasetMouseArea - z: 2 + id: wheelCatcher + z: 1000 anchors.fill: myFlickable anchors.leftMargin: theView.rowNumberWidth anchors.topMargin: theView.headerHeight - toolTipText: doubleClickWorkaround ? qsTr("Double click to edit data") : "" + toolTipText: __JASPDataViewRoot.doubleClickWorkaround ? qsTr("Double click to edit data") : "" - acceptedButtons: doubleClickWorkaround ? Qt.LeftButton : Qt.NoButton + acceptedButtons: __JASPDataViewRoot.doubleClickWorkaround ? Qt.LeftButton : Qt.NoButton dragging: myFlickable.dragging //hoverEnabled: !flickableInteractive @@ -59,7 +177,8 @@ FocusScope onPressed: (mouse)=> { - if(!doubleClickWorkaround) + console.log("doubleclick workaround pressed") + if(!__JASPDataViewRoot.doubleClickWorkaround) { mouse.accepted = false; return; @@ -69,11 +188,13 @@ FocusScope if(lastTimeClicked === -1 || curTime - lastTimeClicked > doubleClickTime) { + console.log("doubleclick workaround pressed set time") lastTimeClicked = curTime mouse.accepted = false } else { + console.log("doubleclick workaround activated") lastTimeClicked = -1 __JASPDataViewRoot.doubleClicked() } @@ -100,40 +221,10 @@ FocusScope else wheel.accepted = false; } - - - } - - signal doubleClicked() - - Flickable - { - id: myFlickable - z: -1 - clip: true - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: vertiScroller.left - anchors.bottom: horiScroller.top - - contentWidth: theView.width - contentHeight: theView.height - - DataSetView - { - z: -1 - id: theView - model: null - - viewportX: myFlickable.visibleArea.xPosition * width - viewportY: myFlickable.visibleArea.yPosition * height - viewportW: myFlickable.visibleArea.widthRatio * width - viewportH: myFlickable.visibleArea.heightRatio * height - } } + JASPScrollBar { id: vertiScroller; diff --git a/Desktop/components/JASP/Widgets/JASPMouseAreaToolTipped.qml b/Desktop/components/JASP/Widgets/JASPMouseAreaToolTipped.qml index 3f53e7049f..19e0a475f9 100644 --- a/Desktop/components/JASP/Widgets/JASPMouseAreaToolTipped.qml +++ b/Desktop/components/JASP/Widgets/JASPMouseAreaToolTipped.qml @@ -3,9 +3,10 @@ import QtQuick.Controls 2.2 MouseArea { - id: theMouseArea - anchors.fill: parent - hoverEnabled: true + id: theMouseArea + //hoverEnabled: true + propagateComposedEvents: true + //preventStealing: true cursorShape: dragging ? Qt.ClosedHandCursor : Qt.OpenHandCursor @@ -25,7 +26,7 @@ MouseArea text: theMouseArea.toolTipText delay: 0 timeout: theMouseArea.toolTipTimeOut - visible: theMouseArea._toolTipVisible + visible: theMouseArea._toolTipVisible && toolTipText != "" x: theMouseArea.mouseX - (width / 2) y: theMouseArea.mouseY + height diff --git a/Desktop/components/JASP/Widgets/MainPage.qml b/Desktop/components/JASP/Widgets/MainPage.qml index 6fc729975d..6d0b2bdae9 100644 --- a/Desktop/components/JASP/Widgets/MainPage.qml +++ b/Desktop/components/JASP/Widgets/MainPage.qml @@ -44,6 +44,7 @@ Item function minimizeDataPanel() { handleDataAnalyses.x = 0 + } function maximizeDataPanel() @@ -166,7 +167,7 @@ Item { id: handleAnalysesResults anchors.left: !dragging ? analysesPane.right : undefined - visible: hasAnalysis + visible: hasAnalysis && !ribbonModel.dataMode pointingLeft: analysesModel.visible onArrowClicked: analysesModel.visible = !analysesModel.visible toolTipDrag: hasData ? (handleAnalysesResults.pointingLeft ? qsTr("Resize data/results") : qsTr("Drag to show data")) : "" @@ -225,7 +226,7 @@ Item right: parent.right bottom: parent.bottom } - visible: hasAnalysis + visible: hasAnalysis && !ribbonModel.dataMode color: analysesModel.currentAnalysisIndex !== -1 ? jaspTheme.uiBackground : jaspTheme.white ALTNavigation.enabled: true diff --git a/Desktop/components/JASP/Widgets/MainWindow.qml b/Desktop/components/JASP/Widgets/MainWindow.qml index e81b56f4c7..6754e0195d 100644 --- a/Desktop/components/JASP/Widgets/MainWindow.qml +++ b/Desktop/components/JASP/Widgets/MainWindow.qml @@ -70,7 +70,7 @@ Window function changeFocusToFileMenu() { - ribbon.focus = true; + ribbon.forceActiveFocus(); ribbon.showFileMenuPressed(); } @@ -226,12 +226,9 @@ Window CreateComputeColumnDialog { id: createComputeDialog } ModuleInstaller { id: moduleInstallerDialog } - - PlotEditor - { - id: plotEditingDialog - visible: plotEditorModel.visible - } + ResizeDataDialog { id: resizeDataDialog } + RenameColumnDialog { id: renameColumnDialog } + PlotEditor { id: plotEditingDialog } /*MessageBox { diff --git a/Desktop/components/JASP/Widgets/RenameColumnDialog.qml b/Desktop/components/JASP/Widgets/RenameColumnDialog.qml new file mode 100644 index 0000000000..30fdb95725 --- /dev/null +++ b/Desktop/components/JASP/Widgets/RenameColumnDialog.qml @@ -0,0 +1,150 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import JASP.Controls 1.0 + +Popup +{ + id: popupRenameColumnDialog; + modal: true; + + y: (parent.height / 2) - (height / 2) + x: (parent.width / 2) - (width / 2) + width: popupLoader.width + height: popupLoader.height+1 + + property int colIndex; + + background: Rectangle + { + color: jaspTheme.uiBackground + border.color: jaspTheme.uiBorder + border.width: 1 + } + padding: 0 + + Connections + { + target: dataSetModel + onRenameColumnDialog: + { + console.log("renaming column dialog opened for " + String(columnIndex)) + colIndex = columnIndex; + popupRenameColumnDialog.open() + } + } + + Loader + { + id: popupLoader + sourceComponent: visible ? renameComp : null + visible: popupRenameColumnDialog.opened + } + + Component + { + id: renameComp + + Item + { + height: renameButton.y + renameButton.height + jaspTheme.generalAnchorMargin + width: 200 * jaspTheme.uiScale + + Component.onCompleted: columnName.forceActiveFocus(); + + Text + { + id: title + text: qsTr("Rename column") + font: jaspTheme.fontGroupTitle + color: jaspTheme.textEnabled + verticalAlignment: Text.AlignVCenter + anchors + { + top: parent.top + topMargin: jaspTheme.generalAnchorMargin + horizontalCenter: parent.horizontalCenter + } + } + + + TextInput + { + id: columnName + text: dataSetModel.columnName(popupRenameColumnDialog.colIndex) + color: jaspTheme.textEnabled + font: jaspTheme.fontGroupTitle + selectByMouse: true + anchors + { + top: title.bottom + left: parent.left + right: parent.right + margins: jaspTheme.generalAnchorMargin + } + + onEditingFinished: renameButton.clicked(); + Keys.onEnterPressed: renameButton.clicked(); + + KeyNavigation.tab: renameButton + KeyNavigation.down: renameButton + + Rectangle + { + color: jaspTheme.controlBackgroundColor + border.width: 1 + border.color: jaspTheme.borderColor + radius: jaspTheme.borderRadius + z: -1 + anchors.fill: parent + anchors.margins: -jaspTheme.jaspControlPadding + } + + MouseArea + { + acceptedButtons: Qt.NoButton + anchors.fill: parent + cursorShape: Qt.IBeamCursor + } + } + + RectangularButton + { + id: renameButton + activeFocusOnTab: true + text: qsTr("Rename") + onClicked: { dataSetModel.setColumnName(popupRenameColumnDialog.colIndex, columnName.text); popupRenameColumnDialog.close(); } + toolTip: qsTr("Rename column %1 to %2").arg(popupRenameColumnDialog.colIndex + 1).arg(columnName.text) + KeyNavigation.right: closeButtonCross + KeyNavigation.tab: closeButtonCross + + anchors + { + top: columnName.bottom + margins: jaspTheme.generalAnchorMargin + topMargin: jaspTheme.generalAnchorMargin + jaspTheme.jaspControlPadding + left: parent.left + right: closeButtonCross.left + } + } + + RectangularButton + { + id: closeButtonCross + activeFocusOnTab: true + iconSource: jaspTheme.iconPath + "cross.png" + width: height + height: renameButton.height + onClicked: popupResizeData.close() + toolTip: qsTr("Close without renaming column") + KeyNavigation.up: columnName + + anchors + { + right: parent.right + top: columnName.bottom + margins: jaspTheme.generalAnchorMargin + } + } + } + } +} diff --git a/Desktop/components/JASP/Widgets/ResizeDataDialog.qml b/Desktop/components/JASP/Widgets/ResizeDataDialog.qml new file mode 100644 index 0000000000..ef351c6ca3 --- /dev/null +++ b/Desktop/components/JASP/Widgets/ResizeDataDialog.qml @@ -0,0 +1,186 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import JASP.Controls 1.0 + +Popup +{ + id: popupResizeData; + modal: true; + + y: (parent.height / 2) - (height / 2) + x: (parent.width / 2) - (width / 2) + width: popupLoader.width + height: popupLoader.height+1 + + background: Rectangle + { + color: jaspTheme.uiBackground + border.color: jaspTheme.uiBorder + border.width: 1 + } + padding: 0 + + Connections + { + target: ribbonModel + onResizeData: popupResizeData.open() + } + + Loader + { + id: popupLoader + sourceComponent: visible ? resizeComp : null + visible: popupResizeData.opened + } + + Component + { + id: resizeComp + + Item + { + height: resizeButton.y + resizeButton.height + jaspTheme.generalAnchorMargin + width: inputs.width + + Component.onCompleted: cols.forceActiveFocus(); + + Text + { + id: title + text: qsTr("Resize Data") + font: jaspTheme.fontGroupTitle + color: jaspTheme.textEnabled + verticalAlignment: Text.AlignVCenter + anchors + { + top: parent.top + topMargin: jaspTheme.generalAnchorMargin + horizontalCenter: parent.horizontalCenter + } + } + + Item + { + id: inputs + width: 200 * jaspTheme.uiScale + height: cols.y + cols.height + jaspTheme.generalAnchorMargin + + anchors + { + top: title.bottom + left: parent.left + right: parent.right + margins: jaspTheme.generalAnchorMargin + } + + Label + { + id: colsLabel + text: qsTr("Columns") + anchors + { + top: inputs.top + left: cols.left + right: cols.right + } + } + + Label + { + id: rowsLabel + text: qsTr("Rows") + anchors + { + top: inputs.top + left: rows.left + right: rows.right + } + } + + Label + { + id: x + text: "X" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + anchors.centerIn: parent + } + + IntegerField + { + id: cols + value: dataSetModel.columnCount() + fieldWidth: width + anchors + { + top: x.verticalCenter + left: parent.left + right: x.left + margins: jaspTheme.generalAnchorMargin + } + + //KeyNavigation.tab: rows + KeyNavigation.right: rows + KeyNavigation.down: resizeButton + } + + IntegerField + { + id: rows + value: dataSetModel.rowCount() + fieldWidth: width + anchors + { + top: x.verticalCenter + left: x.right + right: parent.right + margins: jaspTheme.generalAnchorMargin + } + KeyNavigation.down: resizeButton + KeyNavigation.right: resizeButton + //KeyNavigation.tab: resizeButton + } + } + + RectangularButton + { + id: resizeButton + activeFocusOnTab: true + text: qsTr("Resize") + onClicked: { dataSetModel.resizeData(rows.value, cols.value); popupResizeData.close(); } + toolTip: qsTr("Resize data to set values") + KeyNavigation.right: closeButtonCross + //KeyNavigation.tab: closeButtonCross + //KeyNavigation.backtab: rows + KeyNavigation.left: rows + KeyNavigation.up: cols + + anchors + { + top: inputs.bottom + margins: jaspTheme.generalAnchorMargin + left: parent.left + right: closeButtonCross.left + } + } + + RectangularButton + { + id: closeButtonCross + activeFocusOnTab: true + iconSource: jaspTheme.iconPath + "cross.png" + width: height + height: resizeButton.height + onClicked: popupResizeData.close() + toolTip: qsTr("Close without resizing data") + KeyNavigation.up: rows + anchors + { + right: parent.right + top: inputs.bottom + margins: jaspTheme.generalAnchorMargin + } + } + } + } +} diff --git a/Desktop/components/JASP/Widgets/Ribbon/RibbonBar.qml b/Desktop/components/JASP/Widgets/Ribbon/RibbonBar.qml index 04cc6616ac..4e5208050c 100644 --- a/Desktop/components/JASP/Widgets/Ribbon/RibbonBar.qml +++ b/Desktop/components/JASP/Widgets/Ribbon/RibbonBar.qml @@ -132,14 +132,14 @@ FocusScope function showModulesMenuPressed() { - modulesPlusButton.focus = true; + modulesPlusButton.forceActiveFocus() modulesPlusButton.showPressed = true; } function showFileMenuPressed() { isFileMenuPressed = true; - fileMenuOpenButton.focus = true; + fileMenuOpenButton.forceActiveFocus(); } Rectangle diff --git a/Desktop/components/JASP/Widgets/Ribbon/RibbonButton.qml b/Desktop/components/JASP/Widgets/Ribbon/RibbonButton.qml index 26a36b10b3..cd3b079d7b 100644 --- a/Desktop/components/JASP/Widgets/Ribbon/RibbonButton.qml +++ b/Desktop/components/JASP/Widgets/Ribbon/RibbonButton.qml @@ -89,6 +89,7 @@ Rectangle { customMenu.hide() ribbonModel.analysisClicked("", "", "", ribbonButton.moduleName) + } else if (ribbonButton.menu.rowCount() === 1) { diff --git a/Desktop/components/JASP/Widgets/Ribbon/Ribbons.qml b/Desktop/components/JASP/Widgets/Ribbon/Ribbons.qml index 776c51a70f..46671c696c 100644 --- a/Desktop/components/JASP/Widgets/Ribbon/Ribbons.qml +++ b/Desktop/components/JASP/Widgets/Ribbon/Ribbons.qml @@ -29,6 +29,12 @@ Item onActiveFocusChanged: buttonList.focus = true; + Connections + { + target: ribbonModel + onDataModeChanged: focusOut(); + } + function setCurrentIndex(which, _index=null) { if (which === 'last') @@ -66,7 +72,7 @@ Item while(true) { - if (nextIndex === -1) { + if (nextIndex === -1) { buttonList.currentItem.focus = false; buttonList.currentItem.myMenuOpen = false; showFileMenuPressed(); @@ -172,8 +178,8 @@ Item text: model.moduleTitle listIndex: index moduleName: model.moduleName - source: !model.ribbonButton || model.ribbonButton.iconSource === "" ? "" : (!model.ribbonButton.special ? "file:" : "qrc:/icons/") + model.ribbonButton.iconSource - menu: !model.ribbonButton ? undefined : model.ribbonButton.analysisMenu + source: !model.ribbonButton || model.ribbonButton.iconSource === "" ? "" : (!model.ribbonButton.special ? "file:" : jaspTheme.iconPath ) + model.ribbonButton.iconSource + menu: !model.ribbonButton ? undefined : model.ribbonButton.menu toolTip: !model.ribbonButton ? undefined : model.ribbonButton.toolTip enabled: model.ribbonButton && model.active visible: model.ribbonButton diff --git a/Desktop/components/JASP/Widgets/VariablesWindow.qml b/Desktop/components/JASP/Widgets/VariablesWindow.qml index d08374e690..c09d720d7b 100644 --- a/Desktop/components/JASP/Widgets/VariablesWindow.qml +++ b/Desktop/components/JASP/Widgets/VariablesWindow.qml @@ -70,18 +70,42 @@ FocusScope anchors.fill: parent anchors.margins: jaspTheme.generalAnchorMargin - Text + TextInput { id: columnNameVariablesWindow text: labelModel.columnName + onTextChanged: labelModel.columnName = text color: jaspTheme.textEnabled font: jaspTheme.fontGroupTitle + enabled: ribbonModel.dataMode + selectByMouse: true + anchors { horizontalCenter: parent.horizontalCenter top: parent.top topMargin: jaspTheme.generalAnchorMargin } + + Rectangle + { + color: jaspTheme.controlBackgroundColor + border.color: jaspTheme.uiBorder + border.width: 1 + visible: enabled + + anchors.fill: parent + anchors.margins: -1 * jaspTheme.jaspControlPadding + z: -1 + } + + MouseArea + { + acceptedButtons: Qt.NoButton + anchors.fill: parent + cursorShape: Qt.IBeamCursor + } + } Rectangle diff --git a/Desktop/components/JASP/Widgets/qmldir b/Desktop/components/JASP/Widgets/qmldir index fdfddce849..51959f647a 100644 --- a/Desktop/components/JASP/Widgets/qmldir +++ b/Desktop/components/JASP/Widgets/qmldir @@ -34,3 +34,5 @@ ImageInverter 1.0 ImageInverter.qml PlotEditor 1.0 PlotEditor/PlotEditor.qml ErrorMessage 1.0 ErrorMessage.qml EnginesWindow 1.0 EnginesWindow.qml +ResizeDataDialog 1.0 ResizeDataDialog.qml +RenameColumnDialog 1.0 RenameColumnDialog.qml diff --git a/Desktop/data/computedcolumnsmodel.cpp b/Desktop/data/computedcolumnsmodel.cpp index 72b0ee50e5..ae16611e33 100644 --- a/Desktop/data/computedcolumnsmodel.cpp +++ b/Desktop/data/computedcolumnsmodel.cpp @@ -13,6 +13,7 @@ ComputedColumnsModel::ComputedColumnsModel() assert(_singleton == nullptr); _singleton = this; + //Is it really dataSetChanged that needs to connect to datasetLoadedChanged? connect(DataSetPackage::pkg(), &DataSetPackage::dataSetChanged, this, &ComputedColumnsModel::datasetLoadedChanged ); connect(this, &ComputedColumnsModel::datasetLoadedChanged, this, &ComputedColumnsModel::computeColumnJsonChanged ); diff --git a/Desktop/data/datasetpackage.cpp b/Desktop/data/datasetpackage.cpp index 260e907468..1af39a451c 100644 --- a/Desktop/data/datasetpackage.cpp +++ b/Desktop/data/datasetpackage.cpp @@ -29,6 +29,9 @@ #include "utilities/messageforwarder.h" #include "datasetpackagesubnodemodel.h" #include "databaseconnectioninfo.h" +#include "utilities/settings.h" +#include "modules/ribbonmodel.h" + DataSetPackage * DataSetPackage::_singleton = nullptr; @@ -43,6 +46,7 @@ DataSetPackage::DataSetPackage(QObject * parent) : QAbstractItemModel(parent) connect(this, &DataSetPackage::currentFileChanged, this, &DataSetPackage::windowTitleChanged); connect(this, &DataSetPackage::folderChanged, this, &DataSetPackage::windowTitleChanged); connect(this, &DataSetPackage::currentFileChanged, this, &DataSetPackage::nameChanged); + connect(this, &DataSetPackage::dataModeChanged, this, &DataSetPackage::logDataModeChanged); _dataSubModel = new SubNodeModel(parIdxType::dataRoot); _filterSubModel = new SubNodeModel(parIdxType::filterRoot); @@ -116,6 +120,7 @@ void DataSetPackage::reset() endLoadingData(); } + void DataSetPackage::setDataSet(DataSet * dataSet) { if(_dataSet == dataSet) @@ -155,6 +160,31 @@ void DataSetPackage::regenerateInternalPointers() _internalPointers.push_back({parIdxType::label, col}); //these do because they need to know which column is ref'd } +void DataSetPackage::generateEmptyData() +{ + if(_dataSet || isLoaded()) + { + Log::log() << "void DataSetPackage::generateEmptyData() called but dataset already exists, ignoring it." << std::endl; + return; + } + + beginLoadingData(); + createDataSet(); + setDataSetSize(1, 1); + initColumnAsScale(0, freeNewColumnName(0), { NAN }); + endLoadingData(); + emit newDataLoaded(); + resetAllFilters(); +} + +//Some debugprinting +void DataSetPackage::logDataModeChanged(bool dataMode) +{ + Log::log() << "Data Mode " << (dataMode ? "on" : "off") << "!" << std::endl; + beginResetModel(); + endResetModel(); +} + QModelIndex DataSetPackage::index(int row, int column, const QModelIndex &parent) const { const void * pointer = nullptr; @@ -192,8 +222,6 @@ QModelIndex DataSetPackage::index(int row, int column, const QModelIndex &parent } } - - return createIndex(row, column, pointer); } const DataSetPackage::intnlPntPair * DataSetPackage::getInternalPointerPairFromIndex(const QModelIndex & index) const @@ -312,9 +340,10 @@ int DataSetPackage::rowCount(const QModelIndex & parent) const int labelSize = col.labels().size(); return labelSize; } - case parIdxType::filterRoot: [[fallthrough]]; - case parIdxType::dataRoot: return !_dataSet ? 0 : _dataSet->rowCount(); - default: return 0; + + case parIdxType::filter: [[fallthrough]]; + default: [[fallthrough]]; + case parIdxType::data: return !_dataSet ? 0 : _dataSet->rowCount(); } return 0; // <- because gcc is stupid @@ -373,6 +402,8 @@ QVariant DataSetPackage::data(const QModelIndex &index, int role) const if(_dataSet == nullptr || index.column() >= int(_dataSet->columnCount()) || index.row() >= int(_dataSet->rowCount())) return QVariant(); // if there is no data then it doesn't matter what role we play + // Log::log() << "Data requested for col " << index.column() << " and row " << index.row() << std::endl; + switch(role) { case Qt::DisplayRole: @@ -483,8 +514,8 @@ bool DataSetPackage::setData(const QModelIndex &index, const QVariant &value, in return false; case parIdxType::data: - Log::log() << "setData for data is not supported!" << std::endl; - return false; + pasteSpreadsheet(index.row(), index.column(), {{value.toString()}}); + return true; case parIdxType::label: { @@ -915,35 +946,36 @@ int DataSetPackage::setColumnTypeFromQML(int columnIndex, int newColumnType) return int(getColumnType(columnIndex)); } -void DataSetPackage::beginSynchingData() +void DataSetPackage::beginSynchingData(bool informEngines) { - beginLoadingData(); + beginLoadingData(informEngines); _synchingData = true; } -void DataSetPackage::endSynchingDataChangedColumns(std::vector & changedColumns) +void DataSetPackage::endSynchingDataChangedColumns(std::vector & changedColumns, bool hasNewColumns, bool informEngines) { std::vector missingColumns; std::map changeNameColumns; - endSynchingData(changedColumns, missingColumns, changeNameColumns, false, false); + endSynchingData(changedColumns, missingColumns, changeNameColumns, hasNewColumns, informEngines); } void DataSetPackage::endSynchingData(std::vector & changedColumns, std::vector & missingColumns, std::map & changeNameColumns, //origname -> newname bool rowCountChanged, - bool hasNewColumns) + bool hasNewColumns, + bool informEngines) { - endLoadingData(); + endLoadingData(informEngines); _synchingData = false; //We convert all of this stuff to qt containers even though this takes time etc. Because it needs to go through a (queued) connection and it might not work otherwise emit datasetChanged(tq(changedColumns), tq(missingColumns), tq(changeNameColumns), rowCountChanged, hasNewColumns); } -void DataSetPackage::beginLoadingData() +void DataSetPackage::beginLoadingData(bool informEngines) { JASPTIMER_SCOPE(DataSetPackage::beginLoadingData); @@ -951,7 +983,7 @@ void DataSetPackage::beginLoadingData() beginResetModel(); } -void DataSetPackage::endLoadingData() +void DataSetPackage::endLoadingData(bool informEngines) { JASPTIMER_SCOPE(DataSetPackage::endLoadingData); @@ -1082,6 +1114,35 @@ bool DataSetPackage::initColumnAsNominalOrOrdinal( QVariant colID, std::string n return initColumnAsNominalOrOrdinal(colID.toString().toStdString(), newName, values, uniqueValues, is_ordinal); } + +void DataSetPackage::initColumnWithStrings(QVariant colId, std::string newName, const std::vector &values) +{ + // interpret the column as a datatype + std::set uniqueValues; + std::vector intValues; + std::vector doubleValues; + std::map emptyValuesMap; + + //If less unique integers than the thresholdScale then we think it must be ordinal: https://github.com/jasp-stats/INTERNAL-jasp/issues/270 + bool useCustomThreshold = Settings::value(Settings::USE_CUSTOM_THRESHOLD_SCALE).toBool(); + size_t thresholdScale = (useCustomThreshold ? Settings::value(Settings::THRESHOLD_SCALE) : Settings::defaultValue(Settings::THRESHOLD_SCALE)).toUInt(); + + bool valuesAreIntegers = Utils::convertVecToInt(values, intValues, uniqueValues, emptyValuesMap); + + size_t minIntForThresh = thresholdScale > 2 ? 2 : 0; + + auto isNominalInt = [&](){ return valuesAreIntegers && uniqueValues.size() == minIntForThresh; }; + auto isOrdinal = [&](){ return valuesAreIntegers && uniqueValues.size() > minIntForThresh && uniqueValues.size() <= thresholdScale; }; + auto isScalar = [&](){ return Utils::convertVecToDouble(values, doubleValues, emptyValuesMap); }; + + if (isOrdinal()) initColumnAsNominalOrOrdinal( colId, newName, intValues, true ); + else if (isNominalInt()) initColumnAsNominalOrOrdinal( colId, newName, intValues, false ); + else if (isScalar()) initColumnAsScale( colId, newName, doubleValues ); + else emptyValuesMap = initColumnAsNominalText( colId, newName, values ); + + storeInEmptyValues(newName, emptyValuesMap); +} + void DataSetPackage::enlargeDataSetIfNecessary(std::function tryThis, const char * callerText) { while(true) @@ -1351,6 +1412,46 @@ std::vector DataSetPackage::getColumnDataDbls(size_t columnIndex) return std::vector(col.AsDoubles.begin(), col.AsDoubles.end()); } +std::vector DataSetPackage::getColumnDataStrs(size_t columnIndex) +{ + if(_dataSet == nullptr) return {}; + + Column & col = _dataSet->column(columnIndex); + + std::vector out; + + for(size_t r=0; rcolumn(columnIndex).setName(newName); + endResetModel(); + + emit datasetChanged({}, {}, QMap({{tq(oldName), tq(newName)}}), false, false); +} + + + void DataSetPackage::setColumnDataInts(size_t columnIndex, std::vector ints) { Column & col = _dataSet->column(columnIndex); @@ -1540,6 +1641,221 @@ void DataSetPackage::columnSetDefaultValues(std::string columnName, columnType c } } +std::string DataSetPackage::freeNewColumnName(size_t startHere) +{ + const QString nameBase = tr("Column %1"); + + while(true) + { + const std::string newColName = fq(nameBase.arg(++startHere)); + if(isColumnNameFree(newColName)) + return newColName; + } +} + +void DataSetPackage::unicifyColumnNames() +{ + for(int c=0; c columnCount(), + newRows = int(rows) > rowCount(); + size_t oriRows = rowCount(), + oriCols = columnCount(); + + beginSynchingData(false); //I assume this is all being called in dataMode, so the engines will be informed once we are done + setDataSetSize(cols, rows); + + for(size_t c=newRows ? 0 : oriCols; c colVals = getColumnDataStrs(c); + std::string colName = getColumnName(c); + + for(size_t r=oriRows; r(), + missing = fq(namesBefore); + + std::map changeNameColumns; + + endSynchingData(changed, missing, changeNameColumns, rowsChanged, newCols, false); + +} + +void DataSetPackage::pasteSpreadsheet(size_t row, size_t col, const std::vector> & cells, QStringList newColNames) +{ + int rowMax = ( cells.size() > 0 ? cells[0].size() : 0), + colMax = cells.size(); + bool rowCountChanged = int(row + rowMax) > rowCount() , + colCountChanged = int(col + colMax) > columnCount() ; + + setSynchingExternally(false); //Don't synch with external file after editing + + //beginResetModel(); + beginSynchingData(false); + + if(colCountChanged || rowCountChanged) + setDataSetSize(std::max(size_t(columnCount()), colMax + col), std::max(size_t(rowCount()), rowMax + row)); + + + + stringvec colNames = getColumnNames(false); + + std::vector changed; + + for(int c=0; c colVals = getColumnDataStrs(dataCol); + + for(int r=0; r c && newColNames[c] != "" ? fq(newColNames[c]) : colName == "" ? freeNewColumnName(dataCol) : colName; + + initColumnWithStrings(dataCol, newName, colVals); + + if(newName != "") + changed.push_back(newName); + + } + + std::map changeNameColumns; + if(newColNames.size() > 0) + { + unicifyColumnNames(); + + stringvec colNamesNew = getColumnNames(false); + + for(size_t i=0; i missingColumns; + + + endSynchingData(changed, missingColumns, changeNameColumns, rowCountChanged, colCountChanged, false); + + if(isLoaded()) setModified(true); +} + +void DataSetPackage::columnInsert(size_t column) +{ + setSynchingExternally(false); //Don't synch with external file after editing + beginSynchingData(false); + + _dataSet->columns().insertColumn(column); + setColumnName(column, freeNewColumnName(column)); + + stringvec changed({getColumnName(column)}); + endSynchingDataChangedColumns(changed, true, false); +} + +void DataSetPackage::columnDelete(size_t column) +{ + setSynchingExternally(false); //Don't synch with external file after editing + beginSynchingData(false); + + std::vector changed; + std::map changeNameColumns; + std::vector missingColumns({getColumnName(column)}); + + _dataSet->columns().removeColumn(column); + + endSynchingData(changed, missingColumns, changeNameColumns, false, true, false); +} + +void DataSetPackage::rowInsert(size_t row) +{ + setSynchingExternally(false); //Don't synch with external file after editing + beginSynchingData(false); + stringvec changed; + + setDataSetSize(columnCount(), rowCount()+1); + + for(int c=0; c colVals = getColumnDataStrs(c); + + colVals.insert(colVals.begin() + row, ""); + + if(int(colVals.size()) > rowCount()) + { + Log::log() << "ASSUMPTION CORRECT! I guess?" << std::endl; + colVals.resize(rowCount()); + } + initColumnWithStrings(c, name, colVals); + } + + std::map changeNameColumns; + std::vector missingColumns; + + endSynchingData(changed, missingColumns, changeNameColumns, true, false, false); +} + + + +void DataSetPackage::rowDelete(size_t row) +{ + setSynchingExternally(false); //Don't synch with external file after editing + beginSynchingData(false); + stringvec changed; + + for(int c=0; c colVals = getColumnDataStrs(c); + + colVals.erase(colVals.begin() + row); + colVals.push_back(""); + + initColumnWithStrings(c, name, colVals); + } + + setDataSetSize(columnCount(), rowCount()-1); + + std::map changeNameColumns; + std::vector missingColumns; + + endSynchingData(changed, missingColumns, changeNameColumns, true, false, false); +} + bool DataSetPackage::createColumn(std::string name, columnType columnType) { if(getColumnIndex(name) >= 0) return false; @@ -1630,9 +1946,35 @@ void DataSetPackage::databaseStartSynching(bool syncImmediately) if(syncImmediately) emit synchingIntervalPassed(); + + emit synchingExternallyChanged(); } } +bool DataSetPackage::synchingExternally() const +{ + return PreferencesModel::prefs()->dataAutoSynchronization() && (_dataFilePath != "" || (_database != Json::nullValue && _databaseIntervalSyncher.isActive())); +} + +void DataSetPackage::setSynchingExternally(bool synchingExternally_) +{ + if(synchingExternally() == synchingExternally_) + return; + + if (!synchingExternally_) + { + PreferencesModel::prefs()->setDataAutoSynchronization(false); + } + else + { + if(_dataFilePath == "") + emit askUserForExternalDataFile(); + else + PreferencesModel::prefs()->setDataAutoSynchronization(true); + } + + emit synchingExternallyChanged(); +} void DataSetPackage::setCurrentFile(QString currentFile) { @@ -1676,7 +2018,6 @@ void DataSetPackage::setFolder(QString folder) emit folderChanged(); } - QString DataSetPackage::name() const { QFileInfo file(_currentFile); @@ -1687,6 +2028,16 @@ QString DataSetPackage::name() const return "JASP"; } +bool DataSetPackage::dataMode() const +{ + return RibbonModel::singleton()->dataMode(); +} + +QModelIndex DataSetPackage::lastCurrentCell() +{ + throw std::runtime_error("Not implemented!"); +} + QString DataSetPackage::windowTitle() const { QString name = DataSetPackage::name(), diff --git a/Desktop/data/datasetpackage.h b/Desktop/data/datasetpackage.h index 4a5577234c..71ab75fc9b 100644 --- a/Desktop/data/datasetpackage.h +++ b/Desktop/data/datasetpackage.h @@ -57,13 +57,15 @@ class DataSetPackageSubNodeModel; class DataSetPackage : public QAbstractItemModel //Not QAbstractTableModel because of: https://stackoverflow.com/a/38999940 (And this being a tree model) { Q_OBJECT - Q_PROPERTY(int columnsFilteredCount READ columnsFilteredCount NOTIFY columnsFilteredCountChanged ) - Q_PROPERTY(QString name READ name NOTIFY nameChanged ) - Q_PROPERTY(QString folder READ folder WRITE setFolder NOTIFY folderChanged ) - Q_PROPERTY(QString windowTitle READ windowTitle NOTIFY windowTitleChanged ) - Q_PROPERTY(bool modified READ isModified WRITE setModified NOTIFY isModifiedChanged ) - Q_PROPERTY(bool loaded READ isLoaded WRITE setLoaded NOTIFY loadedChanged ) - Q_PROPERTY(QString currentFile READ currentFile WRITE setCurrentFile NOTIFY currentFileChanged ) + Q_PROPERTY(int columnsFilteredCount READ columnsFilteredCount NOTIFY columnsFilteredCountChanged ) + Q_PROPERTY(QString name READ name NOTIFY nameChanged ) + Q_PROPERTY(QString folder READ folder WRITE setFolder NOTIFY folderChanged ) + Q_PROPERTY(QString windowTitle READ windowTitle NOTIFY windowTitleChanged ) + Q_PROPERTY(bool modified READ isModified WRITE setModified NOTIFY isModifiedChanged ) + Q_PROPERTY(bool loaded READ isLoaded WRITE setLoaded NOTIFY loadedChanged ) + Q_PROPERTY(QString currentFile READ currentFile WRITE setCurrentFile NOTIFY currentFileChanged ) + Q_PROPERTY(bool dataMode READ dataMode NOTIFY dataModeChanged ) + Q_PROPERTY(bool synchingExternally READ synchingExternally WRITE setSynchingExternally NOTIFY synchingExternallyChanged ) typedef std::map> emptyValsType; typedef std::pair intnlPntPair; //first value is what kind of data the index is for and the int is for parIdxType::label only, to know which column is selected. @@ -80,6 +82,7 @@ class DataSetPackage : public QAbstractItemModel //Not QAbstractTableModel becau ~DataSetPackage(); void setEngineSync(EngineSync * engineSync); void reset(); + void resizeData(size_t rowCount, size_t columnCount); //Should do reset and such things unlike setDataSetSize void setDataSetSize(size_t columnCount, size_t rowCount); void setDataSetColumnCount(size_t columnCount) { setDataSetSize(columnCount, dataRowCount()); } void setDataSetRowCount(size_t rowCount) { setDataSetSize(dataColumnCount(), rowCount); } @@ -100,17 +103,21 @@ class DataSetPackage : public QAbstractItemModel //Not QAbstractTableModel becau SubNodeModel * labelsSubModel () { return _labelsSubModel; } void waitForExportResultsReady(); - - void beginLoadingData(); - void endLoadingData(); - void beginSynchingData(); - void endSynchingDataChangedColumns(std::vector & changedColumns); + + + void beginLoadingData( bool informEngines = true); + void endLoadingData( bool informEngines = true); + void beginSynchingData( bool informEngines = true); + void endSynchingDataChangedColumns(std::vector & changedColumns, bool hasNewColumns = false, bool informEngines = true); void endSynchingData(std::vector & changedColumns, std::vector & missingColumns, std::map & changeNameColumns, //origname -> newname bool rowCountChanged, - bool hasNewColumns); + bool hasNewColumns, bool informEngines = true); + + + void initColumnWithStrings(QVariant colId, std::string newName, const std::vector &values); QHash roleNames() const override; int rowCount( const QModelIndex &parent = QModelIndex()) const override; @@ -140,6 +147,8 @@ class DataSetPackage : public QAbstractItemModel //Not QAbstractTableModel becau std::string id() const { return _id; } QString name() const; QString folder() const { return _folder; } + bool dataMode() const; + bool isReady() const { return _analysesHTMLReady; } bool isLoaded() const { return _isLoaded; } bool isArchive() const { return _isArchive; } @@ -174,7 +183,7 @@ class DataSetPackage : public QAbstractItemModel //Not QAbstractTableModel becau void setAnalysesData(const Json::Value & analysesData) { _analysesData = analysesData; } void setArchiveVersion(Version archiveVersion) { _archiveVersion = archiveVersion; } void setWarningMessage(std::string message) { _warningMessage = message; } - void setDataFilePath(std::string filePath) { _dataFilePath = filePath; } + void setDataFilePath(std::string filePath) { _dataFilePath = filePath; emit synchingExternallyChanged(); } void setDatabaseJson(const Json::Value & dbInfo); void setInitialMD5(std::string initialMD5) { _initialMD5 = initialMD5; } void setDataFileTimestamp(uint timestamp) { _dataFileTimestamp = timestamp; } @@ -205,6 +214,12 @@ class DataSetPackage : public QAbstractItemModel //Not QAbstractTableModel becau std::map initColumnAsNominalText( size_t colNo, std::string newName, const std::vector & values, const std::map & labels = std::map()); std::map initColumnAsNominalText( std::string colName, std::string newName, const std::vector & values, const std::map & labels = std::map()) { return initColumnAsNominalText(_dataSet->getColumnIndex(colName), newName, values, labels); } std::map initColumnAsNominalText( QVariant colID, std::string newName, const std::vector & values, const std::map & labels = std::map()); + + void pasteSpreadsheet(size_t row, size_t column, const std::vector> & cells, QStringList newColNames = QStringList()); + void columnInsert( size_t column ); //Maybe these functions should be made to depend on "columnInsert" etc from AbstractItemModel + void columnDelete( size_t column ); + void rowInsert( size_t row ); + void rowDelete( size_t row ); void columnSetDefaultValues(std::string columnName, columnType colType = columnType::unknown); bool createColumn(std::string name, columnType colType); @@ -239,20 +254,23 @@ class DataSetPackage : public QAbstractItemModel //Not QAbstractTableModel becau Json::Value columnToJsonForJASPFile(size_t columnIndex, Json::Value & labelsData, size_t & dataSize); void columnLabelsFromJsonForJASPFile(Json::Value xData, Json::Value columnDesc, size_t columnIndex, std::map > & mapNominalTextValues); - enum columnType getColumnType(std::string columnName) const; - enum columnType getColumnType(size_t columnIndex) const { return _dataSet ? _dataSet->column(columnIndex).getColumnType() : columnType::unknown; } - std::string getColumnName(size_t columnIndex) const { return _dataSet ? _dataSet->column(columnIndex).name() : ""; } - int getColumnIndex(std::string name) const { return !_dataSet ? -1 : _dataSet->getColumnIndex(name); } - int getColumnIndex(QString name) const { return getColumnIndex(name.toStdString()); } - std::vector getColumnDataInts(size_t columnIndex); - std::vector getColumnDataDbls(size_t columnIndex); - void setColumnDataInts(size_t columnIndex, std::vector ints); - void setColumnDataDbls(size_t columnIndex, std::vector dbls); + int getColumnIndex( std::string name) const { return !_dataSet ? -1 : _dataSet->getColumnIndex(name); } + int getColumnIndex( QString name) const { return getColumnIndex(name.toStdString()); } + enum columnType getColumnType( std::string columnName) const; + enum columnType getColumnType( size_t columnIndex) const { return _dataSet ? _dataSet->column(columnIndex).getColumnType() : columnType::unknown; } + std::string getColumnName( size_t columnIndex) const { return _dataSet ? _dataSet->column(columnIndex).name() : ""; } + std::vector getColumnDataInts( size_t columnIndex); + std::vector getColumnDataDbls( size_t columnIndex); + std::vector getColumnDataStrs( size_t columnIndex); + void setColumnName( size_t columnIndex, const std::string & newName); + void setColumnDataInts( size_t columnIndex, std::vector ints); + void setColumnDataDbls( size_t columnIndex, std::vector dbls); size_t getMaximumColumnWidthInCharacters(int columnIndex) const; QStringList getColumnLabelsAsStringList(std::string columnName) const; QStringList getColumnLabelsAsStringList(size_t columnIndex) const; QStringList getColumnValuesAsStringList(size_t columnIndex) const; QList getColumnValuesAsDoubleList(size_t columnIndex) const; + void unicifyColumnNames(); bool setFilterData(std::string filter, std::vector filterResult); void resetFilterAllows(size_t columnIndex); @@ -268,8 +286,7 @@ class DataSetPackage : public QAbstractItemModel //Not QAbstractTableModel becau void databaseStartSynching(bool syncImmediately); void databaseStopSynching(); - - + bool synchingExternally() const; signals: void datasetChanged( QStringList changedColumns, @@ -302,6 +319,10 @@ class DataSetPackage : public QAbstractItemModel //Not QAbstractTableModel becau void loadedChanged(); void currentFileChanged(); void synchingIntervalPassed(); + void newDataLoaded(); + void dataModeChanged(bool dataMode); + void synchingExternallyChanged(); + void askUserForExternalDataFile(); public slots: void refresh() { beginResetModel(); endResetModel(); } @@ -312,12 +333,19 @@ public slots: void emptyValuesChangedHandler(); void setCurrentFile(QString currentFile); void setFolder(QString folder); + void generateEmptyData(); + void logDataModeChanged(bool dataMode); + + + void setSynchingExternally(bool synchingExternally); private: ///This function allows you to run some code that changes something in the _dataSet and will try to enlarge it if it fails with an allocation error. Otherwise it might keep going for ever? void enlargeDataSetIfNecessary(std::function tryThis, const char * callerText); bool isThisTheSameThreadAsEngineSync(); bool setAllowFilterOnLabel(const QModelIndex & index, bool newAllowValue); + std::string freeNewColumnName(size_t startHere); + QModelIndex lastCurrentCell(); private: diff --git a/Desktop/data/datasettablemodel.cpp b/Desktop/data/datasettablemodel.cpp index 101bfd7048..3577878048 100644 --- a/Desktop/data/datasettablemodel.cpp +++ b/Desktop/data/datasettablemodel.cpp @@ -17,6 +17,7 @@ // #include "datasettablemodel.h" +#include "utilities/qutils.h" DataSetTableModel* DataSetTableModel::_singleton = nullptr; @@ -71,3 +72,44 @@ QStringList DataSetTableModel::getColumnLabelsAsStringList(int col) const return labels; } + +QString DataSetTableModel::columnName(int column) const +{ + //map to source might be needed here once we start filtering columns + return tq(getColumnName(column)); +} + +void DataSetTableModel::setColumnName(int col, QString name) const +{ + return DataSetPackage::pkg()->setColumnName(col, fq(name)); +} + +void DataSetTableModel::pasteSpreadsheet(size_t row, size_t col, const std::vector > & cells, QStringList newColNames) +{ + QModelIndex idx = mapToSource(index(row, col)); + DataSetPackage::pkg()->pasteSpreadsheet(idx.row(), idx.column(), cells, newColNames); +} + +void DataSetTableModel::columnInsert(size_t column) +{ + QModelIndex idx = mapToSource(index(0, column)); + DataSetPackage::pkg()->columnInsert(idx.column()); +} + +void DataSetTableModel::columnDelete(size_t column) +{ + QModelIndex idx = mapToSource(index(0, column)); + DataSetPackage::pkg()->columnDelete(idx.column()); +} + +void DataSetTableModel::rowInsert(size_t row) +{ + QModelIndex idx = mapToSource(index(row, 0)); + DataSetPackage::pkg()->rowInsert(idx.row()); +} + +void DataSetTableModel::rowDelete(size_t row) +{ + QModelIndex idx = mapToSource(index(row, 0)); + DataSetPackage::pkg()->rowDelete(idx.row()); +} diff --git a/Desktop/data/datasettablemodel.h b/Desktop/data/datasettablemodel.h index f26b4eb6b6..6a7c3cc5da 100644 --- a/Desktop/data/datasettablemodel.h +++ b/Desktop/data/datasettablemodel.h @@ -41,10 +41,14 @@ class DataSetTableModel : public DataSetTableProxy int columnsFilteredCount() const { return DataSetPackage::pkg()->columnsFilteredCount(); } Q_INVOKABLE bool isColumnNameFree(QString name) { return DataSetPackage::pkg()->isColumnNameFree(name); } Q_INVOKABLE QVariant columnTitle(int column) const { return DataSetPackage::pkg()->getColumnTitle(column); } + Q_INVOKABLE QVariant columnIcon(int column) const { return DataSetPackage::pkg()->getColumnIcon(column); } + Q_INVOKABLE QString columnName(int column) const; + Q_INVOKABLE void setColumnName(int col, QString name) const; Q_INVOKABLE QVariant getColumnTypesWithCorrespondingIcon() const { return DataSetPackage::pkg()->getColumnTypesWithCorrespondingIcon(); } Q_INVOKABLE bool columnUsedInEasyFilter(int column) const { return DataSetPackage::pkg()->isColumnUsedInEasyFilter(column); } Q_INVOKABLE void resetAllFilters() { DataSetPackage::pkg()->resetAllFilters(); } Q_INVOKABLE int setColumnTypeFromQML(int columnIndex, int newColumnType) { return DataSetPackage::pkg()->setColumnTypeFromQML(columnIndex, newColumnType); } + Q_INVOKABLE void resizeData(int row, int col) { DataSetPackage::pkg()->resizeData(row, col); } columnType getColumnType(size_t column) const { return DataSetPackage::pkg()->getColumnType(column); } std::string getColumnName(size_t col) const { return DataSetPackage::pkg()->getColumnName(col); } @@ -56,6 +60,12 @@ class DataSetTableModel : public DataSetTableProxy QModelIndex parentModelForType(parIdxType type, int column = 0) const { return DataSetPackage::pkg()->parentModelForType(type, column); } bool synchingData() const { return DataSetPackage::pkg()->synchingData(); } + void pasteSpreadsheet(size_t row, size_t col, const std::vector> & cells, QStringList newColNames = QStringList()); + void columnInsert( size_t column ); + void columnDelete( size_t column ); + void rowInsert( size_t row ); + void rowDelete( size_t row ); + bool showInactive() const { return _showInactive; } signals: @@ -65,6 +75,8 @@ class DataSetTableModel : public DataSetTableProxy void labelChanged(QString columnName, QString originalLabel, QString newLabel); void labelsReordered(QString columnName); + void renameColumnDialog(int columnIndex); + public slots: void setShowInactive(bool showInactive); //void onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { if( roles.count(int(DataSetPackage::specialRoles::filter)) > 0) invalidateFilter(); } diff --git a/Desktop/data/importers/importcolumn.cpp b/Desktop/data/importers/importcolumn.cpp index 1b9788a1fc..81835bb633 100644 --- a/Desktop/data/importers/importcolumn.cpp +++ b/Desktop/data/importers/importcolumn.cpp @@ -19,63 +19,6 @@ std::string ImportColumn::name() const return _name; } - -bool ImportColumn::convertVecToInt(const std::vector &values, std::vector &intValues, std::set &uniqueValues, std::map &emptyValuesMap) -{ - emptyValuesMap.clear(); - uniqueValues.clear(); - intValues.clear(); - intValues.reserve(values.size()); - - int row = 0; - - for (const std::string &value : values) - { - int intValue = std::numeric_limits::lowest(); - - if (ColumnUtils::convertValueToIntForImport(value, intValue)) - { - if (intValue != std::numeric_limits::lowest()) uniqueValues.insert(intValue); - else if (!value.empty()) emptyValuesMap.insert(make_pair(row, value)); - - intValues.push_back(intValue); - } - else - return false; - - row++; - } - - return true; -} - -bool ImportColumn::convertVecToDouble(const std::vector &values, std::vector &doubleValues, std::map &emptyValuesMap) -{ - emptyValuesMap.clear(); - doubleValues.clear(); - doubleValues.reserve(values.size()); - - int row = 0; - for (const std::string &value : values) - { - double doubleValue = static_cast(NAN); - - if (ColumnUtils::convertValueToDoubleForImport(value, doubleValue)) - { - doubleValues.push_back(doubleValue); - - if (std::isnan(doubleValue) && value != "") - emptyValuesMap.insert(std::make_pair(row, value)); - } - else - return false; - - row++; - } - - return true; -} - void ImportColumn::changeName(const std::string & name) { Log::log() << "Changing name of column from '" << _name << "' to '" << name << "'\n." << std::endl; diff --git a/Desktop/data/importers/importcolumn.h b/Desktop/data/importers/importcolumn.h index ba0d11e6eb..0981525608 100644 --- a/Desktop/data/importers/importcolumn.h +++ b/Desktop/data/importers/importcolumn.h @@ -23,9 +23,6 @@ class ImportColumn std::string name() const; void changeName(const std::string & name); - static bool convertVecToInt( const std::vector & values, std::vector & intValues, std::set &uniqueValues, std::map &emptyValuesMap); - static bool convertVecToDouble( const std::vector & values, std::vector & doubleValues, std::map &emptyValuesMap); - static bool isStringValueEqual(const std::string &value, Column &col, size_t row); protected: diff --git a/Desktop/data/importers/importer.cpp b/Desktop/data/importers/importer.cpp index 340a123b28..6c5f513962 100644 --- a/Desktop/data/importers/importer.cpp +++ b/Desktop/data/importers/importer.cpp @@ -39,34 +39,6 @@ void Importer::initColumn(QVariant colId, ImportColumn *importColumn) initColumnWithStrings(colId, importColumn->name(), importColumn->allValuesAsStrings()); } -void Importer::initColumnWithStrings(QVariant colId, std::string newName, const std::vector &values) -{ - // interpret the column as a datatype - std::set uniqueValues; - std::vector intValues; - std::vector doubleValues; - std::map emptyValuesMap; - - //If less unique integers than the thresholdScale then we think it must be ordinal: https://github.com/jasp-stats/INTERNAL-jasp/issues/270 - bool useCustomThreshold = Settings::value(Settings::USE_CUSTOM_THRESHOLD_SCALE).toBool(); - size_t thresholdScale = (useCustomThreshold ? Settings::value(Settings::THRESHOLD_SCALE) : Settings::defaultValue(Settings::THRESHOLD_SCALE)).toUInt(); - - bool valuesAreIntegers = ImportColumn::convertVecToInt(values, intValues, uniqueValues, emptyValuesMap); - - size_t minIntForThresh = thresholdScale > 2 ? 2 : 0; - - auto isNominalInt = [&](){ return valuesAreIntegers && uniqueValues.size() == minIntForThresh; }; - auto isOrdinal = [&](){ return valuesAreIntegers && uniqueValues.size() > minIntForThresh && uniqueValues.size() <= thresholdScale; }; - auto isScalar = [&](){ return ImportColumn::convertVecToDouble(values, doubleValues, emptyValuesMap); }; - - if (isOrdinal()) initColumnAsNominalOrOrdinal( colId, newName, intValues, true ); - else if (isNominalInt()) initColumnAsNominalOrOrdinal( colId, newName, intValues, false ); - else if (isScalar()) initColumnAsScale( colId, newName, doubleValues ); - else emptyValuesMap = initColumnAsNominalText( colId, newName, values ); - - storeInEmptyValues(newName, emptyValuesMap); -} - void Importer::syncDataSet(const std::string &locator, boost::function progress) { ImportDataSet *importDataSet = loadFile(locator, progress); diff --git a/Desktop/data/importers/importer.h b/Desktop/data/importers/importer.h index 3c187ee3b7..81a05726db 100644 --- a/Desktop/data/importers/importer.h +++ b/Desktop/data/importers/importer.h @@ -29,7 +29,7 @@ class Importer ///colID can be either an integer (the column index in the data) or a string (the (old) name of the column in the data) virtual void initColumn(QVariant colId, ImportColumn *importColumn); - void initColumnWithStrings(QVariant colId, std::string newName, const std::vector &values); + void initColumnWithStrings(QVariant colId, const std::string & newName, const std::vector & values) { DataSetPackage::pkg()->initColumnWithStrings(colId, newName, values); } ///colID can be either an integer (the column index in the data) or a string (the (old) name of the column in the data) bool initColumnAsNominalOrOrdinal( QVariant colID, std::string newName, const std::vector & values, bool is_ordinal = false) { return DataSetPackage::pkg()->initColumnAsNominalOrOrdinal(colID, newName, values, is_ordinal); } diff --git a/Desktop/data/importers/readstat/readstatimportcolumn.cpp b/Desktop/data/importers/readstat/readstatimportcolumn.cpp index 4152a4a5eb..5f6ce6c42d 100644 --- a/Desktop/data/importers/readstat/readstatimportcolumn.cpp +++ b/Desktop/data/importers/readstat/readstatimportcolumn.cpp @@ -31,7 +31,7 @@ std::string ReadStatImportColumn::valueAsString(size_t row) const switch(_type) { default: return missingValueString(); - case columnType::scale: return Utils::doubleToString(_doubles[row]); + case columnType::scale: return ColumnUtils::doubleToString(_doubles[row]); case columnType::ordinal: [[fallthrough]]; case columnType::nominal: return std::to_string(_ints[row]); case columnType::nominalText: return _strings[row]; @@ -164,14 +164,14 @@ void ReadStatImportColumn::setType(columnType newType) case columnType::nominal: for(double d : _doubles) if(isMissingValue(d)) _ints.push_back(missingValueInt()); - else if(d != double(int(d))) conversionFailed("Double '" + Utils::doubleToString(d) + "' cannot be converted to int."); + else if(d != double(int(d))) conversionFailed("Double '" + ColumnUtils::doubleToString(d) + "' cannot be converted to int."); else _ints.push_back(int(d)); break; case columnType::nominalText: for(double d : _doubles) - _strings.push_back(isMissingValue(d) ? missingValueString() : Utils::doubleToString(d)); + _strings.push_back(isMissingValue(d) ? missingValueString() : ColumnUtils::doubleToString(d)); _doubles.clear(); break; default: break; @@ -345,7 +345,7 @@ void ReadStatImportColumn::addValue(const double & val) [[fallthrough]]; case columnType::nominalText: - addValue(Utils::doubleToString(val)); + addValue(ColumnUtils::doubleToString(val)); break; } } @@ -421,7 +421,7 @@ void ReadStatImportColumn::addLabel(const double & val, const std::string & labe setType(columnType::nominalText); //Because we do not support having doubles as values for labels } - addLabel(Utils::doubleToString(val), label); + addLabel(ColumnUtils::doubleToString(val), label); } void ReadStatImportColumn::addLabel(const std::string & val, const std::string & label) diff --git a/Desktop/data/labelmodel.cpp b/Desktop/data/labelmodel.cpp index a8343f42df..dba20283c9 100644 --- a/Desktop/data/labelmodel.cpp +++ b/Desktop/data/labelmodel.cpp @@ -1,6 +1,7 @@ #include "labelmodel.h" #include "log.h" #include "jasptheme.h" +#include "utilities/qutils.h" LabelModel::LabelModel() : DataSetTableProxy(DataSetPackage::pkg()->labelsSubModel()) { @@ -32,6 +33,20 @@ std::string LabelModel::columnName(size_t col) return DataSetPackage::pkg()->getColumnName(col); } +QString LabelModel::columnNameQ() +{ + return QString::fromStdString(columnName(proxyParentColumn())); +} + + +void LabelModel::setColumnNameQ(QString newColumnName) +{ + if(DataSetPackage::pkg()->columnCount() <= int(proxyParentColumn())) + return; + + return DataSetPackage::pkg()->setColumnName(proxyParentColumn(), fq(newColumnName)); +} + std::vector LabelModel::filterAllows(size_t col) { DataSetPackage * pkg = DataSetPackage::pkg(); @@ -288,7 +303,6 @@ void LabelModel::setSelected(int row, int modifier) } - void LabelModel::unselectAll() { _selected.clear(); diff --git a/Desktop/data/labelmodel.h b/Desktop/data/labelmodel.h index ad618b9afe..bad123895a 100644 --- a/Desktop/data/labelmodel.h +++ b/Desktop/data/labelmodel.h @@ -15,7 +15,7 @@ class LabelModel : public DataSetTableProxy Q_PROPERTY(int filteredOut READ filteredOut NOTIFY filteredOutChanged ) Q_PROPERTY(int chosenColumn READ proxyParentColumn WRITE setProxyParentColumn NOTIFY proxyParentColumnChanged ) Q_PROPERTY(bool visible READ visible WRITE setVisible NOTIFY visibleChanged ) - Q_PROPERTY(QString columnName READ columnNameQ NOTIFY columnNameChanged ) + Q_PROPERTY(QString columnName READ columnNameQ WRITE setColumnNameQ NOTIFY columnNameChanged ) Q_PROPERTY(double rowWidth READ rowWidth WRITE setRowWidth NOTIFY rowWidthChanged ) Q_PROPERTY(double valueMaxWidth READ valueMaxWidth NOTIFY valueMaxWidthChanged ) Q_PROPERTY(double labelMaxWidth READ labelMaxWidth NOTIFY labelMaxWidthChanged ) @@ -25,7 +25,7 @@ class LabelModel : public DataSetTableProxy bool labelNeedsFilter(size_t col); std::string columnName(size_t col); - QString columnNameQ() { return QString::fromStdString(columnName(proxyParentColumn())); } + QString columnNameQ(); bool setData(const QModelIndex & index, const QVariant & value, int role = -1) override; QVariant data( const QModelIndex & index, int role = Qt::DisplayRole) const override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; @@ -53,6 +53,7 @@ public slots: void filteredOutChangedHandler(int col); void setVisible(bool visible); void setSelected(int row, int modifier); + void setColumnNameQ(QString newColumnName); void removeAllSelected(); void columnAboutToBeRemoved(int column); void columnDataTypeChanged(const QString & colName); diff --git a/Desktop/engine/enginesync.cpp b/Desktop/engine/enginesync.cpp index 5288ae73b5..b56b8f0c4a 100644 --- a/Desktop/engine/enginesync.cpp +++ b/Desktop/engine/enginesync.cpp @@ -501,7 +501,6 @@ void EngineSync::computeColumn(const QString & columnName, const QString & compu void EngineSync::processFilterScript() { - if (!_waitingFilter) return; @@ -993,6 +992,9 @@ void EngineSync::pauseEngines(bool unloadData) { JASPTIMER_SCOPE(EngineSync::pauseEngines); + + Log::log() << "EngineSync::pauseEngines()" << std::endl; + //make sure we process any received messages first. for(auto * engine : _engines) engine->processReplies(); @@ -1022,7 +1024,9 @@ void EngineSync::startStoppedEngine(EngineRepresentation * engine) void EngineSync::resumeEngines() { JASPTIMER_SCOPE(EngineSync::resumeEngines); - + + Log::log() << "EngineSync::resumeEngines()" << std::endl; + for(EngineRepresentation * engine : _engines) startStoppedEngine(engine); @@ -1065,6 +1069,17 @@ bool EngineSync::allEnginesInitializing(std::set these) return true; } +void EngineSync::dataModeChanged(bool dataMode) +{ + if(!dataMode) + { + Log::log() << "Data mode turned off, so restarting engines." << std::endl; + + pauseEngines(); + resumeEngines(); + } +} + void EngineSync::enginesPrepareForData() { JASPTIMER_SCOPE(EngineSync::enginesPrepareForData); diff --git a/Desktop/engine/enginesync.h b/Desktop/engine/enginesync.h index 58caf45d67..97344b42d0 100644 --- a/Desktop/engine/enginesync.h +++ b/Desktop/engine/enginesync.h @@ -54,8 +54,6 @@ class EngineSync : public QAbstractListModel EngineRepresentation * createNewEngine(bool addToEngines = true, int overrideChannel = -1); EngineRepresentation * createRCmdEngine(); - std::string currentStateForDebug() const; - int rowCount(const QModelIndex & = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QHash roleNames() const override; @@ -85,6 +83,9 @@ public slots: void enginesPrepareForData(); void enginesReceiveNewData(); bool isModuleInstallRequestActive(const QString & moduleName); + std::string currentStateForDebug() const; + void dataModeChanged(bool dataMode); + signals: void processNewFilterResult(const std::vector & filterResult, int requestID); diff --git a/Desktop/mainwindow.cpp b/Desktop/mainwindow.cpp index 46a27b465b..73f44854f1 100644 --- a/Desktop/mainwindow.cpp +++ b/Desktop/mainwindow.cpp @@ -69,7 +69,7 @@ #include "modules/activemodules.h" #include "modules/dynamicmodules.h" -#include "modules/analysismenumodel.h" +#include "modules/menumodel.h" #include "modules/description/entrybase.h" #include "qquick/datasetview.h" @@ -282,6 +282,7 @@ void MainWindow::makeConnections() connect(this, &MainWindow::screenPPIChanged, _preferences, &PreferencesModel::setDefaultPPI ); connect(this, &MainWindow::editImageCancelled, _resultsJsInterface, &ResultsJsInterface::cancelImageEdit ); connect(this, &MainWindow::dataAvailableChanged, _dynamicModules, &DynamicModules::setDataLoaded ); + connect(this, &MainWindow::dataAvailableChanged, _ribbonModel, &RibbonModel::dataLoadedChanged ); connect(_package, &DataSetPackage::datasetChanged, _filterModel, &FilterModel::datasetChanged, Qt::QueuedConnection); connect(_package, &DataSetPackage::datasetChanged, _computedColumnsModel, &ComputedColumnsModel::datasetChanged, Qt::QueuedConnection); @@ -292,7 +293,13 @@ void MainWindow::makeConnections() connect(_package, &DataSetPackage::freeDatasetSignal, _loader, &AsyncLoader::free ); connect(_package, &DataSetPackage::checkDoSync, _loader, &AsyncLoader::checkDoSync, Qt::DirectConnection); //Force DirectConnection because the signal is called from Importer which means it is running in AsyncLoaderThread... connect(_package, &DataSetPackage::synchingIntervalPassed, this, &MainWindow::syncKeyPressed ); - + connect(_package, &DataSetPackage::newDataLoaded, this, &MainWindow::populateUIfromDataSet ); + connect(_package, &DataSetPackage::newDataLoaded, _fileMenu, [&](){ _fileMenu->enableButtonsForOpenedWorkspace(); } ); + connect(_package, &DataSetPackage::dataModeChanged, _analyses, &Analyses::dataModeChanged ); + connect(_package, &DataSetPackage::dataModeChanged, _engineSync, &EngineSync::dataModeChanged ); + //connect(_package, &DataSetPackage::dataModeChanged, this, &MainWindow::onDataModeChanged ); + connect(_package, &DataSetPackage::askUserForExternalDataFile, this, &MainWindow::startDataEditorHandler ); + connect(_engineSync, &EngineSync::computeColumnSucceeded, _computedColumnsModel, &ComputedColumnsModel::computeColumnSucceeded ); connect(_engineSync, &EngineSync::computeColumnFailed, _computedColumnsModel, &ComputedColumnsModel::computeColumnFailed ); connect(_engineSync, &EngineSync::engineTerminated, this, &MainWindow::fatalError, Qt::QueuedConnection); //To give the process some time to realize it has crashed or something @@ -364,7 +371,7 @@ void MainWindow::makeConnections() connect(_preferences, &PreferencesModel::missingValuesChanged, _package, &DataSetPackage::emptyValuesChangedHandler ); connect(_preferences, &PreferencesModel::dataLabelNAChanged, _package, &DataSetPackage::refresh, Qt::QueuedConnection); - + connect(_preferences, &PreferencesModel::dataAutoSynchronizationChanged, _package, &DataSetPackage::synchingExternallyChanged ); connect(_preferences, &PreferencesModel::plotBackgroundChanged, this, &MainWindow::setImageBackgroundHandler ); connect(_preferences, &PreferencesModel::plotPPIChanged, this, &MainWindow::plotPPIChangedHandler ); connect(_preferences, &PreferencesModel::dataAutoSynchronizationChanged, _fileMenu, &FileMenu::dataAutoSynchronizationChanged ); @@ -397,6 +404,8 @@ void MainWindow::makeConnections() connect(_ribbonModel, &RibbonModel::analysisClickedSignal, _analyses, &Analyses::analysisClickedHandler ); connect(_ribbonModel, &RibbonModel::showRCommander, this, &MainWindow::showRCommander ); + connect(_ribbonModel, &RibbonModel::generateEmptyData, _package, &DataSetPackage::generateEmptyData ); + connect(_ribbonModel, &RibbonModel::dataModeChanged, _package, &DataSetPackage::dataModeChanged ); connect(_dynamicModules, &DynamicModules::dynamicModuleUnloadBegin, _analyses, &Analyses::removeAnalysesOfDynamicModule ); connect(_dynamicModules, &DynamicModules::dynamicModuleChanged, _analyses, &Analyses::refreshAnalysesOfDynamicModule ); @@ -1180,6 +1189,7 @@ void MainWindow::dataSetIOCompleted(FileEvent *event) _package->setDataSet(nullptr); // Prevent analyses to change the dataset if they have computed columns. _analyses->clear(); _package->reset(); + _ribbonModel->showStatistics(); setWelcomePageVisible(true); @@ -1229,7 +1239,7 @@ void MainWindow::populateUIfromDataSet() matchComputedColumnsToAnalyses(); - _package->setLoaded(); + _package->setLoaded(true); checkUsedModules(); _resultsJsInterface->setScrollAtAll(true); diff --git a/Desktop/mainwindow.h b/Desktop/mainwindow.h index f6aba5ffdf..12bff0280b 100644 --- a/Desktop/mainwindow.h +++ b/Desktop/mainwindow.h @@ -237,6 +237,7 @@ private slots: void resetQmlCache(); void setCurrentJaspTheme(); + //void onDataModeChanged(bool dataMode) { if(analysesAvailable()) setDataPanelVisible(dataMode); } void printQmlWarnings(const QList &warnings); void setQmlImportPaths(); diff --git a/Desktop/modules/analysisentry.cpp b/Desktop/modules/analysisentry.cpp index 90592a427d..476592ec8d 100644 --- a/Desktop/modules/analysisentry.cpp +++ b/Desktop/modules/analysisentry.cpp @@ -23,6 +23,29 @@ namespace Modules { +AnalysisEntry::AnalysisEntry(std::function specialFunc, std::string menuTitle, bool requiresData, std::string icon) + : _title(menuTitle), _function(menuTitle), _menu(menuTitle), _isSeparator(false), _isGroupTitle(!specialFunc), _requiresData(requiresData), _icon(icon), _specialFunc(specialFunc) +{} + +AnalysisEntry::AnalysisEntry(Json::Value & analysisEntry, DynamicModule * dynamicModule, bool defaultRequiresData) : + _title( analysisEntry.get("title", "???").asString() ), + _function( analysisEntry.get("function", "???").asString() ), + _qml( analysisEntry.get("qml", _function != "???" ? _function + ".qml" : "???").asString() ), + _menu( analysisEntry.get("menu", _title).asString() ), + _dynamicModule( dynamicModule ), + _isSeparator( true), + _requiresData( analysisEntry.get("requiresData", defaultRequiresData).asBool() ), + _icon( analysisEntry.get("icon", "").asString() ) +{ + for (size_t i = 0; i < _title.length(); ++i) + if (_title[i] != '-') _isSeparator = false; + + _isGroupTitle = !_isSeparator && !(analysisEntry.isMember("qml") || analysisEntry.isMember("function")); + _isAnalysis = !_isGroupTitle && !_isSeparator; +} + +AnalysisEntry::AnalysisEntry(){} + DynamicModule* AnalysisEntry::dynamicModule() const { return _dynamicModule; @@ -80,4 +103,14 @@ std::string AnalysisEntry::buttonMenuString() const return dynamicModule() == nullptr ? function() : codedReference(); } + +bool AnalysisEntry::requiresDataEntries(const AnalysisEntries & entries) +{ + for(const AnalysisEntry * entry : entries) + if(!entry->requiresData()) + return false; + + return true; +} + } // namespace Modules diff --git a/Desktop/modules/analysisentry.h b/Desktop/modules/analysisentry.h index 74f97f1186..220224a54c 100644 --- a/Desktop/modules/analysisentry.h +++ b/Desktop/modules/analysisentry.h @@ -23,11 +23,14 @@ #include #include #include +#include namespace Modules { class DynamicModule; class EntryBase; +class AnalysisEntry; +typedef std::vector AnalysisEntries; /// All information required to show an analysis/separator/grouptitle in a module-menu @@ -36,7 +39,9 @@ class AnalysisEntry { friend EntryBase; public: - AnalysisEntry() {} + AnalysisEntry(std::function specialFunc, std::string menuTitle, bool requiresData=true, std::string icon = ""); + AnalysisEntry(Json::Value & analysisEntry, DynamicModule * dynamicModule, bool defaultRequiresData = true); + AnalysisEntry(); std::string menu() const { return _menu; } std::string title() const { return _title; } @@ -60,23 +65,26 @@ class AnalysisEntry std::string codedReference() const; std::string buttonMenuString() const; + void runSpecialFunc() const { _specialFunc(); } + + static bool requiresDataEntries(const AnalysisEntries & entries); + private: - std::string _title = "???", - _function = "???", - _qml = "???", - _menu = "???"; - DynamicModule* _dynamicModule = nullptr; - bool _isSeparator = true, - _isGroupTitle = false, - _isAnalysis = false, - _isEnabled = true, - _requiresData = true; - std::string _icon = ""; - bool _hasWrapper = false; + std::string _title = "???" , + _function = "???" , + _qml = "???" , + _menu = "???" , + _icon = "" ; + DynamicModule* _dynamicModule = nullptr ; + bool _isSeparator = true , + _isGroupTitle = false , + _isAnalysis = false , + _isEnabled = true , + _requiresData = true , + _hasWrapper = false ; + std::function _specialFunc = nullptr ; }; -typedef std::vector AnalysisEntries; - } #endif // ANALYSISENTRY_H diff --git a/Desktop/modules/dynamicmodule.cpp b/Desktop/modules/dynamicmodule.cpp index 140ad51cbb..4b5145a21a 100644 --- a/Desktop/modules/dynamicmodule.cpp +++ b/Desktop/modules/dynamicmodule.cpp @@ -843,16 +843,6 @@ std::string DynamicModule::extractPackageNameFromFolder(const std::string & fold return foundName; } -bool DynamicModule::requiresData() const -{ - for(const AnalysisEntry * entry : _menuEntries) - if(!entry->requiresData()) - return false; - - return true; -} - - Json::Value DynamicModule::asJsonForJaspFile(const std::string & analysisFunction) const { Json::Value json(Json::objectValue); diff --git a/Desktop/modules/dynamicmodule.h b/Desktop/modules/dynamicmodule.h index dde6bb5823..329ec46196 100644 --- a/Desktop/modules/dynamicmodule.h +++ b/Desktop/modules/dynamicmodule.h @@ -100,7 +100,7 @@ class DynamicModule : public QObject QString nameQ() const { return QString::fromStdString(name()); } std::string title() const { return (isDevMod() ? "Dev: " : "") + _title; } QString titleQ() const { return QString::fromStdString(title()); } - bool requiresData() const; + bool requiresData() const { return AnalysisEntry::requiresDataEntries(_menuEntries); } std::string author() const { return _author; } std::string version() const { return _version; } QString versionQ() const { return QString::fromStdString(_version); } diff --git a/Desktop/modules/analysismenumodel.cpp b/Desktop/modules/menumodel.cpp similarity index 73% rename from Desktop/modules/analysismenumodel.cpp rename to Desktop/modules/menumodel.cpp index 231ec72e0f..250eeea9c5 100644 --- a/Desktop/modules/analysismenumodel.cpp +++ b/Desktop/modules/menumodel.cpp @@ -17,17 +17,25 @@ // -#include "analysismenumodel.h" +#include "menumodel.h" #include "modules/ribbonbutton.h" -AnalysisMenuModel::AnalysisMenuModel(RibbonButton *parent, Modules::DynamicModule * module) +MenuModel::MenuModel(RibbonButton *parent, Modules::DynamicModule * module) : QAbstractListModel(parent), _ribbonButton(parent), _module(module) { } -QVariant AnalysisMenuModel::data(const QModelIndex &index, int role) const +MenuModel::MenuModel(RibbonButton * parent, Modules::AnalysisEntries * entries) +: QAbstractListModel(parent), _ribbonButton(parent), _entries(entries) +{ + for(const auto * entry : *_entries) + if(entry->icon() != "") + _hasIcons = true; +} + +QVariant MenuModel::data(const QModelIndex &index, int role) const { if (index.row() >= rowCount()) return QVariant(); @@ -47,7 +55,7 @@ QVariant AnalysisMenuModel::data(const QModelIndex &index, int role) const } -QHash AnalysisMenuModel::roleNames() const +QHash MenuModel::roleNames() const { static const auto roles = QHash{ { DisplayRole, "displayText" }, @@ -61,25 +69,23 @@ QHash AnalysisMenuModel::roleNames() const return roles; } -Modules::AnalysisEntry *AnalysisMenuModel::getAnalysisEntry(const std::string& name) +Modules::AnalysisEntry *MenuModel::getAnalysisEntry(const std::string& func) { for (Modules::AnalysisEntry* analysis : analysisEntries()) { - if (analysis->function() == name) + if (analysis->function() == func) return analysis; } return nullptr; } -const std::vector & AnalysisMenuModel::analysisEntries() const +const std::vector & MenuModel::analysisEntries() const { - static const std::vector dummy; - - return _module ? _module->menu() : dummy; + return _module ? _module->menu() : *_entries; } -bool AnalysisMenuModel::isAnalysisEnabled(int index) +bool MenuModel::isAnalysisEnabled(int index) { return analysisEntries().at(index)->isEnabled() && (!analysisEntries().at(index)->requiresData() || _ribbonButton->dataLoaded()); } diff --git a/Desktop/modules/analysismenumodel.h b/Desktop/modules/menumodel.h similarity index 72% rename from Desktop/modules/analysismenumodel.h rename to Desktop/modules/menumodel.h index 809c040df0..6517c20ee8 100644 --- a/Desktop/modules/analysismenumodel.h +++ b/Desktop/modules/menumodel.h @@ -30,7 +30,7 @@ class RibbonButton; /// This class should not hold it's own data but simply be an interface for the description of a RibbonButton/Dynamic Module /// And I suppose that means it could also be subclassed by Description? So that that directly delivers the entries? /// This all depends on how EntryBase and AnalysisEntry are merged though -class AnalysisMenuModel : public QAbstractListModel +class MenuModel : public QAbstractListModel { Q_OBJECT @@ -44,32 +44,40 @@ class AnalysisMenuModel : public QAbstractListModel IsEnabledRole }; - AnalysisMenuModel(RibbonButton * parent, Modules::DynamicModule * module); + MenuModel(RibbonButton * parent, Modules::DynamicModule * module); + MenuModel(RibbonButton * parent, Modules::AnalysisEntries * entries = new Modules::AnalysisEntries()); //The default new entries is to make sure that analysisEntries() has something to return for special buttons without their own entries + + ~MenuModel() + { + if(_entries) delete _entries; + _entries = nullptr; + } int rowCount(const QModelIndex &parent = QModelIndex()) const override { return analysisEntries().size(); } QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QHash roleNames() const override; - const std::vector & analysisEntries() const; + const Modules::AnalysisEntries & analysisEntries() const; - Modules::AnalysisEntry* getAnalysisEntry(const std::string& name); + Modules::AnalysisEntry* getAnalysisEntry(const std::string& func); Q_INVOKABLE QString getFirstAnalysisFunction() { return getAnalysisFunction(0); } Q_INVOKABLE QString getFirstAnalysisTitle() { return getAnalysisTitle(0); } Q_INVOKABLE QString getFirstAnalysisQML() { return getAnalysisQML(0); } Q_INVOKABLE QString getAnalysisFunction(int index) const { return QString::fromStdString(analysisEntries().at(index)->function()); } - Q_INVOKABLE QString getAnalysisTitle(int index) const { return QString::fromStdString(analysisEntries().at(index)->title()); } - Q_INVOKABLE QString getAnalysisQML(int index) const { return QString::fromStdString(analysisEntries().at(index)->qml()); } - Q_INVOKABLE bool isAnalysisEnabled(int index); + Q_INVOKABLE QString getAnalysisTitle( int index) const { return QString::fromStdString(analysisEntries().at(index)->title()); } + Q_INVOKABLE QString getAnalysisQML( int index) const { return QString::fromStdString(analysisEntries().at(index)->qml()); } + Q_INVOKABLE bool isAnalysisEnabled( int index); void setDynamicModule(Modules::DynamicModule * module) { beginResetModel(); _module = module; endResetModel(); } Q_INVOKABLE bool hasIcons() const { return _hasIcons; } private: - RibbonButton * _ribbonButton = nullptr; - Modules::DynamicModule * _module = nullptr; - bool _hasIcons = false; + RibbonButton * _ribbonButton = nullptr; + Modules::DynamicModule * _module = nullptr; + Modules::AnalysisEntries * _entries = nullptr; //For special buttons + bool _hasIcons = false; }; #endif diff --git a/Desktop/modules/ribbonbutton.cpp b/Desktop/modules/ribbonbutton.cpp index 0b978a2683..40fd445b80 100644 --- a/Desktop/modules/ribbonbutton.cpp +++ b/Desktop/modules/ribbonbutton.cpp @@ -48,10 +48,12 @@ void RibbonButton::setDynamicModule(DynamicModule * module) connect(_module, &DynamicModule::readyChanged, this, &RibbonButton::setReady, Qt::QueuedConnection); connect(_module, &DynamicModule::errorChanged, this, &RibbonButton::setError ); - if(!_analysisMenuModel) - _analysisMenuModel = new AnalysisMenuModel(this, _module); + if(!_menuModel) + _menuModel = new MenuModel(this, _module); - _analysisMenuModel->setDynamicModule(_module); + _menuModel->setDynamicModule(_module); + + setReady(_module->installed()); } } @@ -71,6 +73,21 @@ void RibbonButton::reloadDynamicModule(DynamicModule * dynMod) emit iChanged(this); } +void RibbonButton::setRemember(bool remember) +{ + if (_remember == remember) + return; + + _remember = remember; + emit rememberChanged(_remember); +} + +void RibbonButton::runSpecial(QString func) +{ + if(_specialButtonFunc) _specialButtonFunc(); + else _menuModel->getAnalysisEntry(fq(func))->runSpecialFunc(); +} + void RibbonButton::setError(bool error) { if (_error == error) @@ -94,10 +111,11 @@ void RibbonButton::setReady(bool ready) setEnabled(true); } -RibbonButton::RibbonButton(QObject *parent, std::string name, std::string title, std::string icon, bool requiresData, std::function justThisFunction, std::string toolTip) - : QObject(parent), _module(nullptr), _specialButtonFunc(justThisFunction) +RibbonButton::RibbonButton(QObject *parent, std::string name, std::string title, std::string icon, bool requiresData, std::function justThisFunction, std::string toolTip, bool enabled, bool remember) + : QObject(parent), _enabled(enabled), _remember(remember), _special(true), _module(nullptr), _specialButtonFunc(justThisFunction) { - _analysisMenuModel = new AnalysisMenuModel(this, nullptr); + _menuModel = new MenuModel(this); + setModuleName(name); setTitle(title); setToolTip(tq(toolTip)); @@ -108,6 +126,21 @@ RibbonButton::RibbonButton(QObject *parent, std::string name, std::string title, bindYourself(); } +RibbonButton::RibbonButton(QObject *parent, std::string name, std::string title, std::string icon, Modules::AnalysisEntries * funcEntries, bool enabled, bool remember) + : QObject(parent), _enabled(enabled), _remember(remember), _special(true), _module(nullptr) +{ + _menuModel = new MenuModel(this, funcEntries); + + setRequiresData(AnalysisEntry::requiresDataEntries(*funcEntries)); + setModuleName(name); + setTitle(title); + setIconSource(tq(icon)); + + bindYourself(); +} + + + void RibbonButton::bindYourself() { connect(this, &RibbonButton::enabledChanged, this, &RibbonButton::somePropertyChanged); @@ -163,11 +196,11 @@ void RibbonButton::setEnabled(bool enabled) { if(!isSpecial()) { - if(enabled) DynamicModules::dynMods()->loadModule(moduleName()); - else DynamicModules::dynMods()->unloadModule(moduleName()); + if(enabled) DynamicModules::dynMods()->loadModule(name()); + else DynamicModules::dynMods()->unloadModule(name()); } - emit DynamicModules::dynMods()->moduleEnabledChanged(moduleNameQ(), enabled); + emit DynamicModules::dynMods()->moduleEnabledChanged(nameQ(), enabled); } } @@ -185,27 +218,27 @@ void RibbonButton::setIsCommon(bool isCommon) void RibbonButton::setModuleName(std::string moduleName) { - if (_moduleName == moduleName) + if (_name == moduleName) return; - _moduleName = moduleName; + _name = moduleName; emit moduleNameChanged(); } DynamicModule * RibbonButton::dynamicModule() { - return _module; //DynamicModules::dynMods()->dynamicModule(_moduleName); //Why would we get this from DynamicModules?? + return _module; } -AnalysisEntry *RibbonButton::getAnalysis(const std::string &name) +AnalysisEntry *RibbonButton::getEntry(const std::string &name) { AnalysisEntry* analysis = nullptr; - analysis = _analysisMenuModel->getAnalysisEntry(name); + analysis = _menuModel->getAnalysisEntry(name); return analysis; } -std::vector RibbonButton::getAllAnalysisNames() const +std::vector RibbonButton::getAllEntries() const { std::vector allAnalyses; for (AnalysisEntry* menuEntry : _module->menu()) diff --git a/Desktop/modules/ribbonbutton.h b/Desktop/modules/ribbonbutton.h index d5278e7ed5..ccb13c8577 100644 --- a/Desktop/modules/ribbonbutton.h +++ b/Desktop/modules/ribbonbutton.h @@ -25,7 +25,7 @@ #include #include "modules/dynamicmodules.h" -#include "modules/analysismenumodel.h" +#include "modules/menumodel.h" #include "dirs.h" #include "log.h" @@ -40,21 +40,23 @@ class RibbonButton : public QObject Q_PROPERTY(bool requiresData READ requiresData WRITE setRequiresData NOTIFY requiresDataChanged ) Q_PROPERTY(bool isCommon READ isCommon WRITE setIsCommon NOTIFY isCommonChanged ) Q_PROPERTY(QString title READ titleQ WRITE setTitleQ NOTIFY titleChanged ) - Q_PROPERTY(QString moduleName READ moduleNameQ NOTIFY moduleNameChanged ) - Q_PROPERTY(QString moduleTitle READ titleQ NOTIFY titleChanged ) + Q_PROPERTY(QString name READ nameQ NOTIFY moduleNameChanged ) + Q_PROPERTY(QString title READ titleQ NOTIFY titleChanged ) Q_PROPERTY(QString iconSource READ iconSource WRITE setIconSource NOTIFY iconSourceChanged ) - Q_PROPERTY(QVariant analysisMenu READ analysisMenu NOTIFY analysisMenuChanged ) + Q_PROPERTY(QVariant menu READ menu NOTIFY analysisMenuChanged ) Q_PROPERTY(bool dataLoaded READ dataLoaded NOTIFY dataLoadedChanged ) Q_PROPERTY(bool active READ active NOTIFY activeChanged ) Q_PROPERTY(QString toolTip READ toolTip WRITE setToolTip NOTIFY toolTipChanged ) Q_PROPERTY(bool special READ isSpecial NOTIFY isSpecialChanged ) Q_PROPERTY(bool ready READ ready WRITE setReady NOTIFY readyChanged ) Q_PROPERTY(bool error READ error WRITE setError NOTIFY errorChanged ) + Q_PROPERTY(bool remember READ remember WRITE setRemember NOTIFY rememberChanged ) public: RibbonButton(QObject *parent, Modules::DynamicModule * module); - RibbonButton(QObject *parent, std::string name, std::string title, std::string icon, bool requiresData, std::function justThisFunction, std::string toolTip = ""); + RibbonButton(QObject *parent, std::string name, std::string title, std::string icon, bool requiresData, std::function justThisFunction, std::string toolTip = "", bool enabled = true, bool remember = false); + RibbonButton(QObject *parent, std::string name, std::string title, std::string icon, Modules::AnalysisEntries * funcEntries, bool enabled = true, bool remember = false); ~RibbonButton() {} @@ -64,12 +66,12 @@ class RibbonButton : public QObject QString titleQ() const { return QString::fromStdString(_title); } QString iconSource() const { return _iconSource; } bool enabled() const { return _enabled; } - std::string moduleName() const { return _moduleName; } - QString moduleNameQ() const { return QString::fromStdString(_moduleName); } + std::string name() const { return _name; } + QString nameQ() const { return QString::fromStdString(_name); } Modules::DynamicModule* dynamicModule(); - Modules::AnalysisEntry* getAnalysis(const std::string& name); - QVariant analysisMenu() const { return QVariant::fromValue(_analysisMenuModel); } - std::vector getAllAnalysisNames() const; + Modules::AnalysisEntry* getEntry(const std::string& name); + QVariant menu() const { return QVariant::fromValue(_menuModel); } + std::vector getAllEntries() const; bool dataLoaded() const { return Modules::DynamicModules::dynMods() && Modules::DynamicModules::dynMods()->dataLoaded(); } bool active() const { return _enabled && (!requiresData() || dataLoaded()); } QString toolTip() const { return _toolTip; } @@ -77,11 +79,9 @@ class RibbonButton : public QObject QString version() const { return !_module ? "?" : _module->versionQ(); } bool ready() const { return _ready; } bool error() const { return _error; } + bool remember() const { return _remember; } - static QString getJsonDescriptionFilename(); - - - + static QString getJsonDescriptionFilename(); public slots: void setDynamicModule(Modules::DynamicModule * module); @@ -97,14 +97,13 @@ public slots: void setToolTip(QString toolTip); void setReady(bool ready); void setError(bool error); + void setRemember(bool remember); - bool isSpecial() const { return _specialButtonFunc != nullptr ; } - void runSpecial() { _specialButtonFunc(); }; + bool isSpecial() const { return _special; } + void runSpecial(QString func);; void reloadDynamicModule(Modules::DynamicModule * dynMod); - - signals: void enabledChanged(); void requiresDataChanged(); @@ -121,20 +120,24 @@ public slots: void readyChanged(bool ready); void errorChanged(bool error); + void rememberChanged(bool remember); + private: void bindYourself(); private: - AnalysisMenuModel* _analysisMenuModel = nullptr; + MenuModel* _menuModel = nullptr; bool _requiresData = true, _isDynamicModule = true, _isCommonModule = false, _enabled = false, _ready = false, - _error = false; + _error = false, + _remember = true, + _special = false; std::string _title = "", - _moduleName = ""; + _name = ""; Modules::DynamicModule * _module = nullptr; QString _iconSource, _toolTip; diff --git a/Desktop/modules/ribbonmodel.cpp b/Desktop/modules/ribbonmodel.cpp index 42d9b0b3bc..d4d125ddcf 100644 --- a/Desktop/modules/ribbonmodel.cpp +++ b/Desktop/modules/ribbonmodel.cpp @@ -21,6 +21,7 @@ #include "utilities/appdirs.h" #include "utilities/messageforwarder.h" #include "log.h" +#include "data/datasetpackage.h" using namespace Modules; @@ -31,10 +32,12 @@ RibbonModel::RibbonModel() : QAbstractListModel(DynamicModules::dynMods()) if(_singleton) throw std::runtime_error("RibbonModel can only be instantiated once!"); _singleton = this; - connect(DynamicModules::dynMods(), &DynamicModules::dynamicModuleAdded, this, &RibbonModel::addDynamicRibbonButtonModel ); - connect(DynamicModules::dynMods(), &DynamicModules::dynamicModuleUninstalled, this, &RibbonModel::removeDynamicRibbonButtonModel ); - connect(DynamicModules::dynMods(), &DynamicModules::dynamicModuleReplaced, this, &RibbonModel::dynamicModuleReplaced ); - connect(DynamicModules::dynMods(), &DynamicModules::dynamicModuleChanged, this, &RibbonModel::dynamicModuleChanged ); + _buttonNames = std::vector(2); //Analyses & Data + + connect(DynamicModules::dynMods(), &DynamicModules::dynamicModuleAdded, this, &RibbonModel::addRibbonButtonModelFromDynamicModule ); + connect(DynamicModules::dynMods(), &DynamicModules::dynamicModuleUninstalled, this, &RibbonModel::removeDynamicRibbonButtonModel ); + connect(DynamicModules::dynMods(), &DynamicModules::dynamicModuleReplaced, this, &RibbonModel::dynamicModuleReplaced ); + connect(DynamicModules::dynMods(), &DynamicModules::dynamicModuleChanged, this, &RibbonModel::dynamicModuleChanged ); } void RibbonModel::loadModules(std::vector commonModulesToLoad, std::vector extraModulesToLoad) @@ -42,6 +45,8 @@ void RibbonModel::loadModules(std::vector commonModulesToLoad, std: DynamicModules::dynMods()->initializeInstalledModules(); DynamicModules::dynMods()->insertCommonModuleNames(std::set(commonModulesToLoad.begin(), commonModulesToLoad.end())); + addSpecialRibbonButtonsEarly(); + auto loadModulesFromBundledOrUserData = [&](bool common) { for(const std::string & moduleName : (common ? commonModulesToLoad : extraModulesToLoad)) @@ -80,7 +85,7 @@ void RibbonModel::loadModules(std::vector commonModulesToLoad, std: if(!isModuleName(modName)) //Was it already added from commonModulesToLoad or extraModulesToLoad? addRibbonButtonModelFromDynamicModule((*DynamicModules::dynMods())[modName]); - addRibbonButtonRPrompt(); + addSpecialRibbonButtonsLate(); if(PreferencesModel::prefs()->modulesRemember()) { @@ -90,7 +95,7 @@ void RibbonModel::loadModules(std::vector commonModulesToLoad, std: { std::string mod = enabledModule.toStdString(); - if(_buttonModelsByName.count(mod) > 0) + if(_buttonModelsByName.count(mod) > 0 && _buttonModelsByName[mod]->remember()) _buttonModelsByName[mod]->setEnabled(true); } } @@ -98,12 +103,44 @@ void RibbonModel::loadModules(std::vector commonModulesToLoad, std: void RibbonModel::addRibbonButtonModelFromDynamicModule(Modules::DynamicModule * module) { - RibbonButton * button = new RibbonButton(this, module); + addRibbonButtonModel(new RibbonButton(this, module), size_t(RowType::Analyses)); +} - addRibbonButtonModel(button); +void RibbonModel::addSpecialRibbonButtonsEarly() +{ + auto * switchData = new RibbonButton(this, "Data", fq(tr("Edit Data")), "data-button.svg", false, [&](){ emit showData(); }, fq(tr("Switch JASP to data editing mode")), false); + addRibbonButtonModel( new RibbonButton(this, "Analyses", fq(tr("Analyses")), "JASP_logo_green.svg", false, [&](){ emit finishCurrentEdit(); emit showStatistics(); }, fq(tr("Switch JASP to analyses mode")), true), size_t(RowType::Data) ); + + + auto * newData = new RibbonButton(this, "Data-New", fq(tr("New Data")), "data-button-new.svg", false, [&](){ emit genShowEmptyData(); emit resizeData(); }, fq(tr("Open a workspace without data")), true); + //auto * insertData = new RibbonButton(this, "Data-Insert", fq(tr("Insert")), "data-button-insert.svg", insertDataEntries, fq(tr("")), false); + //auto * eraseData = new RibbonButton(this, "Data-Erase", fq(tr("Erase")), "data-button-erase.svg", eraseDataEntries, fq(tr("")), false); + auto * resizeDataB = new RibbonButton(this, "Data-Resize", fq(tr("Resize Data")), "data-button-resize.svg", false, [&](){ emit resizeData(); }, fq(tr("Resize your dataset")), false); + //auto * externalEdit = new RibbonButton(this, "Data-External", fq(tr("External Editor")), "data-button-external.svg", false, [&](){ DataSetPackage::pkg()->setSynchingExternally(true); }, fq(tr("Open data in external editor")), false); + //auto * internalEdit = new RibbonButton(this, "Data-Internal", fq(tr("Editing in JASP")), "data-button-internal.svg", false, [&](){ DataSetPackage::pkg()->setSynchingExternally(false); }, fq(tr("Stop synchronizing with external data")), false); + + connect(this, &RibbonModel::dataLoadedChanged, switchData, &RibbonButton::setEnabled); + connect(this, &RibbonModel::dataLoadedChanged, newData, [=](bool loaded){ newData->setEnabled( !loaded); }); + //connect(this, &RibbonModel::dataLoadedChanged, insertData, &RibbonButton::setEnabled); + //connect(this, &RibbonModel::dataLoadedChanged, eraseData, &RibbonButton::setEnabled); + connect(this, &RibbonModel::dataLoadedChanged, resizeDataB, &RibbonButton::setEnabled); + + //connect(this, &RibbonModel::dataLoadedChanged, externalEdit, [=](bool loaded) { externalEdit->setEnabled(!DataSetPackage::pkg()->synchingExternally()); }); + //connect(this, &RibbonModel::dataLoadedChanged, internalEdit, [=](bool loaded) { internalEdit->setEnabled(DataSetPackage::pkg()->synchingExternally()); }); + + //connect(DataSetPackage::pkg(), &DataSetPackage::synchingExternallyChanged, externalEdit, [=]() { externalEdit->setEnabled(!DataSetPackage::pkg()->synchingExternally()); }); + //connect(DataSetPackage::pkg(), &DataSetPackage::synchingExternallyChanged, internalEdit, [=]() { internalEdit->setEnabled( DataSetPackage::pkg()->synchingExternally()); }); + + addRibbonButtonModel(switchData, size_t(RowType::Analyses)); + addRibbonButtonModel(newData, size_t(RowType::Analyses)); + //addRibbonButtonModel(insertData, size_t(RowType::Data)); + //addRibbonButtonModel(eraseData, size_t(RowType::Data)); + addRibbonButtonModel(resizeDataB, size_t(RowType::Data)); + //addRibbonButtonModel(externalEdit, size_t(RowType::Data)); + //addRibbonButtonModel(internalEdit, size_t(RowType::Data)); } -void RibbonModel::addRibbonButtonRPrompt() +void RibbonModel::addSpecialRibbonButtonsLate() { addRibbonButtonModel(new RibbonButton(this, "R", fq(tr("R console")), "Rlogo.svg", false, [&](){ emit showRCommander(); }, fq(tr("Execute R code in a console")))); } @@ -117,17 +154,19 @@ void RibbonModel::dynamicModuleChanged(Modules::DynamicModule * dynMod) nameButton.second->reloadDynamicModule(dynMod); } -void RibbonModel::addRibbonButtonModel(RibbonButton* model) +void RibbonModel::addRibbonButtonModel(RibbonButton* model, size_t row) { - if(isModuleName(model->moduleName())) - removeRibbonButtonModel(model->moduleName()); + if(isModuleName(model->name())) + removeRibbonButtonModel(model->name()); - beginInsertRows(QModelIndex(), rowCount(), rowCount()); + if(_currentRow == row) + emit beginInsertRows(QModelIndex(), rowCount(), rowCount()); - _moduleNames.push_back(model->moduleName()); - _buttonModelsByName[model->moduleName()] = model; + _buttonNames[row].push_back(model->name()); + _buttonModelsByName[model->name()] = model; - endInsertRows(); + if(_currentRow == row) + emit endInsertRows(); connect(model, &RibbonButton::iChanged, this, &RibbonModel::ribbonButtonModelChanged); } @@ -154,7 +193,7 @@ QVariant RibbonModel::data(const QModelIndex &index, int role) const case EnabledRole: return ribbonButtonModelAt(row)->enabled(); case ActiveRole: return ribbonButtonModelAt(row)->active(); case CommonRole: return ribbonButtonModelAt(row)->isCommon(); - case ModuleNameRole: return ribbonButtonModelAt(row)->moduleNameQ(); + case ModuleNameRole: return ribbonButtonModelAt(row)->nameQ(); case ModuleRole: return QVariant::fromValue(ribbonButtonModelAt(row)->dynamicModule()); case BundledRole: return ribbonButtonModelAt(row)->isBundled(); case VersionRole: return ribbonButtonModelAt(row)->version(); @@ -198,33 +237,55 @@ void RibbonModel::removeRibbonButtonModel(std::string moduleName) if(!isModuleName(moduleName)) return; - int indexRemoved = -1; + for(size_t row=0; row= 0; i--) - if(_moduleNames[i] == moduleName) - { - indexRemoved = i; - break; - } + for(int i=_buttonNames.size() - 1; i >= 0; i--) + if(_buttonNames[row][i] == moduleName) + { + indexRemoved = i; + break; + } - emit beginRemoveRows(QModelIndex(), indexRemoved, indexRemoved); + if(indexRemoved != -1) + { + if(row == _currentRow) + beginRemoveRows(QModelIndex(), indexRemoved, indexRemoved); - delete _buttonModelsByName[moduleName]; - _buttonModelsByName.erase(moduleName); + delete _buttonModelsByName[moduleName]; + _buttonModelsByName.erase(moduleName); - _moduleNames.erase(_moduleNames.begin() + indexRemoved); + _buttonNames[row].erase(_buttonNames[row].begin() + indexRemoved); - emit endRemoveRows(); + if(row == _currentRow) + endRemoveRows(); + } + } } void RibbonModel::analysisClicked(QString analysisFunction, QString analysisQML, QString analysisTitle, QString module) { RibbonButton * button = ribbonButtonModel(fq(module)); - if(button->isSpecial()) button->runSpecial(); + if(button->isSpecial()) button->runSpecial(analysisFunction); else emit analysisClickedSignal(analysisFunction, analysisQML, analysisTitle, module); } +void RibbonModel::setCurrentRow(int currentRow) +{ + size_t cur = currentRow; + if (_currentRow == cur) + return; + + beginResetModel(); + _currentRow = cur; + endResetModel(); + + emit currentRowChanged(); + emit dataModeChanged(dataMode()); +} + void RibbonModel::setHighlightedModuleIndex(int highlightedModuleIndex) { if (_highlightedModuleIndex == highlightedModuleIndex) @@ -253,9 +314,9 @@ QStringList RibbonModel::getModulesEnabled() const { QStringList list; - for(auto nameButton : _buttonModelsByName) + for(auto & nameButton : _buttonModelsByName) if(nameButton.second->enabled()) - list.append(nameButton.second->moduleNameQ()); + list.append(nameButton.second->nameQ()); return list; } @@ -269,7 +330,7 @@ Modules::AnalysisEntry *RibbonModel::getAnalysis(std::string moduleName, const s RibbonButton * ribbonButton = ribbonButtonModel(moduleName); if (ribbonButton) - return ribbonButton->getAnalysis(analysisName); + return ribbonButton->getEntry(analysisName); else { std::string strippedModuleName = stringUtils::stripNonAlphaNum(moduleName); @@ -286,8 +347,8 @@ Modules::AnalysisEntry *RibbonModel::getAnalysis(std::string moduleName, const s std::string RibbonModel::getModuleNameFromAnalysisName(const std::string & analysisName) { // This function is needed for old JASP file: they still have a reference to the common module that does not exist anymore. - for (const std::string & moduleName : _moduleNames) - for (const std::string & name : _buttonModelsByName[moduleName]->getAllAnalysisNames()) + for (const std::string & moduleName : _buttonNames[size_t(RowType::Analyses)]) + for (const std::string & name : _buttonModelsByName[moduleName]->getAllEntries()) if (name == analysisName) return moduleName; @@ -310,8 +371,8 @@ int RibbonModel::ribbonButtonModelIndex(RibbonButton * model) const { for(auto & keyval : _buttonModelsByName) if(keyval.second == model) - for(size_t i=0; i<_moduleNames.size(); i++) - if(_moduleNames[i] == keyval.first) + for(size_t i=0; i<_buttonNames[_currentRow].size(); i++) + if(_buttonNames[_currentRow][i] == keyval.first) return int(i); return -1; } @@ -320,7 +381,8 @@ int RibbonModel::ribbonButtonModelIndex(RibbonButton * model) const void RibbonModel::ribbonButtonModelChanged(RibbonButton* model) { int row = ribbonButtonModelIndex(model); - emit dataChanged(index(row), index(row)); + if(row > -1) + emit dataChanged(index(row), index(row)); } /*void RibbonModel::moduleLoadingSucceeded(const QString & moduleName) diff --git a/Desktop/modules/ribbonmodel.h b/Desktop/modules/ribbonmodel.h index cb68898816..6b011bc4e1 100644 --- a/Desktop/modules/ribbonmodel.h +++ b/Desktop/modules/ribbonmodel.h @@ -33,7 +33,9 @@ class RibbonModel : public QAbstractListModel { Q_OBJECT - Q_PROPERTY(int highlightedModuleIndex READ highlightedModuleIndex WRITE setHighlightedModuleIndex NOTIFY highlightedModuleIndexChanged) + Q_PROPERTY(int highlightedModuleIndex READ highlightedModuleIndex WRITE setHighlightedModuleIndex NOTIFY highlightedModuleIndexChanged) + Q_PROPERTY(int currentRow READ currentRow WRITE setCurrentRow NOTIFY currentRowChanged ) + Q_PROPERTY(bool dataMode READ dataMode NOTIFY dataModeChanged ) public: enum { @@ -51,27 +53,32 @@ class RibbonModel : public QAbstractListModel SpecialRole }; + enum class RowType { Analyses = 0, Data}; + RibbonModel(); ~RibbonModel() { _singleton = nullptr; } static RibbonModel * singleton() { return _singleton; } - int rowCount(const QModelIndex & = QModelIndex()) const override { return int(_moduleNames.size()); } + int rowCount(const QModelIndex & = QModelIndex()) const override { return int(_buttonNames[_currentRow].size()); } QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; virtual QHash roleNames() const override; void loadModules(std::vector commonModulesToLoad = {}, std::vector extraModulesToLoad = {}); - void addRibbonButtonModelFromDynamicModule(Modules::DynamicModule * module); - void addRibbonButtonRPrompt(); + void addSpecialRibbonButtonsEarly(); + void addSpecialRibbonButtonsLate(); + Q_INVOKABLE void showData() { setCurrentRow(int(RowType::Data)); } + Q_INVOKABLE void showStatistics() { setCurrentRow(int(RowType::Analyses)); } + void genShowEmptyData() { generateEmptyData(); showData(); } void removeRibbonButtonModel(std::string moduleName); bool isModuleName(std::string name) const { return _buttonModelsByName.count(name) > 0; } - QString moduleName(size_t index) const { return QString::fromStdString(_moduleNames[index]);} - RibbonButton* ribbonButtonModelAt(size_t index) const { return ribbonButtonModel(_moduleNames[index]); } + QString moduleName(size_t index) const { return QString::fromStdString(_buttonNames[_currentRow][index]);} + RibbonButton* ribbonButtonModelAt(size_t index) const { return ribbonButtonModel( _buttonNames[_currentRow][index]); } RibbonButton* ribbonButtonModel(std::string moduleName) const; int ribbonButtonModelIndex(RibbonButton * model) const; @@ -79,38 +86,54 @@ class RibbonModel : public QAbstractListModel Q_INVOKABLE void setModuleEnabled(int ribbonButtonModelIndex, bool enabled); QStringList getModulesEnabled() const; - int highlightedModuleIndex() const { return _highlightedModuleIndex; } - Modules::AnalysisEntry* getAnalysis(std::string moduleName, const std::string & analysisName); + int highlightedModuleIndex() const { return _highlightedModuleIndex; } + int currentRow() const { return _currentRow; } + bool dataMode() const { return _currentRow == size_t(RowType::Data); } + Modules::AnalysisEntry* getAnalysis(std::string moduleName, const std::string & analysisName); std::string getModuleNameFromAnalysisName(const std::string & analysisName); + + + signals: void currentButtonModelChanged(); void analysisClickedSignal(QString analysisFunction, QString analysisQML, QString analysisTitle, QString module); void highlightedModuleIndexChanged(int highlightedModuleIndex); void showRCommander(); void invalidateFilterModel(); + void currentRowChanged(); + void dataLoadedChanged(bool loaded); + void generateEmptyData(); + void dataModeChanged(bool dataMode); + void startExternalEdit(); + void stopExternalEdit(); + void resizeData(); + void finishCurrentEdit(); + public slots: - void addDynamicRibbonButtonModel(Modules::DynamicModule * module) { addRibbonButtonModelFromDynamicModule(module); } + void addRibbonButtonModelFromDynamicModule(Modules::DynamicModule * module); void removeDynamicRibbonButtonModel(QString moduleName) { removeRibbonButtonModel(moduleName.toStdString()); } void setHighlightedModuleIndex(int highlightedModuleIndex); //void moduleLoadingSucceeded(const QString & moduleName); void analysisClicked(QString analysisFunction, QString analysisQML, QString analysisTitle, QString module); + void setCurrentRow(int currentRow); private slots: - void dynamicModuleChanged(Modules::DynamicModule * module); + void dynamicModuleChanged( Modules::DynamicModule * module); + void dynamicModuleReplaced( Modules::DynamicModule * oldModule, Modules::DynamicModule * module); void ribbonButtonModelChanged(RibbonButton* model); - void dynamicModuleReplaced(Modules::DynamicModule * oldModule, Modules::DynamicModule * module); private: // functions - void addRibbonButtonModel(RibbonButton* model); + void addRibbonButtonModel(RibbonButton* model, size_t row); private: // fields std::map _buttonModelsByName; - std::vector _moduleNames; + std::vector _buttonNames; //Can be multiple rows, originally [ { Analyses }, { Data Mode } ] int _highlightedModuleIndex = -1; std::vector _commonModulesToLoad; + size_t _currentRow = size_t(RowType::Analyses); static RibbonModel * _singleton; }; diff --git a/Desktop/modules/ribbonmodeluncommon.cpp b/Desktop/modules/ribbonmodeluncommon.cpp index 64afd0d2cb..b43f82c21e 100644 --- a/Desktop/modules/ribbonmodeluncommon.cpp +++ b/Desktop/modules/ribbonmodeluncommon.cpp @@ -18,8 +18,13 @@ void RibbonModelUncommon::setRibbonModel(RibbonModel * ribbonModel) bool RibbonModelUncommon::filterAcceptsRow(int source_row, const QModelIndex &) const { if(source_row < 0) return false; + + if(!_ribbonModel) + return false; + + auto * button = _ribbonModel->ribbonButtonModelAt(size_t(source_row)); - return _ribbonModel != nullptr && (!_ribbonModel->ribbonButtonModelAt(size_t(source_row))->isCommon() || !_ribbonModel->ribbonButtonModelAt(size_t(source_row))->isBundled()); + return button->remember() && (!button->isCommon() || !button->isBundled()); } void RibbonModelUncommon::setModuleEnabled(int filteredRow, bool checked) diff --git a/Desktop/qquick/datasetview.cpp b/Desktop/qquick/datasetview.cpp index cffcc3dada..cec8ea62a9 100644 --- a/Desktop/qquick/datasetview.cpp +++ b/Desktop/qquick/datasetview.cpp @@ -11,37 +11,45 @@ #include #include "data/datasetpackage.h" #include +#include +#include +#include "utils.h" +#include "utilities/languagemodel.h" +#include "data/datasettablemodel.h" DataSetView * DataSetView::_lastInstancedDataSetView = nullptr; -DataSetView::DataSetView(QQuickItem *parent) : QQuickItem (parent) +DataSetView::DataSetView(QQuickItem *parent) : QQuickItem (parent), _selectionModel(new QItemSelectionModel(nullptr, this)) { - setFlag(QQuickItem::ItemHasContents, true); - setFlag(ItemIsFocusScope); + setFlag(QQuickItem::ItemHasContents); + //setFlag(QQuickItem::ItemIsFocusScope); - material.setColor(Qt::gray); + _material.setColor(Qt::gray); - connect(this, &DataSetView::parentChanged, this, &DataSetView::myParentChanged); + connect(this, &DataSetView::parentChanged, this, &DataSetView::myParentChanged); - connect(this, &DataSetView::viewportXChanged, this, &DataSetView::viewportChanged); - connect(this, &DataSetView::viewportYChanged, this, &DataSetView::viewportChanged); - connect(this, &DataSetView::viewportWChanged, this, &DataSetView::viewportChanged); - connect(this, &DataSetView::viewportHChanged, this, &DataSetView::viewportChanged); + connect(this, &DataSetView::viewportXChanged, this, &DataSetView::viewportChanged); + connect(this, &DataSetView::viewportYChanged, this, &DataSetView::viewportChanged); + connect(this, &DataSetView::viewportWChanged, this, &DataSetView::viewportChanged); + connect(this, &DataSetView::viewportHChanged, this, &DataSetView::viewportChanged); - connect(this, &DataSetView::itemDelegateChanged, this, &DataSetView::reloadTextItems); - connect(this, &DataSetView::rowNumberDelegateChanged, this, &DataSetView::reloadRowNumbers); - connect(this, &DataSetView::columnHeaderDelegateChanged, this, &DataSetView::reloadColumnHeaders); + connect(this, &DataSetView::itemDelegateChanged, this, &DataSetView::reloadTextItems); + connect(this, &DataSetView::rowNumberDelegateChanged, this, &DataSetView::reloadRowNumbers); + connect(this, &DataSetView::columnHeaderDelegateChanged, this, &DataSetView::reloadColumnHeaders); - connect(this, &DataSetView::itemHorizontalPaddingChanged, this, &DataSetView::calculateCellSizes); - connect(this, &DataSetView::itemVerticalPaddingChanged, this, &DataSetView::calculateCellSizes); - connect(this, &DataSetView::extraColumnItemChanged, this, &DataSetView::calculateCellSizes); + connect(this, &DataSetView::itemHorizontalPaddingChanged, this, &DataSetView::calculateCellSizes); + connect(this, &DataSetView::itemVerticalPaddingChanged, this, &DataSetView::calculateCellSizes); + connect(this, &DataSetView::extraColumnItemChanged, this, &DataSetView::calculateCellSizes); - connect(this, &DataSetView::itemSizeChanged, this, &DataSetView::reloadTextItems); - connect(this, &DataSetView::itemSizeChanged, this, &DataSetView::reloadRowNumbers); - connect(this, &DataSetView::itemSizeChanged, this, &DataSetView::reloadColumnHeaders); + connect(this, &DataSetView::itemSizeChanged, this, &DataSetView::reloadTextItems); + connect(this, &DataSetView::itemSizeChanged, this, &DataSetView::reloadRowNumbers); + connect(this, &DataSetView::itemSizeChanged, this, &DataSetView::reloadColumnHeaders); + + connect(PreferencesModel::prefs(), &PreferencesModel::uiScaleChanged, this, &DataSetView::resetItems, Qt::QueuedConnection); + connect(PreferencesModel::prefs(), &PreferencesModel::interfaceFontChanged, this, &DataSetView::resetItems, Qt::QueuedConnection); + + connect(DataSetPackage::pkg(), &DataSetPackage::dataModeChanged, this, &DataSetView::onDataModeChanged); - connect(PreferencesModel::prefs(), &PreferencesModel::uiScaleChanged, this, &DataSetView::resetItems, Qt::QueuedConnection); - connect(PreferencesModel::prefs(), &PreferencesModel::interfaceFontChanged, this, &DataSetView::resetItems, Qt::QueuedConnection); setZ(10); @@ -59,6 +67,9 @@ void DataSetView::setModel(QAbstractItemModel * model) connect(_model, &QAbstractItemModel::modelAboutToBeReset, this, &DataSetView::modelAboutToBeReset ); connect(_model, &QAbstractItemModel::modelReset, this, &DataSetView::modelWasReset ); + _selectionModel->setModel(_model); + emit selectionModelChanged(); //Or maybe it hasn't? + setRolenames(); setRowNumberWidth(getRowHeaderSize().width()); @@ -157,12 +168,16 @@ void DataSetView::modelHeaderDataChanged(Qt::Orientation, int, int) void DataSetView::modelAboutToBeReset() { + //Ok, this weird hack is because if I do not recreate the selectionmodel after resetting everything crashes real hard. Maybe there is a bug in Qt? + delete _selectionModel; + _selectionModel = nullptr; _storedLineFlags.clear(); _storedDisplayText.clear(); } void DataSetView::modelWasReset() { + _selectionModel = new QItemSelectionModel(_model, this); setRolenames(); calculateCellSizes(); } @@ -425,8 +440,9 @@ void DataSetView::buildNewLinesAndCreateNewItems() up = (lineFlags & 4) > 0 && pos0y > _dataRowsMaxHeight + _viewportY, down = (lineFlags & 8) > 0 && pos1y > _dataRowsMaxHeight + _viewportY; -#ifdef DATASETVIEW_SHOW_ITEMS_PLEASE - createTextItem(row, col); +#ifdef SHOW_ITEMS_PLEASE + if(!(editing() && row == _prevEditRow && col == _prevEditCol)) + createTextItem(row, col); #endif @@ -513,6 +529,9 @@ void DataSetView::buildNewLinesAndCreateNewItems() createleftTopCorner(); updateExtraColumnItem(); + if(editing()) + positionEditItem(_prevEditRow, _prevEditCol); + JASPTIMER_STOP(buildNewLinesAndCreateNewItems); } @@ -531,6 +550,8 @@ QQuickItem * DataSetView::createTextItem(int row, int col) { _itemDelegate = new QQmlComponent(qmlEngine(this)); _itemDelegate->setData("import QtQuick 2.9\nText { text: itemText; color: itemActive ? 'black' : 'grey'; verticalAlignment: Text.AlignVCenter; }", QUrl()); + + emit itemDelegateChanged(); } QQuickItem * textItem = nullptr; @@ -592,8 +613,8 @@ void DataSetView::setTextItemInfo(int row, int col, QQuickItem * textItem) textItem->setHeight(_dataRowsMaxHeight - (2 * _itemVerticalPadding)); textItem->setWidth(_dataColsMaxWidth[col] - (2 * _itemHorizontalPadding)); - textItem->setX(_colXPositions[col] + _itemHorizontalPadding ); - textItem->setY(((row + 1) * _dataRowsMaxHeight) + _itemVerticalPadding ); + textItem->setX(_colXPositions[col] + _itemHorizontalPadding); + textItem->setY(((row + 1) * _dataRowsMaxHeight) + _itemVerticalPadding); textItem->setZ(-4); textItem->setVisible(true); @@ -644,6 +665,8 @@ QQuickItem * DataSetView::createRowNumber(int row) "Rectangle { color: jaspTheme.uiBackground; anchors.fill: parent }\n" "Text { text: rowIndex; anchors.centerIn: parent; color: jaspTheme.textEnabled; }\n" "}", QUrl()); + + emit rowNumberDelegateChanged(); } QQuickItem * rowNumber = nullptr; @@ -716,7 +739,8 @@ void DataSetView::storeRowNumber(int row) rowNumber->item->setVisible(false); - _rowNumberStorage.push(rowNumber); + if (_cacheItems) _rowNumberStorage.push(rowNumber); + else delete rowNumber; } @@ -731,6 +755,8 @@ QQuickItem * DataSetView::createColumnHeader(int col) "Rectangle { color: jaspTheme.uiBackground; anchors.fill: parent }\n" "Text { text: headerText; anchors.centerIn: parent; color: jaspTheme.textEnabled; }\n" "}", QUrl()); + + emit columnHeaderDelegateChanged(); } @@ -814,7 +840,8 @@ void DataSetView::storeColumnHeader(int col) columnHeader->item->setVisible(false); - _columnHeaderStorage.push(columnHeader); + if (_cacheItems) _columnHeaderStorage.push(columnHeader); + else delete columnHeader; } QQuickItem * DataSetView::createleftTopCorner() @@ -863,15 +890,442 @@ void DataSetView::updateExtraColumnItem() connect(_extraColumnItem, &QQuickItem::widthChanged, this, &DataSetView::setExtraColumnX, Qt::UniqueConnection); } +void DataSetView::destroyEditItem() +{ + Log::log() << "Destroying old edit item (row=" << _prevEditRow << ", col=" << _prevEditCol << ") and context" << std::endl; + + if(!_editItemContextual || _prevEditRow == -1 || _prevEditCol == -1) + { + Log::log() << "Its already gone" << std::endl; + return; + } + + _editItemContextual->item ->setVisible(false); + _editItemContextual->item ->deleteLater(); + _editItemContextual->item = nullptr; + _editItemContextual->context ->deleteLater(); + _editItemContextual->context = nullptr; + _editItemContextual = nullptr; + + Log::log() << "Restoring text item for old edit item" << std::endl; + createTextItem(_prevEditRow, _prevEditCol)->forceActiveFocus(); + + _prevEditRow = -1; + _prevEditCol = -1; +} + +void DataSetView::positionEditItem(int row, int col) +{ + if(!_editDelegate) + { + _editDelegate = new QQmlComponent(qmlEngine(this)); + + _editDelegate->setData( +"import QtQuick 2.9" "\n" +"TextInput { text: itemText; color: itemActive ? 'black' : 'grey'; verticalAlignment: Text.AlignVCenter; \n" +" onEditingFinished: dataview.editFinished(index, text); " "\n" +"}", QUrl()); + + emit editDelegateChanged(_editDelegate); + } + + QModelIndex ind ( _model->index(row, col)); + bool active = _model->data(ind, _roleNameToRole["filter"]).toBool(); + + if(_editItemContextual && !(_prevEditRow == row && _prevEditCol == col)) //remove previous edit item to avoid old values or broken bindings messing everything up. But only if it is in a different place than where we're at + destroyEditItem(); + + + if(!_editItemContextual) + { + _editItemContextual = new ItemContextualized(setStyleDataItem(nullptr, active, col, row)); + + //forceActiveFocus(); + + Log::log() << "Destroying old text item (row=" << row << ", col=" << col << ") and creating edit item + context" << std::endl; + storeTextItem(row, col, true); + _prevEditRow = row; //Store info to recreate it later + _prevEditCol = col; + + QQmlIncubator localIncubator(QQmlIncubator::Synchronous); + _editDelegate->create(localIncubator, _editItemContextual->context); + + if(localIncubator.isError()) + throw std::runtime_error("Something went wrong incubating an edit item delegate for tableview!"); + + _editItemContextual->item = qobject_cast(localIncubator.object()); + _editItemContextual->item->setParent(this); + _editItemContextual->item->setParentItem(this); + } + else + { + //Log::log() << "repositioning current edit item (row=" << row << ", col=" << col << ")" << std::endl; + setStyleDataItem(_editItemContextual->context, active, col, row); + } + + setTextItemInfo(row, col, _editItemContextual->item); //Will set it visible + //_editItemContextual->item->setFocus(true); +} + void DataSetView::setExtraColumnX() { _extraColumnItem->setX(_viewportX + _viewportW - extraColumnWidth()); } +void DataSetView::setSelectionStart(QModelIndex selectionStart) +{ + if (_selectionStart == selectionStart) + return; + + Log::log() << "DataSetView::setSelectionStart( row=" << selectionStart.row() << ", col=" << selectionStart.column() << " )" << std::endl; + + _selectionStart = _model->index(selectionStart.row(), selectionStart.column());; + emit selectionStartChanged(_selectionStart); + + if(!_selectionStart.isValid()) + { + //_selectionModel->clear(); + return; + } + + _selectionModel->select(_selectionStart, QItemSelectionModel::SelectCurrent); + + edit(_selectionStart); +} + +void DataSetView::setSelectionEnd(QModelIndex selectionEnd) +{ + if (_selectionEnd == selectionEnd) + return; + + if(_selectionStart.column() == -1 || _selectionStart.row() == -1) + return; + + Log::log() << "DataSetView::setSelectionEnd( row=" << selectionEnd.row() << ", col=" << selectionEnd.column() << " )" << std::endl; + + _selectionEnd = _model->index(selectionEnd.row(), selectionEnd.column());; + emit selectionEndChanged(_selectionEnd); + + _selectionModel->select(QItemSelection(_model->index(_selectionStart.row(), _selectionStart.column()), _selectionEnd), QItemSelectionModel::ClearAndSelect); + + _selectScrollMs = Utils::currentMillis(); +} + +void DataSetView::selectAll() +{ + setSelectionStart(_model->index(0, 0)); + setSelectionEnd(_model->index(_model->rowCount() - 1, _model->columnCount() - 1)); +} + + +bool DataSetView::relaxForSelectScroll() +{ + long curMs = Utils::currentMillis(); + + //Log::log() << "_selectScrollMs = " << _selectScrollMs << ", curMs = " << curMs << std::endl; + + if(curMs < _selectScrollMs) + return false; + + _selectScrollMs = curMs + 200; + return true; +} + + +void DataSetView::pollSelectScroll(QModelIndex mouseIndex) +{ + if(!relaxForSelectScroll()) + return; + + + const int b = 3; + + //Log::log() << "DataSetView::pollSelectScroll row=" << mouseIndex.row() << "col=" << mouseIndex.column() << std::endl; + + + int minMouseCol = mouseIndex.column() - b, + maxMouseCol = mouseIndex.column() + b, + minMouseRow = mouseIndex.row() - b, + maxMouseRow = mouseIndex.row() + b; + + bool maybeBudgeLeft = minMouseCol <= _currentViewportColMin + _viewportMargin, + maybeBudgeRight = maxMouseCol > _currentViewportColMax - _viewportMargin, + maybeBudgeUp = minMouseRow < _currentViewportRowMin + _viewportMargin, + maybeBudgeDown = maxMouseRow >= _currentViewportRowMax - _viewportMargin; + + maybeBudgeLeft = maybeBudgeLeft && (_currentViewportColMin > 0 || 0 < mouseIndex.column ()); + maybeBudgeUp = maybeBudgeUp && (_currentViewportRowMin > 0 || 0 < mouseIndex.row ()); + maybeBudgeRight = maybeBudgeRight && (_currentViewportColMax < _model->columnCount() || _model->columnCount() > mouseIndex.column ()); + maybeBudgeDown = maybeBudgeDown && (_currentViewportRowMax < _model->rowCount() || _model->rowCount() > mouseIndex.row ()); + + if(maybeBudgeLeft) emit selectionBudgesLeft (); + if(maybeBudgeUp) emit selectionBudgesUp (); + if(maybeBudgeRight) emit selectionBudgesRight (); + if(maybeBudgeDown) emit selectionBudgesDown (); +} + + + + +void DataSetView::_copy(bool includeHeader, bool clear) +{ + QModelIndexList selected = _selectionModel->selectedIndexes(); + + std::vector rows; + + int minCol = _model->columnCount(), maxCol = 0; + + + int previousRow = -1; + for(const QModelIndex & selectee : selected) //How do I know this is the right order? Random SO post. It does however seem to work and it would be surprising for it to suddenly change. + { + if(selectee.row() != previousRow) + rows.push_back({}); + + minCol = std::min(minCol, selectee.column()); + maxCol = std::max(maxCol, selectee.column()); + + QVariant valVar = _model->data(selectee); + std::string val = fq(valVar.toString()); + + rows[ rows.size()-1 ].append( + valVar.type() == QVariant::Double ? + QLocale::system().toString(valVar.toDouble()) : //To make sure commas etc are formatted as the system expects. + tq(val).replace('\n', ' ').replace('\t', ' ') ) ; //Because Excel/etc use tab/newline for cell/row we make sure they are not in the cells at least. + previousRow = selectee.row(); + } + + int rowsSelected = rows.size(); + + if(includeHeader) + { + rows.insert(rows.begin(), tq(std::vector(maxCol - minCol + 1, ""))); + for(int c=minCol; c<=maxCol; c++) + rows[0][c-minCol] = _model->headerData(c, Qt::Horizontal).toString(); + } + + if(rows.size() == 0) + return; //Nothing to copy + + QStringList all; + for(const auto & row : rows) + all.append(row.join("\t")); + QString copyThis = all.join("\n"); + + //Log::log() << "copying:\n" << copyThis << "\nThats it" << std::endl; + + QGuiApplication::clipboard()->setText(copyThis); + + if(clear && qobject_cast(_model) != nullptr) + { + QModelIndex topLeft = selectionTopLeft(); + + Log::log() << "DataSetView about to clear at row: " << topLeft.row() << " and col: " << topLeft.column() << std::endl; + qobject_cast(_model)->pasteSpreadsheet( + topLeft.row(), + topLeft.column(), + std::vector>( + (maxCol - minCol) + 1, + std::vector( + rowsSelected, + "" + ) + ) + ); + } +} + +void DataSetView::paste(bool includeHeader) +{ + QClipboard * clipboard = QGuiApplication::clipboard(); + + std::vector> newData; + + QStringList newNames; + + size_t row = 0, col = 0; + for(const QString & rowStr : clipboard->text().split("\n")) + { + col = 0; + if(rowStr != "") + { + if(includeHeader) + { + newNames = rowStr.split("\t"); + includeHeader = false; + } + else + { + for(const QString & cellStr : rowStr.split("\t")) + { + if(newData.size() <= col) newData. resize(col+1); + if(newData[col].size() <= row) newData[col].resize(row+1); + + newData[col][row] = cellStr; + col++; + } + row++; + } + } + } + + if(qobject_cast(_model) != nullptr) + { + QModelIndex topLeft = selectionTopLeft(); + + Log::log() << "DataSetView about to paste to data at row: " << topLeft.row() << " and col: " << topLeft.column() << std::endl; + + qobject_cast(_model)->pasteSpreadsheet(topLeft.row(), topLeft.column(), newData, newNames); + } +} + +QModelIndex DataSetView::selectionTopLeft() const +{ + int r = INT_MAX, + c = INT_MAX; + + for(const QModelIndex & i : _selectionModel->selectedIndexes()) + { + r = std::min(r, i.row()); + c = std::min(c, i.column()); + } + + if(r == INT_MAX) r = 0; + if(c == INT_MAX) c = 0; + + return _model->index(r, c); +} + + +void DataSetView::columnSelect(int col) +{ + setSelectionStart(_model->index(0, col)); + setSelectionEnd(_model->index(_model->rowCount() - 1, col)); +} + +void DataSetView::columnInsertBefore(int col) +{ + if(qobject_cast(_model) != nullptr) + qobject_cast(_model)->columnInsert(col); +} + +void DataSetView::columnInsertAfter(int col) +{ + columnInsertBefore(col + 1); +} + +void DataSetView::columnDelete(int col) +{ + if(qobject_cast(_model) != nullptr) + qobject_cast(_model)->columnDelete(col); +} + +void DataSetView::rowSelect(int row) +{ + setSelectionStart(_model->index(row, 0)); + setSelectionEnd(_model->index(row, _model->columnCount() - 1)); +} + +void DataSetView::rowInsertBefore(int row) +{ + if(qobject_cast(_model) != nullptr) + qobject_cast(_model)->rowInsert(row); +} + +void DataSetView::rowInsertAfter(int row) +{ + rowInsertBefore(row + 1); +} + +void DataSetView::rowDelete(int row) +{ + if(qobject_cast(_model) != nullptr) + qobject_cast(_model)->rowDelete(row); +} + +void DataSetView::setEditDelegate(QQmlComponent *editDelegate) +{ + if (_editDelegate == editDelegate) + return; + + _editDelegate = editDelegate; + emit editDelegateChanged(_editDelegate); +} + + + +void DataSetView::setEditing(bool editing) +{ + if (_editing == editing) + return; + + _editing = editing; + + //Log::log() << "DataSetView::setShiftSelectActive( " << (shiftSelectActive? "active!" : "inactive!") << " )" << std::endl; + + emit editingChanged(_editing); +} + +void DataSetView::edit(QModelIndex here) +{ + if(!here.isValid()) + return; + + Log::log() << "DataSetView::edit(row=" << here.row() << ", col=" << here.column() << ")" << std::endl; + + if(editing()) + destroyEditItem(); + + //Turn editing on + setEditing(true); + + positionEditItem(here.row(), here.column()); + + //when editItem is done or loses focus and the contents changed, this calls back to editFinished which will use setData etc + //this will also turn editing off again and replace editItem by normal item +} + +void DataSetView::editFinished(QModelIndex here, QVariant editedValue) +{ + if(!editing()) + { + Log::log() << "editFinished called while not editing..." << std::endl; + return; + } + + QVariant oldValue = _model->data(here); + + Log::log() << "editing finished! old value: '" << oldValue.toString() << "' and new value: '" << editedValue.toString() << "' (row=" << here.row() << ", col=" << here.column() << ")" << std::endl; + + setEditing(false); + + if(oldValue.toString() != editedValue.toString()) + _model->setData(here, editedValue); + + destroyEditItem(); + + _selectionStart = _model->index(_selectionStart.row(), _selectionStart.column()); //To stop setSelectionEnd from crashing everything +} + +void DataSetView::onDataModeChanged(bool dataMode) +{ + if(!dataMode && editing()) + { + destroyEditItem(); + setEditing(false); + } +} + +void DataSetView::contextMenuClickedAtIndex(QModelIndex index) +{ + if(!_selectionModel->isSelected(index)) + _selectionModel->select(index, QItemSelectionModel::SelectCurrent); +} + QQmlContext * DataSetView::setStyleDataItem(QQmlContext * previousContext, bool active, size_t col, size_t row) { QModelIndex idx = _model->index(row, col); - + bool isEditable(_model->flags(idx) & Qt::ItemIsEditable); if(isEditable || _storedDisplayText.count(row) == 0 || _storedDisplayText[row].count(col) == 0) @@ -891,8 +1345,10 @@ QQmlContext * DataSetView::setStyleDataItem(QQmlContext * previousContext, bool previousContext->setContextProperty("itemInputType", _model->data(idx, _roleNameToRole["itemInputType"])); previousContext->setContextProperty("columnIndex", static_cast(col)); previousContext->setContextProperty("rowIndex", static_cast(row)); + previousContext->setContextProperty("index", idx); previousContext->setContextProperty("isDynamic", true); previousContext->setContextProperty("tableView", _tableViewItem); + previousContext->setContextProperty("dataviewer", this); return previousContext; } @@ -1019,6 +1475,18 @@ void DataSetView::setExtraColumnItem(QQuickItem * newItem) } } +void DataSetView::setCacheItems(bool cacheItems) +{ + if(cacheItems == _cacheItems) + return; + + + _cacheItems = cacheItems; + emit cacheItemsChanged(); + + calculateCellSizesAndClear(true); +} + void DataSetView::reloadTextItems() { //Store all current items @@ -1103,7 +1571,7 @@ QSGNode * DataSetView::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) currentNode->setFlag(QSGNode::OwnsMaterial, false); currentNode->setFlag(QSGNode::OwnsGeometry, true); - currentNode->setMaterial(&material); + currentNode->setMaterial(&_material); justAdded = true; diff --git a/Desktop/qquick/datasetview.h b/Desktop/qquick/datasetview.h index 436f03aed8..63abf65292 100644 --- a/Desktop/qquick/datasetview.h +++ b/Desktop/qquick/datasetview.h @@ -13,6 +13,8 @@ #include "utilities/qutils.h" #include "gui/preferencesmodel.h" +#include +#include //#define DATASETVIEW_DEBUG_VIEWPORT //#define DATASETVIEW_DEBUG_CREATION @@ -48,115 +50,165 @@ class DataSetView : public QQuickItem Q_PROPERTY( QQmlComponent * columnHeaderDelegate READ columnHeaderDelegate WRITE setColumnHeaderDelegate NOTIFY columnHeaderDelegateChanged ) Q_PROPERTY( QQuickItem * leftTopCornerItem READ leftTopCornerItem WRITE setLeftTopCornerItem NOTIFY leftTopCornerItemChanged ) Q_PROPERTY( QQuickItem * extraColumnItem READ extraColumnItem WRITE setExtraColumnItem NOTIFY extraColumnItemChanged ) + Q_PROPERTY( QQmlComponent * editDelegate READ editDelegate WRITE setEditDelegate NOTIFY editDelegateChanged ) Q_PROPERTY( double headerHeight READ headerHeight NOTIFY headerHeightChanged ) Q_PROPERTY( double rowNumberWidth READ rowNumberWidth WRITE setRowNumberWidth NOTIFY rowNumberWidthChanged ) Q_PROPERTY( bool cacheItems READ cacheItems WRITE setCacheItems NOTIFY cacheItemsChanged ) Q_PROPERTY( QQuickItem * tableViewItem READ tableViewItem WRITE setTableViewItem ) - + Q_PROPERTY( QItemSelectionModel * selection READ selectionModel NOTIFY selectionModelChanged ) + Q_PROPERTY( QModelIndex selectionStart READ selectionStart WRITE setSelectionStart NOTIFY selectionStartChanged ) + Q_PROPERTY( QModelIndex selectionEnd READ selectionEnd WRITE setSelectionEnd NOTIFY selectionEndChanged ) + Q_PROPERTY( bool editing READ editing WRITE setEditing NOTIFY editingChanged ) + public: - DataSetView(QQuickItem *parent = nullptr); - - static DataSetView * lastInstancedDataSetView() { return _lastInstancedDataSetView; } - - void setModel(QAbstractItemModel * model); - QAbstractItemModel * model() { return _model; } - - int itemHorizontalPadding() { return _itemHorizontalPadding;} - int itemVerticalPadding() { return _itemVerticalPadding;} - - double viewportX() { return _viewportX; } - double viewportY() { return _viewportY; } - double viewportW() { return _viewportW; } - double viewportH() { return _viewportH; } - - QQmlComponent * itemDelegate() { return _itemDelegate; } - QQmlComponent * rowNumberDelegate() { return _rowNumberDelegate; } - QQmlComponent * columnHeaderDelegate() { return _columnHeaderDelegate; } - - QQuickItem * leftTopCornerItem() { return _leftTopItem; } - QQuickItem * extraColumnItem() { return _extraColumnItem; } - - QQuickItem * tableViewItem() { return _tableViewItem; } - - bool cacheItems() { return _cacheItems; } - - GENERIC_SET_FUNCTION(CacheItems, _cacheItems, cacheItemsChanged, bool) - - GENERIC_SET_FUNCTION(ViewportX, _viewportX, viewportXChanged, double) - GENERIC_SET_FUNCTION(ViewportY, _viewportY, viewportYChanged, double) - GENERIC_SET_FUNCTION(ViewportW, _viewportW, viewportWChanged, double) - GENERIC_SET_FUNCTION(ViewportH, _viewportH, viewportHChanged, double) - - void setItemHorizontalPadding( int newHorizontalPadding) { if(newHorizontalPadding != _itemHorizontalPadding) { _itemHorizontalPadding = newHorizontalPadding; emit itemHorizontalPaddingChanged(); update(); }} - void setItemVerticalPadding( int newVerticalPadding) { if(newVerticalPadding != _itemVerticalPadding) { _itemVerticalPadding = newVerticalPadding; emit itemVerticalPaddingChanged(); update(); }} - - void setRowNumberDelegate( QQmlComponent * newDelegate); - void setColumnHeaderDelegate( QQmlComponent * newDelegate); - void setItemDelegate( QQmlComponent * newDelegate); - - void setLeftTopCornerItem( QQuickItem * newItem); - void setExtraColumnItem( QQuickItem * newItem); - - void setTableViewItem( QQuickItem * tableViewItem) { _tableViewItem = tableViewItem; } - - int headerHeight() { return _dataRowsMaxHeight; } - int rowNumberWidth() { return _rowNumberMaxWidth; } + DataSetView(QQuickItem *parent = nullptr); + + static DataSetView * lastInstancedDataSetView() { return _lastInstancedDataSetView; } + + void setModel(QAbstractItemModel * model); + + QAbstractItemModel * model() const { return _model; } + QItemSelectionModel * selectionModel() const { return _selectionModel; } + + int itemHorizontalPadding() const { return _itemHorizontalPadding; } + int itemVerticalPadding() const { return _itemVerticalPadding; } + int headerHeight() const { return _dataRowsMaxHeight; } + int rowNumberWidth() const { return _rowNumberMaxWidth; } + + double viewportX() const { return _viewportX; } + double viewportY() const { return _viewportY; } + double viewportW() const { return _viewportW; } + double viewportH() const { return _viewportH; } + + QQmlComponent * itemDelegate() const { return _itemDelegate; } + QQmlComponent * rowNumberDelegate() const { return _rowNumberDelegate; } + QQmlComponent * columnHeaderDelegate() const { return _columnHeaderDelegate; } + QQuickItem * leftTopCornerItem() const { return _leftTopItem; } + QQuickItem * extraColumnItem() const { return _extraColumnItem; } + QQuickItem * tableViewItem() const { return _tableViewItem; } + QQmlComponent * editDelegate() const { return _editDelegate; } + + bool cacheItems() const { return _cacheItems; } + QModelIndex selectionStart() const { return _selectionStart; } + QModelIndex selectionEnd() const { return _selectionEnd; } + bool editing() const { return _editing; } + + Q_INVOKABLE QQuickItem* getColumnHeader(int col) { return _columnHeaderItems.count(col) > 0 ? _columnHeaderItems[col]->item : nullptr; } + Q_INVOKABLE QQuickItem* getRowHeader( int row) { return _rowNumberItems.count(row) > 0 ? _rowNumberItems[row]->item : nullptr; } + + + + + GENERIC_SET_FUNCTION(ViewportX, _viewportX, viewportXChanged, double ) + GENERIC_SET_FUNCTION(ViewportY, _viewportY, viewportYChanged, double ) + GENERIC_SET_FUNCTION(ViewportW, _viewportW, viewportWChanged, double ) + GENERIC_SET_FUNCTION(ViewportH, _viewportH, viewportHChanged, double ) + + void setItemHorizontalPadding( int newHorizontalPadding) { if(newHorizontalPadding != _itemHorizontalPadding) { _itemHorizontalPadding = newHorizontalPadding; emit itemHorizontalPaddingChanged(); update(); } } + void setItemVerticalPadding( int newVerticalPadding) { if(newVerticalPadding != _itemVerticalPadding) { _itemVerticalPadding = newVerticalPadding; emit itemVerticalPaddingChanged(); update(); } } + + void setRowNumberDelegate( QQmlComponent * newDelegate); + void setColumnHeaderDelegate( QQmlComponent * newDelegate); + void setItemDelegate( QQmlComponent * newDelegate); + void setLeftTopCornerItem( QQuickItem * newItem); + void setExtraColumnItem( QQuickItem * newItem); + void setEditDelegate( QQmlComponent * editDelegate); + void setTableViewItem( QQuickItem * tableViewItem) { _tableViewItem = tableViewItem; } + void setCacheItems( bool cacheItems); void resetItems(); - Q_INVOKABLE QQuickItem* getColumnHeader(int col) { return _columnHeaderItems.count(col) > 0 ? _columnHeaderItems[col]->item : nullptr; } - Q_INVOKABLE QQuickItem* getRowHeader(int row) { return _rowNumberItems.count(row) > 0 ? _rowNumberItems[row]->item : nullptr; } - GENERIC_SET_FUNCTION(HeaderHeight, _dataRowsMaxHeight, headerHeightChanged, double) GENERIC_SET_FUNCTION(RowNumberWidth, _rowNumberMaxWidth, rowNumberWidthChanged, double) - + + + signals: - void modelChanged(); - void itemHorizontalPaddingChanged(); - void itemVerticalPaddingChanged(); - - void viewportXChanged(); - void viewportYChanged(); - void viewportWChanged(); - void viewportHChanged(); - - void rowNumberDelegateChanged(); - void columnHeaderDelegateChanged(); - void itemDelegateChanged(); - void leftTopCornerItemChanged(); - void extraColumnItemChanged(); - - void itemSizeChanged(); - - void headerHeightChanged(); - void rowNumberWidthChanged(); - - void cacheItemsChanged(); - - - + void modelChanged(); + void selectionModelChanged(); + void itemHorizontalPaddingChanged(); + void itemVerticalPaddingChanged(); + + void viewportXChanged(); + void viewportYChanged(); + void viewportWChanged(); + void viewportHChanged(); + + void rowNumberDelegateChanged(); + void columnHeaderDelegateChanged(); + void itemDelegateChanged(); + void leftTopCornerItemChanged(); + void extraColumnItemChanged(); + void editDelegateChanged(QQmlComponent * editDelegate); + + void itemSizeChanged(); + + void headerHeightChanged(); + void rowNumberWidthChanged(); + + void cacheItemsChanged(); + + void selectionStartChanged( QModelIndex selectionStart); + void selectionEndChanged( QModelIndex selectionEnd); + void editingChanged(bool shiftSelectActive); + + void selectionBudgesUp(); + void selectionBudgesDown(); + void selectionBudgesLeft(); + void selectionBudgesRight(); + + public slots: - void calculateCellSizes() { calculateCellSizesAndClear(false); } - void aContentSizeChanged() { _recalculateCellSizes = true; } - void viewportChanged(); - void myParentChanged(QQuickItem *); - - void reloadTextItems(); - void reloadRowNumbers(); - void reloadColumnHeaders(); - - void modelDataChanged(const QModelIndex &, const QModelIndex &, const QVector &); - void modelHeaderDataChanged(Qt::Orientation, int, int); - void modelAboutToBeReset(); - void modelWasReset(); - void setExtraColumnX(); - + void calculateCellSizes() { calculateCellSizesAndClear(false); } + void aContentSizeChanged() { _recalculateCellSizes = true; } + void viewportChanged(); + void myParentChanged(QQuickItem *); + + void reloadTextItems(); + void reloadRowNumbers(); + void reloadColumnHeaders(); + + void modelDataChanged(const QModelIndex &, const QModelIndex &, const QVector &); + void modelHeaderDataChanged(Qt::Orientation, int, int); + void modelAboutToBeReset(); + void modelWasReset(); + void setExtraColumnX(); + + void setSelectionStart( QModelIndex selectionStart ); + void setSelectionEnd( QModelIndex selectionEnd ); + void pollSelectScroll( QModelIndex mouseIndex ); + void setEditing(bool shiftSelectActive); + bool relaxForSelectScroll(); + QModelIndex selectionTopLeft() const; + + void cut( bool includeHeader = false) { _copy(includeHeader, true); } + void copy( bool includeHeader = false) { _copy(includeHeader, false); } + void paste( bool includeHeader = false); + + void columnSelect( int col); + void columnInsertBefore( int col); + void columnInsertAfter( int col); + void columnDelete( int col); + void rowSelect( int row); + void rowInsertBefore( int row); + void rowInsertAfter( int row); + void rowDelete( int row); + + void selectAll(); + + void edit(QModelIndex here); + void destroyEditItem(); + void editFinished(QModelIndex here, QVariant editedValue); + void onDataModeChanged(bool dataMode); + void contextMenuClickedAtIndex(QModelIndex index); + protected: - void calculateCellSizesAndClear(bool clearStorage); - void setRolenames(); - void determineCurrentViewPortIndices(); - void storeOutOfViewItems(); - void buildNewLinesAndCreateNewItems(); + void _copy(bool includeHeader, bool clear); + void calculateCellSizesAndClear(bool clearStorage); + void setRolenames(); + void determineCurrentViewPortIndices(); + void storeOutOfViewItems(); + void buildNewLinesAndCreateNewItems(); #ifdef DATASETVIEW_ADD_LINES_PLEASE QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) override; @@ -167,82 +219,86 @@ public slots: void storeTextItem(int row, int col, bool cleanUp = true); void setTextItemInfo(int row, int col, QQuickItem * textItem); - QQuickItem * createRowNumber(int row); - void storeRowNumber(int row); + QQuickItem * createRowNumber( int row); + void storeRowNumber( int row); - QQuickItem * createColumnHeader(int col); - void storeColumnHeader(int col); + QQuickItem * createColumnHeader( int col); + void storeColumnHeader( int col); - QQuickItem * createleftTopCorner(); + QQuickItem * createleftTopCorner(); + void updateExtraColumnItem(); + void positionEditItem( int row, int col); - QQmlContext * setStyleDataItem( QQmlContext * previousContext, bool active, size_t col, size_t row); - QQmlContext * setStyleDataRowNumber( QQmlContext * previousContext, QString text, int row); - QQmlContext * setStyleDataColumnHeader( QQmlContext * previousContext, QString text, int column, bool isComputed, bool isInvalidated, bool isFiltered, QString computedError, int columnType); + QQmlContext * setStyleDataItem( QQmlContext * previousContext, bool active, size_t col, size_t row); + QQmlContext * setStyleDataRowNumber( QQmlContext * previousContext, QString text, int row); + QQmlContext * setStyleDataColumnHeader( QQmlContext * previousContext, QString text, int column, bool isComputed, bool isInvalidated, bool isFiltered, QString computedError, int columnType); - void addLine(float x0, float y0, float x1, float y1); - QSizeF getTextSize(const QString& text) const; - QSizeF getColumnSize(int col); - QSizeF getRowHeaderSize(); + void addLine(float x0, float y0, float x1, float y1); + + QSizeF getTextSize(const QString& text) const; + QSizeF getColumnSize(int col); + QSizeF getRowHeaderSize(); protected: - QAbstractItemModel * _model = nullptr; - - std::vector _cellSizes; //[col] - std::vector _colXPositions; //[col][row] - std::vector _dataColsMaxWidth; - std::stack _textItemStorage; - bool _cacheItems = true; - std::stack _rowNumberStorage; - std::map _rowNumberItems; - std::stack _columnHeaderStorage; - std::map _columnHeaderItems; - std::map> _cellTextItems; //[col][row] + QAbstractItemModel * _model = nullptr; + QItemSelectionModel * _selectionModel = nullptr; + std::vector _cellSizes; //[col] + std::vector _colXPositions, //[col][row] + _dataColsMaxWidth; + std::stack _textItemStorage, + _rowNumberStorage, + _columnHeaderStorage; + std::map _rowNumberItems, + _columnHeaderItems; + std::map> _cellTextItems; //[col][row] std::vector _lines; - QQuickItem * _leftTopItem = nullptr, - * _extraColumnItem = nullptr, - * _tableViewItem = nullptr; - - bool _recalculateCellSizes = false, - _ignoreViewpoint = true; - + QQuickItem * _leftTopItem = nullptr, + * _extraColumnItem = nullptr, + * _tableViewItem = nullptr; + QQmlComponent * _itemDelegate = nullptr, + * _rowNumberDelegate = nullptr, + * _columnHeaderDelegate = nullptr, + * _leftTopCornerDelegate = nullptr, + * _styleDataCreator = nullptr, + * _editDelegate = nullptr; + ItemContextualized * _editItemContextual = nullptr; + QSGFlatColorMaterial _material; + std::map _roleNameToRole; + std::map> _storedLineFlags; + std::map> _storedDisplayText; + static DataSetView * _lastInstancedDataSetView; + + bool _cacheItems = true, + _recalculateCellSizes = false, + _ignoreViewpoint = true, + _linesWasChanged = false, + _editing = false; double _dataRowsMaxHeight, _dataWidth = -1, - _rowNumberMaxWidth = 0; - + _rowNumberMaxWidth = 0, + _viewportX = 0, + _viewportY = 0, + _viewportW = 1, + _viewportH = 1; int _itemHorizontalPadding = 8, - _itemVerticalPadding = 8; - - - QQmlComponent * _itemDelegate = nullptr; - QQmlComponent * _rowNumberDelegate = nullptr; - QQmlComponent * _columnHeaderDelegate = nullptr; - QQmlComponent * _leftTopCornerDelegate = nullptr; - QQmlComponent * _styleDataCreator = nullptr; - - QSGFlatColorMaterial material; - - double _viewportX=0, _viewportY=0, _viewportW=1, _viewportH=1; - int _previousViewportColMin = -1, - _previousViewportColMax = -1, - _previousViewportRowMin = -1, - _previousViewportRowMax = -1, - _viewportMargin = 1, - _currentViewportColMin = -1, - _currentViewportColMax = -1, - _currentViewportRowMin = -1, - _currentViewportRowMax = -1; - - std::map _roleNameToRole; - - bool _linesWasChanged = false; - size_t _linesActualSize = 0; - - std::map> _storedLineFlags; - std::map> _storedDisplayText; - - static DataSetView * _lastInstancedDataSetView; + _itemVerticalPadding = 8, + _previousViewportColMin = -1, + _previousViewportColMax = -1, + _previousViewportRowMin = -1, + _previousViewportRowMax = -1, + _viewportMargin = 1, + _currentViewportColMin = -1, + _currentViewportColMax = -1, + _currentViewportRowMin = -1, + _currentViewportRowMax = -1, + _prevEditRow = -1, + _prevEditCol = -1; + size_t _linesActualSize = 0; + long _selectScrollMs = 0; + QModelIndex _selectionStart, + _selectionEnd; }; diff --git a/Desktop/resources/icons/Rlogo.svg b/Desktop/resources/icons/Rlogo.svg deleted file mode 100644 index 389b03c113..0000000000 --- a/Desktop/resources/icons/Rlogo.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/Desktop/resources/icons/darkTheme/Rlogo.svg b/Desktop/resources/icons/darkTheme/Rlogo.svg new file mode 100644 index 0000000000..bec1149833 --- /dev/null +++ b/Desktop/resources/icons/darkTheme/Rlogo.svg @@ -0,0 +1,103 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/Desktop/resources/icons/darkTheme/data-button-erase.svg b/Desktop/resources/icons/darkTheme/data-button-erase.svg new file mode 100644 index 0000000000..0801fed826 --- /dev/null +++ b/Desktop/resources/icons/darkTheme/data-button-erase.svg @@ -0,0 +1,255 @@ + +image/svg+xml + + + + + + + + + + + + + + + diff --git a/Desktop/resources/icons/darkTheme/data-button-external.svg b/Desktop/resources/icons/darkTheme/data-button-external.svg new file mode 100644 index 0000000000..d05916e8a0 --- /dev/null +++ b/Desktop/resources/icons/darkTheme/data-button-external.svg @@ -0,0 +1,123 @@ + +image/svg+xml + + + + + + + + + + + + + + + diff --git a/Desktop/resources/icons/darkTheme/data-button-insert.svg b/Desktop/resources/icons/darkTheme/data-button-insert.svg new file mode 100644 index 0000000000..95dd0b9a1b --- /dev/null +++ b/Desktop/resources/icons/darkTheme/data-button-insert.svg @@ -0,0 +1,262 @@ + +image/svg+xml + + + + + + + + + + + + + + + diff --git a/Desktop/resources/icons/darkTheme/data-button-internal.svg b/Desktop/resources/icons/darkTheme/data-button-internal.svg new file mode 100644 index 0000000000..7d88e8bcc4 --- /dev/null +++ b/Desktop/resources/icons/darkTheme/data-button-internal.svg @@ -0,0 +1,132 @@ + +image/svg+xml + + + + + + + + + + + + + + + diff --git a/Desktop/resources/icons/darkTheme/data-button-new.svg b/Desktop/resources/icons/darkTheme/data-button-new.svg new file mode 100644 index 0000000000..b10a1ecb52 --- /dev/null +++ b/Desktop/resources/icons/darkTheme/data-button-new.svg @@ -0,0 +1,135 @@ + +image/svg+xml + + + + + + + + + + + + + + + diff --git a/Desktop/resources/icons/darkTheme/data-button-resize.svg b/Desktop/resources/icons/darkTheme/data-button-resize.svg new file mode 100644 index 0000000000..7ca2371b30 --- /dev/null +++ b/Desktop/resources/icons/darkTheme/data-button-resize.svg @@ -0,0 +1,112 @@ + +image/svg+xml + + + + + + + + + + + + + + + diff --git a/Desktop/resources/icons/darkTheme/data-button.svg b/Desktop/resources/icons/darkTheme/data-button.svg new file mode 100644 index 0000000000..de53f68b5b --- /dev/null +++ b/Desktop/resources/icons/darkTheme/data-button.svg @@ -0,0 +1,105 @@ + +image/svg+xml + + + + + + + + + + + + + + + diff --git a/Desktop/resources/icons/lightTheme/Rlogo.svg b/Desktop/resources/icons/lightTheme/Rlogo.svg new file mode 100644 index 0000000000..77ad0f2a76 --- /dev/null +++ b/Desktop/resources/icons/lightTheme/Rlogo.svg @@ -0,0 +1,101 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/Desktop/resources/icons/lightTheme/data-button-erase.svg b/Desktop/resources/icons/lightTheme/data-button-erase.svg new file mode 100644 index 0000000000..3cdd7cb24f --- /dev/null +++ b/Desktop/resources/icons/lightTheme/data-button-erase.svg @@ -0,0 +1,255 @@ + +image/svg+xml + + + + + + + + + + + + + + + diff --git a/Desktop/resources/icons/lightTheme/data-button-external.svg b/Desktop/resources/icons/lightTheme/data-button-external.svg new file mode 100644 index 0000000000..41986088da --- /dev/null +++ b/Desktop/resources/icons/lightTheme/data-button-external.svg @@ -0,0 +1,118 @@ + +image/svg+xml + + + + + + + + + + + + + + + diff --git a/Desktop/resources/icons/lightTheme/data-button-insert.svg b/Desktop/resources/icons/lightTheme/data-button-insert.svg new file mode 100644 index 0000000000..02dea2bc7b --- /dev/null +++ b/Desktop/resources/icons/lightTheme/data-button-insert.svg @@ -0,0 +1,263 @@ + +image/svg+xml + + + + + + + + + + + + + + + diff --git a/Desktop/resources/icons/lightTheme/data-button-new.svg b/Desktop/resources/icons/lightTheme/data-button-new.svg new file mode 100644 index 0000000000..85b933ac98 --- /dev/null +++ b/Desktop/resources/icons/lightTheme/data-button-new.svg @@ -0,0 +1,135 @@ + +image/svg+xml + + + + + + + + + + + + + + + diff --git a/Desktop/resources/icons/lightTheme/data-button-resize.svg b/Desktop/resources/icons/lightTheme/data-button-resize.svg new file mode 100644 index 0000000000..9c8b3571b8 --- /dev/null +++ b/Desktop/resources/icons/lightTheme/data-button-resize.svg @@ -0,0 +1,112 @@ + +image/svg+xml + + + + + + + + + + + + + + + diff --git a/Desktop/resources/icons/lightTheme/data-button.svg b/Desktop/resources/icons/lightTheme/data-button.svg new file mode 100644 index 0000000000..7f05efd9bf --- /dev/null +++ b/Desktop/resources/icons/lightTheme/data-button.svg @@ -0,0 +1,106 @@ + +image/svg+xml + + + + + + + + + + + + + + + diff --git a/Desktop/utilities/languagemodel.cpp b/Desktop/utilities/languagemodel.cpp index ba7b2999ed..856425c7cb 100644 --- a/Desktop/utilities/languagemodel.cpp +++ b/Desktop/utilities/languagemodel.cpp @@ -146,7 +146,7 @@ void LanguageModel::setCurrentLanguage(QString language) Settings::setValue(Settings::PREFERRED_LANGUAGE , _currentLanguageCode); Settings::setValue(Settings::PREFERRED_COUNTRY, _languages[_currentLanguageCode].locale.country()); _shouldEmitLanguageChanged = true; - + ResultsJsInterface::singleton()->resetResults(); //resumeEngines() will be emitted in resultsPageLoaded diff --git a/Desktop/utilities/settings.cpp b/Desktop/utilities/settings.cpp index 1b9f956311..0aff7c3e1b 100644 --- a/Desktop/utilities/settings.cpp +++ b/Desktop/utilities/settings.cpp @@ -47,18 +47,20 @@ const Settings::Setting Settings::Values[] = { {"generateMarkdownHelp", false}, {"interfaceFont", #ifdef WIN32 - "Arial"}, // https://github.com/jasp-stats/INTERNAL-jasp/issues/1146 -#elif defined(Q_OS_MACOS) - ".AppleSystemUIFont"}, + "Arial" // https://github.com/jasp-stats/INTERNAL-jasp/issues/1146 +#elif defined(__APPLE__) + ".AppleSystemUIFont" #else - "SansSerif"}, + "SansSerif" #endif + }, {"codeFont", -#ifndef Q_OS_MACOS - "Fira Code"}, +#ifndef __APPLE__ + "Fira Code" #else - ".AppleSystemUIFontMonospaced"}, + ".AppleSystemUIFontMonospaced" #endif + }, #ifdef WIN32 {"resultFont", "Arial,sans-serif,freesans,\"Segoe UI\""}, #elif __APPLE__ @@ -87,8 +89,9 @@ const Settings::Setting Settings::Values[] = { {"showReports", false }, {"showRSyntax", false }, {"showAllROptions", false }, - {"showRSyntaxInResults", false } -}; + {"showRSyntaxInResults", false }, + {"guiQtTextRender", true } +}; QVariant Settings::value(Settings::Type key) { diff --git a/Desktop/widgets/filemenu/filemenu.cpp b/Desktop/widgets/filemenu/filemenu.cpp index 3422390a40..68d43f1bdb 100644 --- a/Desktop/widgets/filemenu/filemenu.cpp +++ b/Desktop/widgets/filemenu/filemenu.cpp @@ -228,6 +228,16 @@ QString FileMenu::getDefaultOutFileName() return DefaultOutFileName; } +void FileMenu::enableButtonsForOpenedWorkspace(bool enableSaveButton) +{ + _actionButtons->setEnabled(ActionButtons::Save, enableSaveButton); + _actionButtons->setEnabled(ActionButtons::SaveAs, true); + _actionButtons->setEnabled(ActionButtons::ExportResults, true); + _actionButtons->setEnabled(ActionButtons::ExportData, true); + _actionButtons->setEnabled(ActionButtons::SyncData, true); + _actionButtons->setEnabled(ActionButtons::Close, true); +} + void FileMenu::dataSetIOCompleted(FileEvent *event) { if (event->operation() == FileEvent::FileSave || event->operation() == FileEvent::FileOpen) @@ -275,19 +285,14 @@ void FileMenu::dataSetIOCompleted(FileEvent *event) } _resourceButtons->setButtonEnabled(ResourceButtons::CurrentFile, !_currentDataFile->getCurrentFilePath().isEmpty()); - + if (event->isSuccessful()) { switch(event->operation()) { case FileEvent::FileOpen: case FileEvent::FileSave: - _actionButtons->setEnabled(ActionButtons::Save, event->type() == Utils::FileType::jasp || event->operation() == FileEvent::FileSave); - _actionButtons->setEnabled(ActionButtons::SaveAs, true); - _actionButtons->setEnabled(ActionButtons::ExportResults, true); - _actionButtons->setEnabled(ActionButtons::ExportData, true); - _actionButtons->setEnabled(ActionButtons::SyncData, true); - _actionButtons->setEnabled(ActionButtons::Close, true); + enableButtonsForOpenedWorkspace(event->type() == Utils::FileType::jasp || event->operation() == FileEvent::FileSave); break; case FileEvent::FileClose: diff --git a/Desktop/widgets/filemenu/filemenu.h b/Desktop/widgets/filemenu/filemenu.h index 5e3d09f145..97f9e34dc4 100644 --- a/Desktop/widgets/filemenu/filemenu.h +++ b/Desktop/widgets/filemenu/filemenu.h @@ -128,6 +128,7 @@ public slots: void analysesExportResults(); void refresh(); void close(); + void enableButtonsForOpenedWorkspace(bool enableSaveButton = false); diff --git a/QMLComponents/components/JASP/Controls/JASPScrollBar.qml b/QMLComponents/components/JASP/Controls/JASPScrollBar.qml index 5ef19aa4aa..2597ae5738 100644 --- a/QMLComponents/components/JASP/Controls/JASPScrollBar.qml +++ b/QMLComponents/components/JASP/Controls/JASPScrollBar.qml @@ -26,13 +26,13 @@ Item id : scrollbar width : vertical ? breadth : undefined height : vertical ? undefined : breadth - visible : flickable.visible && ((vertical ? flickable.visibleArea.heightRatio : flickable.visibleArea.widthRatio ) < 1.0) + visible : flickable.visible && ((vertical ? heightRatio : widthRatio ) < 1.0) readonly property int visibleBreadth : bigBar ? jaspTheme.scrollbarBoxWidthBig : jaspTheme.scrollbarBoxWidth property int breadth : visible ? visibleBreadth : 0 property int extraMarginRightOrBottom : 0 property int extraMarginLeftOrTop : 0 - property Flickable flickable : null + property Item flickable : null property int minimumLength : 16 * preferencesModel.uiScale property string bkColor : jaspTheme.white property string fgColor : jaspTheme.gray @@ -41,7 +41,9 @@ Item property bool vertical : true property bool manualAnchor : false property bool bigBar : false - + property real heightRatio : flickable.height / flickable.contentHeight + property real widthRatio : flickable.width / flickable.contentWidth + anchors { right: manualAnchor ? undefined : flickable.right @@ -77,24 +79,30 @@ Item function scroll(movement) { - if(vertical) flickable.contentY = Math.max (0, Math.min (flickable.contentY + (flickable.height * movement), flickable.contentHeight - flickable.height)); - else flickable.contentX = Math.max (0, Math.min (flickable.contentX + (flickable.width * movement), flickable.contentWidth - flickable.width)); + if(vertical) flickable.contentY = Math.max (0, Math.min (flickable.contentY + (flickable.height * movement), flickable.contentHeight - flickable.height)) ; + else flickable.contentX = Math.max (0, Math.min (flickable.contentX + (flickable.width * movement), flickable.contentWidth - flickable.width)) ; } - function scrollDown() { scroll( 0.125); } - function scrollUp () { scroll(-0.125); } + function scrollDown(smallTicks = false) { scroll( (smallTicks ? 0.05 : 0.125) ); } + function scrollUp (smallTicks = false) { scroll(-(smallTicks ? 0.05 : 0.125) ); } - function scrollWheel(wheel) + function scrollWheel(wheel, smallTicks=false) { - if(scrollbar.vertical) + var tryVertical = true; + if(wheel.pixelDelta.y == 0 && wheel.angleDelta.y == 0) + tryVertical = false; + + if(tryVertical) { if(wheel.pixelDelta.y !== 0) scrollbar.scroll(-wheel.pixelDelta.y / scrollbar.height) - else if(wheel.angleDelta.y < 0) scrollbar.scrollDown(); - else if(wheel.angleDelta.y > 0) scrollbar.scrollUp(); + else if(wheel.angleDelta.y < 0) scrollbar.scrollDown(smallTicks); + else if(wheel.angleDelta.y > 0) scrollbar.scrollUp( smallTicks); + else wheel.accepted = false } else { if(wheel.pixelDelta.x !== 0) scrollbar.scroll(-wheel.pixelDelta.x / scrollbar.width) - else if(wheel.angleDelta.x < 0) scrollbar.scrollDown(); - else if(wheel.angleDelta.x > 0) scrollbar.scrollUp(); + else if(wheel.angleDelta.x < 0) scrollbar.scrollDown(smallTicks); + else if(wheel.angleDelta.x > 0) scrollbar.scrollUp( smallTicks); + else wheel.accepted = false } } @@ -152,8 +160,8 @@ Item } - onClicked: if(scrollbar.vertical) flickable.contentY = (mouse.y / groove.height * (flickable.contentHeight - flickable.height)); - else flickable.contentX = (mouse.x / groove.width * (flickable.contentWidth - flickable.width)); + onClicked: if(scrollbar.vertical) flickable.contentY = (mouse.y / groove.height * (flickable.contentHeight - flickable.height)) ; + else flickable.contentX = (mouse.x / groove.width * (flickable.contentWidth - flickable.width)) ; } } @@ -235,8 +243,8 @@ Item Item { id: handle; - height: !scrollbar.vertical ? parent.height : Math.max (scrollbar.minimumLength, (flickable.visibleArea.heightRatio * groove.height)) - width: scrollbar.vertical ? parent.width : Math.max (scrollbar.minimumLength, (flickable.visibleArea.widthRatio * groove.width)) + height: !scrollbar.vertical ? parent.height : Math.max (scrollbar.minimumLength, (scrollbar.heightRatio * groove.height)) + width: scrollbar.vertical ? parent.width : Math.max (scrollbar.minimumLength, (scrollbar.widthRatio * groove.width)) anchors { top: scrollbar.vertical ? undefined : parent.top diff --git a/QMLComponents/components/JASP/Controls/TextField.qml b/QMLComponents/components/JASP/Controls/TextField.qml index ce5e42c926..0a778116c1 100644 --- a/QMLComponents/components/JASP/Controls/TextField.qml +++ b/QMLComponents/components/JASP/Controls/TextField.qml @@ -221,20 +221,21 @@ TextInputBase Rectangle { - id: afterLabelRect - width: afterLabel.implicitWidth - height: control.height - color: debug ? jaspTheme.debugBackgroundColor : "transparent" - visible: afterLabel.text && textField.visible - anchors.left: control.right + id: afterLabelRect + width: afterLabel.implicitWidth + height: control.height + color: debug ? jaspTheme.debugBackgroundColor : "transparent" + visible: afterLabel.text && textField.visible + anchors.left: control.right anchors.leftMargin: jaspTheme.labelSpacing + Label { - id: afterLabel - font: jaspTheme.font + id: afterLabel + font: jaspTheme.font anchors.verticalCenter: parent.verticalCenter - color: enabled ? jaspTheme.textEnabled : jaspTheme.textDisabled - text: textField.afterLabel + color: enabled ? jaspTheme.textEnabled : jaspTheme.textDisabled + text: textField.afterLabel } } } From 003123c2030b0bd795d47c03d8e104586f5cc42c Mon Sep 17 00:00:00 2001 From: Joris Goosen Date: Tue, 21 Sep 2021 16:31:29 +0200 Subject: [PATCH 02/38] SKF JASP postmerge and fix - Quality Control is a "Common" Module for SKF (and is now enabled here) - Fix bug where data wouldn't be writable after resizing data to something larger than 512 rows and back (cleaning up shared memory _blocks basically) - ResizeDataDialog should have roundedbuttons as well --- CommonData/column.cpp | 17 ++++++++++++++++- .../JASP/Widgets/ResizeDataDialog.qml | 4 ++-- Desktop/mainwindow.cpp | 1 + 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CommonData/column.cpp b/CommonData/column.cpp index 6bdd45521d..3c698288ba 100644 --- a/CommonData/column.cpp +++ b/CommonData/column.cpp @@ -1165,6 +1165,8 @@ void Column::truncate(int rows) if (rowsToDelete > _rowCount) rowsToDelete = _rowCount; + + std::vector destroyThese; while (rowsToDelete > 0) { @@ -1179,7 +1181,7 @@ void Column::truncate(int rows) rowsToDelete -= block->rowCount(); _rowCount -= block->rowCount(); block->erase(block->rowCount()); - //_mem->destroy_ptr(block); + destroyThese.push_back(block); itr++; if (itr == _blocks.rend()) { @@ -1193,6 +1195,19 @@ void Column::truncate(int rows) } } } + + for(DataBlock * block : destroyThese) + { + _mem->destroy_ptr(block); + + for(const auto & keyval : _blocks) + if(keyval.second == block) + { + _blocks.erase(keyval.first); + break; + } + } + } void Column::setColumnType(enum columnType columnType) diff --git a/Desktop/components/JASP/Widgets/ResizeDataDialog.qml b/Desktop/components/JASP/Widgets/ResizeDataDialog.qml index ef351c6ca3..5c26947406 100644 --- a/Desktop/components/JASP/Widgets/ResizeDataDialog.qml +++ b/Desktop/components/JASP/Widgets/ResizeDataDialog.qml @@ -142,7 +142,7 @@ Popup } } - RectangularButton + RoundedButton { id: resizeButton activeFocusOnTab: true @@ -164,7 +164,7 @@ Popup } } - RectangularButton + RoundedButton { id: closeButtonCross activeFocusOnTab: true diff --git a/Desktop/mainwindow.cpp b/Desktop/mainwindow.cpp index 73f44854f1..ec594b1e7d 100644 --- a/Desktop/mainwindow.cpp +++ b/Desktop/mainwindow.cpp @@ -567,6 +567,7 @@ void MainWindow::loadQML() } + void MainWindow::showEnginesWindow() { Log::log() << "Showing EnginesWindow" << std::endl; From aa63d2640fe76f0474fc963b802d7bc7f3e57534 Mon Sep 17 00:00:00 2001 From: Joris Goosen Date: Wed, 1 Dec 2021 14:16:10 +0100 Subject: [PATCH 03/38] latest version of jaspProcessControl and other submodules If all goes well this is the SKF 0.16.1 version Upped the buildnumber to 0.16.1.1 to differentiate from normal 0.16.1 and upcoming 0.16.2 as this is a bit in between Also silence some debug prints --- Desktop/components/JASP/Widgets/JASPDataView.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Desktop/components/JASP/Widgets/JASPDataView.qml b/Desktop/components/JASP/Widgets/JASPDataView.qml index 116a5d34f4..564587be18 100644 --- a/Desktop/components/JASP/Widgets/JASPDataView.qml +++ b/Desktop/components/JASP/Widgets/JASPDataView.qml @@ -177,7 +177,7 @@ FocusScope onPressed: (mouse)=> { - console.log("doubleclick workaround pressed") + //console.log("doubleclick workaround pressed") if(!__JASPDataViewRoot.doubleClickWorkaround) { mouse.accepted = false; @@ -188,13 +188,13 @@ FocusScope if(lastTimeClicked === -1 || curTime - lastTimeClicked > doubleClickTime) { - console.log("doubleclick workaround pressed set time") + // console.log("doubleclick workaround pressed set time") lastTimeClicked = curTime mouse.accepted = false } else { - console.log("doubleclick workaround activated") + // console.log("doubleclick workaround activated") lastTimeClicked = -1 __JASPDataViewRoot.doubleClicked() } From 82f7f861a28a556587333ba3186076ae57c7fcce Mon Sep 17 00:00:00 2001 From: Joris Goosen Date: Thu, 10 Mar 2022 16:28:50 +0100 Subject: [PATCH 04/38] post qt6 merge fixes --- Desktop/components/JASP/Widgets/DataTableView.qml | 2 -- .../JASP/Widgets/PlotEditor/PlotEditor.qml | 2 +- .../components/JASP/Widgets/RenameColumnDialog.qml | 2 +- .../components/JASP/Widgets/ResizeDataDialog.qml | 4 ++-- Desktop/components/JASP/Widgets/Ribbon/Ribbons.qml | 3 ++- .../components/JASP/Widgets/VariablesWindow.qml | 2 +- Desktop/data/datasetpackage.cpp | 3 ++- Desktop/qquick/datasetview.cpp | 14 +++++++------- Desktop/qquick/datasetview.h | 6 +++--- 9 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Desktop/components/JASP/Widgets/DataTableView.qml b/Desktop/components/JASP/Widgets/DataTableView.qml index 17ad0f075f..5527901da1 100644 --- a/Desktop/components/JASP/Widgets/DataTableView.qml +++ b/Desktop/components/JASP/Widgets/DataTableView.qml @@ -1,9 +1,7 @@ - import QtQuick import QtQuick.Controls import JASP.Controls as JaspControls import QtQml.Models -import QtGraphicalEffects FocusScope diff --git a/Desktop/components/JASP/Widgets/PlotEditor/PlotEditor.qml b/Desktop/components/JASP/Widgets/PlotEditor/PlotEditor.qml index e70ef608de..2be5de352b 100644 --- a/Desktop/components/JASP/Widgets/PlotEditor/PlotEditor.qml +++ b/Desktop/components/JASP/Widgets/PlotEditor/PlotEditor.qml @@ -21,7 +21,7 @@ Popup onVisibleChanged: plotEditorModel.visible = visible focus: true - Shortcut { onActivated: cancel(); sequence: StandardKey.Cancel; autoRepeat: false; enabled: visible } + Shortcut { onActivated: cancel(); sequences: [StandardKey.Cancel]; autoRepeat: false; enabled: visible } function cancel() { diff --git a/Desktop/components/JASP/Widgets/RenameColumnDialog.qml b/Desktop/components/JASP/Widgets/RenameColumnDialog.qml index 30fdb95725..de2701a598 100644 --- a/Desktop/components/JASP/Widgets/RenameColumnDialog.qml +++ b/Desktop/components/JASP/Widgets/RenameColumnDialog.qml @@ -25,7 +25,7 @@ Popup Connections { target: dataSetModel - onRenameColumnDialog: + function onRenameColumnDialog(columnIndex) { console.log("renaming column dialog opened for " + String(columnIndex)) colIndex = columnIndex; diff --git a/Desktop/components/JASP/Widgets/ResizeDataDialog.qml b/Desktop/components/JASP/Widgets/ResizeDataDialog.qml index 5c26947406..dfff6ada14 100644 --- a/Desktop/components/JASP/Widgets/ResizeDataDialog.qml +++ b/Desktop/components/JASP/Widgets/ResizeDataDialog.qml @@ -22,8 +22,8 @@ Popup Connections { - target: ribbonModel - onResizeData: popupResizeData.open() + target: ribbonModel + function onResizeData() { popupResizeData.open() } } Loader diff --git a/Desktop/components/JASP/Widgets/Ribbon/Ribbons.qml b/Desktop/components/JASP/Widgets/Ribbon/Ribbons.qml index 46671c696c..457f6043a0 100644 --- a/Desktop/components/JASP/Widgets/Ribbon/Ribbons.qml +++ b/Desktop/components/JASP/Widgets/Ribbon/Ribbons.qml @@ -32,7 +32,8 @@ Item Connections { target: ribbonModel - onDataModeChanged: focusOut(); + + function onDataModeChanged() { focusOut(); } } function setCurrentIndex(which, _index=null) diff --git a/Desktop/components/JASP/Widgets/VariablesWindow.qml b/Desktop/components/JASP/Widgets/VariablesWindow.qml index c09d720d7b..26b260a012 100644 --- a/Desktop/components/JASP/Widgets/VariablesWindow.qml +++ b/Desktop/components/JASP/Widgets/VariablesWindow.qml @@ -74,7 +74,7 @@ FocusScope { id: columnNameVariablesWindow text: labelModel.columnName - onTextChanged: labelModel.columnName = text + onTextChanged: if(labelModel.columnName != text) labelModel.columnName = text color: jaspTheme.textEnabled font: jaspTheme.fontGroupTitle enabled: ribbonModel.dataMode diff --git a/Desktop/data/datasetpackage.cpp b/Desktop/data/datasetpackage.cpp index 1af39a451c..87104cb728 100644 --- a/Desktop/data/datasetpackage.cpp +++ b/Desktop/data/datasetpackage.cpp @@ -220,8 +220,9 @@ QModelIndex DataSetPackage::index(int row, int column, const QModelIndex &parent Log::log() << "Got a valid parent in DataSetPackage::index but it isn't one of the `*Root`s" << std::endl; break; } - } + + return createIndex(row, column, pointer); } const DataSetPackage::intnlPntPair * DataSetPackage::getInternalPointerPairFromIndex(const QModelIndex & index) const diff --git a/Desktop/qquick/datasetview.cpp b/Desktop/qquick/datasetview.cpp index cec8ea62a9..6485bfb11e 100644 --- a/Desktop/qquick/datasetview.cpp +++ b/Desktop/qquick/datasetview.cpp @@ -402,7 +402,7 @@ void DataSetView::buildNewLinesAndCreateNewItems() if(_currentViewportColMax == -1 || _currentViewportColMin == -1 || _currentViewportRowMax == -1 || _currentViewportRowMin == -1) return; -#ifdef DATASETVIEW_ADD_LINES_PLEASE +#ifdef ADD_LINES_PLEASE _linesActualSize = 0; size_t expectedLinesSize = (_currentViewportColMax - _currentViewportColMin) * (_currentViewportRowMax - _currentViewportRowMin) * 4 * 2; if(_lines.size() < expectedLinesSize) @@ -446,7 +446,7 @@ void DataSetView::buildNewLinesAndCreateNewItems() #endif -#ifdef DATASETVIEW_ADD_LINES_PLEASE +#ifdef ADD_LINES_PLEASE if(up) addLine(pos1x, pos0y, pos0x, pos0y); if(down) addLine(pos0x, pos1y, pos1x, pos1y); @@ -467,7 +467,7 @@ void DataSetView::buildNewLinesAndCreateNewItems() JASPTIMER_STOP(buildNewLinesAndCreateNewItems_GRID); -#ifdef DATASETVIEW_ADD_LINES_PLEASE +#ifdef ADD_LINES_PLEASE addLine(_viewportX + 0.5f, _viewportY, _viewportX + 0.5f, _viewportY + _viewportH); addLine(_viewportX + _rowNumberMaxWidth, _viewportY, _viewportX + _rowNumberMaxWidth, _viewportY + _viewportH); @@ -486,7 +486,7 @@ void DataSetView::buildNewLinesAndCreateNewItems() if(createRowNumber(row)) { -#ifdef DATASETVIEW_ADD_LINES_PLEASE +#ifdef ADD_LINES_PLEASE float pos0x(_viewportX), pos0y((1 + row) * _dataRowsMaxHeight), pos1x(_viewportX + _rowNumberMaxWidth), @@ -507,7 +507,7 @@ void DataSetView::buildNewLinesAndCreateNewItems() createColumnHeader(col); -#ifdef DATASETVIEW_ADD_LINES_PLEASE +#ifdef ADD_LINES_PLEASE float pos0x(_colXPositions[col]), pos0y(_viewportY), pos1x(pos0x + _dataColsMaxWidth[col]), @@ -522,7 +522,7 @@ void DataSetView::buildNewLinesAndCreateNewItems() #endif } -#ifdef DATASETVIEW_ADD_LINES_PLEASE +#ifdef ADD_LINES_PLEASE _linesWasChanged = true; #endif @@ -1531,7 +1531,7 @@ void DataSetView::myParentChanged(QQuickItem * newParentItem) */ } -#ifdef DATASETVIEW_ADD_LINES_PLEASE +#ifdef ADD_LINES_PLEASE QSGNode * DataSetView::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) { //JASPTIMER_RESUME(updatePaintNode); diff --git a/Desktop/qquick/datasetview.h b/Desktop/qquick/datasetview.h index 63abf65292..05a255fd07 100644 --- a/Desktop/qquick/datasetview.h +++ b/Desktop/qquick/datasetview.h @@ -18,8 +18,8 @@ //#define DATASETVIEW_DEBUG_VIEWPORT //#define DATASETVIEW_DEBUG_CREATION -#define DATASETVIEW_SHOW_ITEMS_PLEASE -#define DATASETVIEW_ADD_LINES_PLEASE +#define SHOW_ITEMS_PLEASE +#define ADD_LINES_PLEASE struct ItemContextualized { @@ -210,7 +210,7 @@ public slots: void storeOutOfViewItems(); void buildNewLinesAndCreateNewItems(); -#ifdef DATASETVIEW_ADD_LINES_PLEASE +#ifdef ADD_LINES_PLEASE QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) override; #endif float extraColumnWidth() { return !_extraColumnItem ? 0 : 2 + _extraColumnItem->width(); } From 825d4746ee4f44780eba6af5f96f9b12a33234af Mon Sep 17 00:00:00 2001 From: Joris Goosen Date: Thu, 10 Nov 2022 19:02:04 +0100 Subject: [PATCH 05/38] post rebase fixes 10-11-22 --- .../components/JASP/Widgets/DataTableView.qml | 25 +++++++++++-------- .../components/JASP/Widgets/JASPDataView.qml | 2 +- Desktop/mainwindow.cpp | 2 +- Desktop/mainwindow.h | 2 +- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/Desktop/components/JASP/Widgets/DataTableView.qml b/Desktop/components/JASP/Widgets/DataTableView.qml index 5527901da1..4cf44177ac 100644 --- a/Desktop/components/JASP/Widgets/DataTableView.qml +++ b/Desktop/components/JASP/Widgets/DataTableView.qml @@ -166,8 +166,8 @@ FocusScope Connections { - target: ribbonModel - onFinishCurrentEdit: finishEdit(); + target: ribbonModel + function onFinishCurrentEdit() { finishEdit(); } } function finishEdit() @@ -177,7 +177,7 @@ FocusScope alreadyFinished = true; } - Keys.onPressed: + Keys.onPressed: (event) => { var controlPressed = Boolean(event.modifiers & Qt.ControlModifier); var shiftPressed = Boolean(event.modifiers & Qt.ShiftModifier ); @@ -290,7 +290,8 @@ FocusScope anchors.fill: itemHighlight acceptedButtons: Qt.LeftButton | Qt.RightButton - onPressed: + onPressed: (mouse) => + { if(ribbonModel.dataMode) { var shiftPressed = Boolean(mouse.modifiers & Qt.ShiftModifier); @@ -309,13 +310,17 @@ FocusScope forceActiveFocus(); } } + } - onPositionChanged: if(ribbonModel.dataMode && Boolean(mouse.modifiers & Qt.ShiftModifier)) - { - var idx = dataTableView.view.model.index(rowIndex, columnIndex) - dataTableView.view.pollSelectScroll(idx) - dataTableView.view.selectionEnd = idx - } + onPositionChanged: (mouse) => + { + if(ribbonModel.dataMode && Boolean(mouse.modifiers & Qt.ShiftModifier)) + { + var idx = dataTableView.view.model.index(rowIndex, columnIndex) + dataTableView.view.pollSelectScroll(idx) + dataTableView.view.selectionEnd = idx + } + } } diff --git a/Desktop/components/JASP/Widgets/JASPDataView.qml b/Desktop/components/JASP/Widgets/JASPDataView.qml index 564587be18..b87b3acd06 100644 --- a/Desktop/components/JASP/Widgets/JASPDataView.qml +++ b/Desktop/components/JASP/Widgets/JASPDataView.qml @@ -74,7 +74,7 @@ FocusScope } - Keys.onPressed: + Keys.onPressed: (event)=> { var controlPressed = Boolean(event.modifiers & Qt.ControlModifier); var shiftPressed = Boolean(event.modifiers & Qt.ShiftModifier ); diff --git a/Desktop/mainwindow.cpp b/Desktop/mainwindow.cpp index ec594b1e7d..eacd7075df 100644 --- a/Desktop/mainwindow.cpp +++ b/Desktop/mainwindow.cpp @@ -297,7 +297,7 @@ void MainWindow::makeConnections() connect(_package, &DataSetPackage::newDataLoaded, _fileMenu, [&](){ _fileMenu->enableButtonsForOpenedWorkspace(); } ); connect(_package, &DataSetPackage::dataModeChanged, _analyses, &Analyses::dataModeChanged ); connect(_package, &DataSetPackage::dataModeChanged, _engineSync, &EngineSync::dataModeChanged ); - //connect(_package, &DataSetPackage::dataModeChanged, this, &MainWindow::onDataModeChanged ); + connect(_package, &DataSetPackage::dataModeChanged, this, &MainWindow::onDataModeChanged ); connect(_package, &DataSetPackage::askUserForExternalDataFile, this, &MainWindow::startDataEditorHandler ); connect(_engineSync, &EngineSync::computeColumnSucceeded, _computedColumnsModel, &ComputedColumnsModel::computeColumnSucceeded ); diff --git a/Desktop/mainwindow.h b/Desktop/mainwindow.h index 12bff0280b..d241173f55 100644 --- a/Desktop/mainwindow.h +++ b/Desktop/mainwindow.h @@ -237,7 +237,7 @@ private slots: void resetQmlCache(); void setCurrentJaspTheme(); - //void onDataModeChanged(bool dataMode) { if(analysesAvailable()) setDataPanelVisible(dataMode); } + void onDataModeChanged(bool dataMode) { if(dataMode && welcomePageVisible()) setWelcomePageVisible(false); } void printQmlWarnings(const QList &warnings); void setQmlImportPaths(); From 8a496305af726bc92ec360074d165afe29112af2 Mon Sep 17 00:00:00 2001 From: Joris Goosen Date: Wed, 7 Dec 2022 15:15:46 +0100 Subject: [PATCH 06/38] post rebase fix 7-12-22 resize the resize data dialog a bit made sure emptyvalue labels are not put in the editItem --- CommonData/columnutils.h | 3 +- .../JASP/Widgets/ResizeDataDialog.qml | 49 +++++++++---------- Desktop/data/datasetpackage.cpp | 5 +- Desktop/qquick/datasetview.cpp | 11 +++-- Desktop/qquick/datasetview.h | 2 +- QMLComponents/CMakeLists.txt | 2 + QMLComponents/controls/textinputbase.cpp | 1 + 7 files changed, 41 insertions(+), 32 deletions(-) diff --git a/CommonData/columnutils.h b/CommonData/columnutils.h index 4a27465118..b09e725b91 100644 --- a/CommonData/columnutils.h +++ b/CommonData/columnutils.h @@ -3,7 +3,8 @@ #include #include - +#include +#include class ColumnUtils { diff --git a/Desktop/components/JASP/Widgets/ResizeDataDialog.qml b/Desktop/components/JASP/Widgets/ResizeDataDialog.qml index dfff6ada14..cf1d0c4d35 100644 --- a/Desktop/components/JASP/Widgets/ResizeDataDialog.qml +++ b/Desktop/components/JASP/Widgets/ResizeDataDialog.qml @@ -39,8 +39,8 @@ Popup Item { - height: resizeButton.y + resizeButton.height + jaspTheme.generalAnchorMargin - width: inputs.width + height: inputs.height + jaspTheme.generalAnchorMargin + (40 * jaspTheme.uiScale) + width: 250 * jaspTheme.uiScale Component.onCompleted: cols.forceActiveFocus(); @@ -62,8 +62,7 @@ Popup Item { id: inputs - width: 200 * jaspTheme.uiScale - height: cols.y + cols.height + jaspTheme.generalAnchorMargin + height: 100 * jaspTheme.uiScale + 2 * jaspTheme.generalAnchorMargin anchors { @@ -79,9 +78,8 @@ Popup text: qsTr("Columns") anchors { - top: inputs.top - left: cols.left - right: cols.right + top: inputs.top + horizontalCenter: cols.horizontalCenter } } @@ -91,9 +89,8 @@ Popup text: qsTr("Rows") anchors { - top: inputs.top - left: rows.left - right: rows.right + top: inputs.top + horizontalCenter: rows.horizontalCenter } } @@ -103,7 +100,12 @@ Popup text: "X" horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter - anchors.centerIn: parent + anchors + { + top: colsLabel.bottom + horizontalCenter: parent.horizontalCenter + topMargin: jaspTheme.generalAnchorMargin + } } IntegerField @@ -113,13 +115,13 @@ Popup fieldWidth: width anchors { - top: x.verticalCenter + top: x.bottom left: parent.left right: x.left margins: jaspTheme.generalAnchorMargin } - //KeyNavigation.tab: rows + KeyNavigation.tab: rows KeyNavigation.right: rows KeyNavigation.down: resizeButton } @@ -131,14 +133,13 @@ Popup fieldWidth: width anchors { - top: x.verticalCenter + top: x.bottom left: x.right right: parent.right margins: jaspTheme.generalAnchorMargin } - KeyNavigation.down: resizeButton - KeyNavigation.right: resizeButton - //KeyNavigation.tab: resizeButton + KeyNavigation.down: closeButtonCross + KeyNavigation.tab: resizeButton } } @@ -149,15 +150,12 @@ Popup text: qsTr("Resize") onClicked: { dataSetModel.resizeData(rows.value, cols.value); popupResizeData.close(); } toolTip: qsTr("Resize data to set values") - KeyNavigation.right: closeButtonCross - //KeyNavigation.tab: closeButtonCross - //KeyNavigation.backtab: rows - KeyNavigation.left: rows - KeyNavigation.up: cols + + KeyNavigation.tab: closeButtonCross anchors { - top: inputs.bottom + bottom: inputs.bottom margins: jaspTheme.generalAnchorMargin left: parent.left right: closeButtonCross.left @@ -173,11 +171,12 @@ Popup height: resizeButton.height onClicked: popupResizeData.close() toolTip: qsTr("Close without resizing data") - KeyNavigation.up: rows + KeyNavigation.tab: cols + anchors { right: parent.right - top: inputs.bottom + bottom: inputs.bottom margins: jaspTheme.generalAnchorMargin } } diff --git a/Desktop/data/datasetpackage.cpp b/Desktop/data/datasetpackage.cpp index 87104cb728..06eff8cba3 100644 --- a/Desktop/data/datasetpackage.cpp +++ b/Desktop/data/datasetpackage.cpp @@ -26,6 +26,7 @@ #include "timers.h" #include "utilities/appdirs.h" #include "utils.h" +#include "columnutils.h" #include "utilities/messageforwarder.h" #include "datasetpackagesubnodemodel.h" #include "databaseconnectioninfo.h" @@ -1128,13 +1129,13 @@ void DataSetPackage::initColumnWithStrings(QVariant colId, std::string newName, bool useCustomThreshold = Settings::value(Settings::USE_CUSTOM_THRESHOLD_SCALE).toBool(); size_t thresholdScale = (useCustomThreshold ? Settings::value(Settings::THRESHOLD_SCALE) : Settings::defaultValue(Settings::THRESHOLD_SCALE)).toUInt(); - bool valuesAreIntegers = Utils::convertVecToInt(values, intValues, uniqueValues, emptyValuesMap); + bool valuesAreIntegers = ColumnUtils::convertVecToInt(values, intValues, uniqueValues, emptyValuesMap); size_t minIntForThresh = thresholdScale > 2 ? 2 : 0; auto isNominalInt = [&](){ return valuesAreIntegers && uniqueValues.size() == minIntForThresh; }; auto isOrdinal = [&](){ return valuesAreIntegers && uniqueValues.size() > minIntForThresh && uniqueValues.size() <= thresholdScale; }; - auto isScalar = [&](){ return Utils::convertVecToDouble(values, doubleValues, emptyValuesMap); }; + auto isScalar = [&](){ return ColumnUtils::convertVecToDouble(values, doubleValues, emptyValuesMap); }; if (isOrdinal()) initColumnAsNominalOrOrdinal( colId, newName, intValues, true ); else if (isNominalInt()) initColumnAsNominalOrOrdinal( colId, newName, intValues, false ); diff --git a/Desktop/qquick/datasetview.cpp b/Desktop/qquick/datasetview.cpp index 6485bfb11e..c40d2fadf6 100644 --- a/Desktop/qquick/datasetview.cpp +++ b/Desktop/qquick/datasetview.cpp @@ -14,6 +14,7 @@ #include #include #include "utils.h" +#include "columnutils.h" #include "utilities/languagemodel.h" #include "data/datasettablemodel.h" @@ -938,7 +939,7 @@ void DataSetView::positionEditItem(int row, int col) if(!_editItemContextual) { - _editItemContextual = new ItemContextualized(setStyleDataItem(nullptr, active, col, row)); + _editItemContextual = new ItemContextualized(setStyleDataItem(nullptr, active, col, row, false)); //forceActiveFocus(); @@ -960,7 +961,7 @@ void DataSetView::positionEditItem(int row, int col) else { //Log::log() << "repositioning current edit item (row=" << row << ", col=" << col << ")" << std::endl; - setStyleDataItem(_editItemContextual->context, active, col, row); + setStyleDataItem(_editItemContextual->context, active, col, row, false); } setTextItemInfo(row, col, _editItemContextual->item); //Will set it visible @@ -1322,7 +1323,7 @@ void DataSetView::contextMenuClickedAtIndex(QModelIndex index) _selectionModel->select(index, QItemSelectionModel::SelectCurrent); } -QQmlContext * DataSetView::setStyleDataItem(QQmlContext * previousContext, bool active, size_t col, size_t row) +QQmlContext * DataSetView::setStyleDataItem(QQmlContext * previousContext, bool active, size_t col, size_t row, bool emptyValLabel) { QModelIndex idx = _model->index(row, col); @@ -1333,6 +1334,9 @@ QQmlContext * DataSetView::setStyleDataItem(QQmlContext * previousContext, bool QString text = _storedDisplayText[row][col]; + if(text == tq(ColumnUtils::emptyValue) && !emptyValLabel) + text = ""; + if(previousContext == nullptr) previousContext = new QQmlContext(qmlContext(this), this); @@ -1572,6 +1576,7 @@ QSGNode * DataSetView::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) currentNode->setFlag(QSGNode::OwnsMaterial, false); currentNode->setFlag(QSGNode::OwnsGeometry, true); currentNode->setMaterial(&_material); + currentNode->setRenderOrder(100); justAdded = true; diff --git a/Desktop/qquick/datasetview.h b/Desktop/qquick/datasetview.h index 05a255fd07..ced92cdb57 100644 --- a/Desktop/qquick/datasetview.h +++ b/Desktop/qquick/datasetview.h @@ -230,7 +230,7 @@ public slots: void updateExtraColumnItem(); void positionEditItem( int row, int col); - QQmlContext * setStyleDataItem( QQmlContext * previousContext, bool active, size_t col, size_t row); + QQmlContext * setStyleDataItem( QQmlContext * previousContext, bool active, size_t col, size_t row, bool emptyValLabel = true); QQmlContext * setStyleDataRowNumber( QQmlContext * previousContext, QString text, int row); QQmlContext * setStyleDataColumnHeader( QQmlContext * previousContext, QString text, int column, bool isComputed, bool isInvalidated, bool isFiltered, QString computedError, int columnType); diff --git a/QMLComponents/CMakeLists.txt b/QMLComponents/CMakeLists.txt index c612352d3f..9c8291f5ba 100644 --- a/QMLComponents/CMakeLists.txt +++ b/QMLComponents/CMakeLists.txt @@ -20,12 +20,14 @@ target_include_directories( QMLComponents PUBLIC # JASP ${PROJECT_SOURCE_DIR}/Common + ${PROJECT_SOURCE_DIR}/CommonData ) target_link_libraries( QMLComponents PUBLIC Common + CommonData Qt::Core Qt::Gui Qt::Widgets diff --git a/QMLComponents/controls/textinputbase.cpp b/QMLComponents/controls/textinputbase.cpp index ab5397c4e2..74a4359e86 100644 --- a/QMLComponents/controls/textinputbase.cpp +++ b/QMLComponents/controls/textinputbase.cpp @@ -19,6 +19,7 @@ #include "textinputbase.h" #include "analysisform.h" #include "utils.h" +#include "columnutils.h" using namespace std; From 3d71dac5f5f504c48ffc4e3b7635c6f3021a8c13 Mon Sep 17 00:00:00 2001 From: Joris Goosen Date: Wed, 7 Dec 2022 15:36:42 +0100 Subject: [PATCH 07/38] (event)=>{} --- Desktop/components/JASP/Widgets/JASPDataView.qml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Desktop/components/JASP/Widgets/JASPDataView.qml b/Desktop/components/JASP/Widgets/JASPDataView.qml index b87b3acd06..a4ae700269 100644 --- a/Desktop/components/JASP/Widgets/JASPDataView.qml +++ b/Desktop/components/JASP/Widgets/JASPDataView.qml @@ -49,10 +49,10 @@ FocusScope property real contentFlickSize: 100 - Keys.onUpPressed: { budgeUp(); event.accepted = true; } - Keys.onLeftPressed: { budgeLeft(); event.accepted = true; } - Keys.onDownPressed: { budgeDown(); event.accepted = true; } - Keys.onRightPressed: { budgeRight(); event.accepted = true; } + Keys.onUpPressed: (event) => { budgeUp(); event.accepted = true; } + Keys.onLeftPressed: (event) => { budgeLeft(); event.accepted = true; } + Keys.onDownPressed: (event) => { budgeDown(); event.accepted = true; } + Keys.onRightPressed: (event) => { budgeRight(); event.accepted = true; } function budgeUp() { if(myFlickable.contentY0 > 0) myFlickable.contentY = Math.max(0, myFlickable.contentY - contentFlickSize) } function budgeDown() { if(myFlickable.contentY1 < myFlickable.contentHeight) myFlickable.contentY = Math.min(myFlickable.contentHeight - myFlickable.height, myFlickable.contentY + contentFlickSize) } @@ -120,7 +120,7 @@ FocusScope DataSetView { - z: -1 + z: 1 id: theView model: null From 7edb7cd9c54fcfee5663f49751dce5b44dfa5b52 Mon Sep 17 00:00:00 2001 From: boutinb Date: Fri, 27 Jan 2023 14:43:07 +0100 Subject: [PATCH 08/38] Rebasing mistake --- Desktop/modules/ribbonmodel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Desktop/modules/ribbonmodel.cpp b/Desktop/modules/ribbonmodel.cpp index d4d125ddcf..a107726a28 100644 --- a/Desktop/modules/ribbonmodel.cpp +++ b/Desktop/modules/ribbonmodel.cpp @@ -142,7 +142,7 @@ void RibbonModel::addSpecialRibbonButtonsEarly() void RibbonModel::addSpecialRibbonButtonsLate() { - addRibbonButtonModel(new RibbonButton(this, "R", fq(tr("R console")), "Rlogo.svg", false, [&](){ emit showRCommander(); }, fq(tr("Execute R code in a console")))); + addRibbonButtonModel(new RibbonButton(this, "R", fq(tr("R console")), "Rlogo.svg", false, [&](){ emit showRCommander(); }, fq(tr("Execute R code in a console"))), size_t(RowType::Analyses)); } void RibbonModel::dynamicModuleChanged(Modules::DynamicModule * dynMod) From 6349af961c38e3221af5bb22a89e9c81fe8e4732 Mon Sep 17 00:00:00 2001 From: Joris Goosen Date: Wed, 1 Mar 2023 11:22:23 +0100 Subject: [PATCH 09/38] Make sure editing is quick and analyses are hidden in dataMode (#5040) --- Desktop/components/JASP/Widgets/MainPage.qml | 2 +- Desktop/engine/enginesync.cpp | 18 ++++++++++-------- Desktop/mainwindow.cpp | 6 ++++++ Desktop/mainwindow.h | 2 +- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/Desktop/components/JASP/Widgets/MainPage.qml b/Desktop/components/JASP/Widgets/MainPage.qml index 6d0b2bdae9..592173d7c4 100644 --- a/Desktop/components/JASP/Widgets/MainPage.qml +++ b/Desktop/components/JASP/Widgets/MainPage.qml @@ -39,7 +39,7 @@ Item // position of the rest) property bool hasData: mainWindow.dataAvailable - property bool hasAnalysis: mainWindow.analysesAvailable + property bool hasAnalysis: mainWindow.analysesAvailable && !ribbonModel.dataMode function minimizeDataPanel() { diff --git a/Desktop/engine/enginesync.cpp b/Desktop/engine/enginesync.cpp index b56b8f0c4a..0f46f80b7c 100644 --- a/Desktop/engine/enginesync.cpp +++ b/Desktop/engine/enginesync.cpp @@ -963,9 +963,7 @@ void EngineSync::heartbeatTempFiles() } void EngineSync::stopEngines() -{ - _stopProcessing = true; - +{ auto timeout = QDateTime::currentSecsSinceEpoch() + 10; for(EngineRepresentation * e : _engines) @@ -1030,8 +1028,6 @@ void EngineSync::resumeEngines() for(EngineRepresentation * engine : _engines) startStoppedEngine(engine); - _stopProcessing = false; - while(!allEnginesResumed()) for (auto * engine : _engines) engine->processReplies(); @@ -1071,13 +1067,19 @@ bool EngineSync::allEnginesInitializing(std::set these) void EngineSync::dataModeChanged(bool dataMode) { + _stopProcessing = dataMode; + if(!dataMode) { - Log::log() << "Data mode turned off, so restarting engines." << std::endl; - - pauseEngines(); + Log::log() << "Data mode turned off, resuming engines." << std::endl; resumeEngines(); } + else + { + Log::log() << "Data mode turned on, pausing engines." << std::endl; + pauseEngines(); + } + } void EngineSync::enginesPrepareForData() diff --git a/Desktop/mainwindow.cpp b/Desktop/mainwindow.cpp index eacd7075df..6d5ee38d6f 100644 --- a/Desktop/mainwindow.cpp +++ b/Desktop/mainwindow.cpp @@ -654,6 +654,12 @@ void MainWindow::setCurrentJaspTheme() _qml->rootContext()->setContextProperty("jaspTheme", JaspTheme::currentTheme()); } +void MainWindow::onDataModeChanged(bool dataMode) +{ + if(dataMode && welcomePageVisible()) + setWelcomePageVisible(false); +} + void MainWindow::initLog() { assert(_engineSync != nullptr && _preferences != nullptr); diff --git a/Desktop/mainwindow.h b/Desktop/mainwindow.h index d241173f55..1a3f83339b 100644 --- a/Desktop/mainwindow.h +++ b/Desktop/mainwindow.h @@ -237,7 +237,7 @@ private slots: void resetQmlCache(); void setCurrentJaspTheme(); - void onDataModeChanged(bool dataMode) { if(dataMode && welcomePageVisible()) setWelcomePageVisible(false); } + void onDataModeChanged(bool dataMode); void printQmlWarnings(const QList &warnings); void setQmlImportPaths(); From 191709f4d1a0898d3cd62cfa1ee71c946f074332 Mon Sep 17 00:00:00 2001 From: RensDofferhoff <20978635+RensDofferhoff@users.noreply.github.com> Date: Wed, 1 Mar 2023 11:48:26 +0100 Subject: [PATCH 10/38] dirty fix for the resize dialog (#5042) --- Desktop/components/JASP/Widgets/ResizeDataDialog.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Desktop/components/JASP/Widgets/ResizeDataDialog.qml b/Desktop/components/JASP/Widgets/ResizeDataDialog.qml index cf1d0c4d35..c22cbaaec1 100644 --- a/Desktop/components/JASP/Widgets/ResizeDataDialog.qml +++ b/Desktop/components/JASP/Widgets/ResizeDataDialog.qml @@ -148,7 +148,7 @@ Popup id: resizeButton activeFocusOnTab: true text: qsTr("Resize") - onClicked: { dataSetModel.resizeData(rows.value, cols.value); popupResizeData.close(); } + onClicked: { forceActiveFocus(); dataSetModel.resizeData(rows.value, cols.value); popupResizeData.close(); } toolTip: qsTr("Resize data to set values") KeyNavigation.tab: closeButtonCross From 55fea62b451d9dd1585a6bc3f660f6ad83c47a23 Mon Sep 17 00:00:00 2001 From: Joris Goosen Date: Thu, 16 Mar 2023 14:12:35 +0100 Subject: [PATCH 11/38] odule shenanigans do stuff with jaspPower add jaspPredictiveAnalytics Add learnstats and right versions of QC and predictive analytics make sure new modules get loaded jaspLearnStats should also track a branch --- .gitmodules | 9 +++++++++ Modules/jaspLearnStats | 1 + Modules/jaspPredictiveAnalytics | 1 + Tools/CMake/Modules.cmake | 3 ++- 4 files changed, 13 insertions(+), 1 deletion(-) create mode 160000 Modules/jaspLearnStats create mode 160000 Modules/jaspPredictiveAnalytics diff --git a/.gitmodules b/.gitmodules index bffeff0336..a20cf9c446 100644 --- a/.gitmodules +++ b/.gitmodules @@ -116,3 +116,12 @@ [submodule "Modules/jaspPower"] path = Modules/jaspPower url = https://github.com/jasp-stats/jaspPower + branch = master +[submodule "Modules/jaspPredictiveAnalytics"] + path = Modules/jaspPredictiveAnalytics + url = https://github.com/jasp-stats/jaspPredictiveAnalytics + branch = main +[submodule "Modules/jaspLearnStats"] + path = Modules/jaspLearnStats + url = https://github.com/jasp-stats/jaspLearnStats + branch = master diff --git a/Modules/jaspLearnStats b/Modules/jaspLearnStats new file mode 160000 index 0000000000..af16b8b2c4 --- /dev/null +++ b/Modules/jaspLearnStats @@ -0,0 +1 @@ +Subproject commit af16b8b2c4e679d9862a9f9790debb06feab0cdb diff --git a/Modules/jaspPredictiveAnalytics b/Modules/jaspPredictiveAnalytics new file mode 160000 index 0000000000..a6db5bd256 --- /dev/null +++ b/Modules/jaspPredictiveAnalytics @@ -0,0 +1 @@ +Subproject commit a6db5bd256a4e4c1d77ead26c3021361564f7e32 diff --git a/Tools/CMake/Modules.cmake b/Tools/CMake/Modules.cmake index 500584bba0..85757b3966 100644 --- a/Tools/CMake/Modules.cmake +++ b/Tools/CMake/Modules.cmake @@ -39,10 +39,11 @@ set(JASP_EXTRA_MODULES "jaspEquivalenceTTests" "jaspJags" "jaspLearnBayes" + "jaspLearnStats" "jaspMachineLearning" "jaspMetaAnalysis" "jaspNetwork" - "jaspPower" + "jaspPredictiveAnalytics" "jaspProphet" "jaspQualityControl" "jaspReliability" From aba724bb1cc278921ba840ebd39236412fab1bce Mon Sep 17 00:00:00 2001 From: RDOFFERHOFF Date: Mon, 3 Apr 2023 16:38:39 +0200 Subject: [PATCH 12/38] start --- .../JASP/Widgets/FileMenu/PrefsAdvanced.qml | 40 ++++++++++++- Desktop/gui/preferencesmodel.h | 7 +++ Desktop/gui/remotesettings.cpp | 60 +++++++++++++++++++ Desktop/gui/remotesettings.h | 43 +++++++++++++ Desktop/utilities/settings.cpp | 5 +- Desktop/utilities/settings.h | 3 +- 6 files changed, 154 insertions(+), 4 deletions(-) create mode 100644 Desktop/gui/remotesettings.cpp create mode 100644 Desktop/gui/remotesettings.h diff --git a/Desktop/components/JASP/Widgets/FileMenu/PrefsAdvanced.qml b/Desktop/components/JASP/Widgets/FileMenu/PrefsAdvanced.qml index eba2bc9156..4670ceddc5 100644 --- a/Desktop/components/JASP/Widgets/FileMenu/PrefsAdvanced.qml +++ b/Desktop/components/JASP/Widgets/FileMenu/PrefsAdvanced.qml @@ -205,9 +205,47 @@ ScrollView toolTip: qsTr("This will erase the 'renv' and 'Modules' folders in the appdata.") onClicked: mainWindow.clearModulesFoldersUser(); - KeyNavigation.tab: logToFile + KeyNavigation.tab: remoteSettingsURL activeFocusOnTab: true } + + + Item + { + id: remoteSettingsItem + width: parent.width + height: cranRepoUrl.height + + Label + { + id: remoteSettingsLabel + text: qsTr("Settings URL: ") + + anchors + { + left: parent.left + verticalCenter: parent.verticalCenter + margins: jaspTheme.generalAnchorMargin + } + } + + PrefsTextInput + { + id: remoteSettingsURL + + text: preferencesModel.remoteSettingsURL + onEditingFinished: preferencesModel.remoteSettingsURL = text + + height: browseDeveloperFolderButton.height + anchors + { + left: remoteSettingsLabel.right + right: parent.right + } + + KeyNavigation.tab: logToFile + } + } } PrefsGroupRect diff --git a/Desktop/gui/preferencesmodel.h b/Desktop/gui/preferencesmodel.h index d029b42d4a..66a7c59668 100644 --- a/Desktop/gui/preferencesmodel.h +++ b/Desktop/gui/preferencesmodel.h @@ -68,7 +68,11 @@ class PreferencesModel : public PreferencesModelBase Q_PROPERTY(bool reportingMode READ reportingMode WRITE setReportingMode NOTIFY reportingModeChanged ) Q_PROPERTY(bool showRSyntax READ showRSyntax WRITE setShowRSyntax NOTIFY showRSyntaxChanged ) Q_PROPERTY(bool showAllROptions READ showAllROptions WRITE setShowAllROptions NOTIFY showAllROptionsChanged ) +<<<<<<< HEAD Q_PROPERTY(bool showRSyntaxInResults READ showRSyntaxInResults WRITE setShowRSyntaxInResults NOTIFY showRSyntaxInResultsChanged ) +======= + Q_PROPERTY(QString remoteSettingsURL READ remoteSettingsURL WRITE setRemoteSettingsURL NOTIFY remoteSettingsURLChanged ) +>>>>>>> 9536009ae (start) public: @@ -135,6 +139,7 @@ class PreferencesModel : public PreferencesModelBase void zoomReset(); int maxEnginesAdmin() const; bool developerMode() const; + QString remoteSettingsURL() const; public slots: void setUiScale( double uiScale); @@ -193,6 +198,7 @@ public slots: void setShowAllROptions( bool showAllROptions) override; void setShowRSyntaxInResults( bool showRSyntax); void currentThemeNameHandler(); + void setRemoteSettingsURL( QString URL); signals: void fixedDecimalsChanged( bool fixedDecimals); @@ -242,6 +248,7 @@ public slots: void guiQtTextRenderChanged( bool guiQtTextRender); void reportingModeChanged( bool reportingMode); void showRSyntaxInResultsChanged( bool showRSyntax); + void remoteSettingsURLChanged( QString remoteSettingsURL); private slots: void dataLabelNAChangedSlot(QString label); diff --git a/Desktop/gui/remotesettings.cpp b/Desktop/gui/remotesettings.cpp new file mode 100644 index 0000000000..ee32b00d13 --- /dev/null +++ b/Desktop/gui/remotesettings.cpp @@ -0,0 +1,60 @@ +#include "remotesettings.h" +#include "utilities/settings.h" +#include "log.h" +#include +#include +#include +#include +#include +#include + + +RemoteSettings::RemoteSettings(QObject *parent) + : QObject{parent} +{ + //connect(&_networkManager, &QNetworkAccessManager::finished, this, &RemoteSettings::downloadFinished); + versionRE.setPattern("Version:\\s*(?\\d+)"); +} + +void RemoteSettings::processRemoteSettings() +{ + //read & parse local + QString confPath = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); + QFile local(confPath + configurationFilename); + if (!local.open(QIODevice::ReadWrite | QIODevice::Text)) + { + Log::log() << "Could not open local configuration file!"; + return; + } + QTextStream in(&local); + parse(in.readAll()); + + + //read & parse remote +} + +void RemoteSettings::parse(const QString &settings) +{ + _version = getVersion(settings); + + _patches.clear(); + QStringList lines = settings.split("\n"); + for(auto& line : lines) + { + auto match = versionRE.match(line); + if(!match.hasMatch()) + _patches.append(line); + } +} + +int32_t RemoteSettings::getVersion(const QString& settings) +{ + auto match = versionRE.match(settings); + if(match.hasCaptured("versionNum")) + { + bool ok; + int32_t version = match.captured("versionNum").toULong(&ok); + return ok ? version : -1; + } + return -1; +} diff --git a/Desktop/gui/remotesettings.h b/Desktop/gui/remotesettings.h new file mode 100644 index 0000000000..194863dd84 --- /dev/null +++ b/Desktop/gui/remotesettings.h @@ -0,0 +1,43 @@ +#ifndef REMOTESETTINGS_H +#define REMOTESETTINGS_H + +#include +#include + +class RemoteSettings : public QObject +{ + Q_OBJECT +public: + explicit RemoteSettings(QObject *parent = nullptr); + + void processRemoteSettings(); + bool patchPresent(const QString module, const QString analysis, QString& path); + +public slots: + void remoteChanged(QString remoteURL); + +signals: + void remoteSettingsProcessed(QString results); + +private slots: +// void downloadFinished(QNetworkReply* reply); + void sslErrors(const QList &errors); + +private: + void processTasks(); + void parse(const QString& settings); + int32_t getVersion(const QString& settings); + QNetworkReply* download(const QString& path); + bool saveToDisk(const QString& path, QNetworkReply* data); + + + QNetworkAccessManager _networkManager; + int32_t _version = -1; + QList _patches; + + const QString configurationFilename = "jasp.conf"; + static QRegularExpression versionRE; + +}; + +#endif // REMOTESETTINGS_H diff --git a/Desktop/utilities/settings.cpp b/Desktop/utilities/settings.cpp index 0aff7c3e1b..ceb29fb21c 100644 --- a/Desktop/utilities/settings.cpp +++ b/Desktop/utilities/settings.cpp @@ -89,8 +89,9 @@ const Settings::Setting Settings::Values[] = { {"showReports", false }, {"showRSyntax", false }, {"showAllROptions", false }, - {"showRSyntaxInResults", false }, - {"guiQtTextRender", true } + {"guiQtTextRender", true }, + {"showRSyntaxInResults", false }, + {"remoteSettingsURL", "" } }; QVariant Settings::value(Settings::Type key) diff --git a/Desktop/utilities/settings.h b/Desktop/utilities/settings.h index 9aa3fa0f8c..3f9de42c98 100644 --- a/Desktop/utilities/settings.h +++ b/Desktop/utilities/settings.h @@ -73,7 +73,8 @@ class Settings { REPORT_SHOW, SHOW_RSYNTAX, SHOW_ALL_R_OPTIONS, - SHOW_RSYNTAX_IN_RESULTS + SHOW_RSYNTAX_IN_RESULTS, + REMOTE_SETTINGS_URL }; static QVariant value(Settings::Type key); From 5f2fb7352432cd4c0a2078bf5d8487ae3d7e382d Mon Sep 17 00:00:00 2001 From: Rens Dofferhoff Date: Tue, 4 Apr 2023 13:13:42 +0200 Subject: [PATCH 13/38] progress --- Desktop/gui/preferencesmodel.cpp.in | 3 + Desktop/gui/remotesettings.cpp | 99 +++++++++++++++++++++++------ Desktop/gui/remotesettings.h | 13 ++-- Desktop/mainwindow.cpp | 4 ++ Desktop/mainwindow.h | 2 + 5 files changed, 93 insertions(+), 28 deletions(-) diff --git a/Desktop/gui/preferencesmodel.cpp.in b/Desktop/gui/preferencesmodel.cpp.in index 3f7b89a8ff..f9d3081359 100644 --- a/Desktop/gui/preferencesmodel.cpp.in +++ b/Desktop/gui/preferencesmodel.cpp.in @@ -127,6 +127,7 @@ GET_PREF_FUNC_BOOL( reportingMode, Settings::REPORT_SHOW ) GET_PREF_FUNC_BOOL( showRSyntax, Settings::SHOW_RSYNTAX ) GET_PREF_FUNC_BOOL( showAllROptions, Settings::SHOW_ALL_R_OPTIONS ) GET_PREF_FUNC_BOOL( showRSyntaxInResults, Settings::SHOW_RSYNTAX_IN_RESULTS ) +GET_PREF_FUNC_STR( remoteSettingsURL, Settings::REMOTE_SETTINGS_URL ) int PreferencesModel::maxEngines() const { @@ -307,6 +308,8 @@ SET_PREF_FUNCTION( bool, setReportingMode, reportingMode, reportingMode SET_PREF_FUNCTION_EMIT_NO_ARG( bool, setShowRSyntax, showRSyntax, showRSyntaxChanged, Settings::SHOW_RSYNTAX ) SET_PREF_FUNCTION_EMIT_NO_ARG( bool, setShowAllROptions, showAllROptions, showAllROptionsChanged, Settings::SHOW_ALL_R_OPTIONS ) SET_PREF_FUNCTION( bool, setShowRSyntaxInResults, showRSyntaxInResults, showRSyntaxInResultsChanged, Settings::SHOW_RSYNTAX_IN_RESULTS ) +SET_PREF_FUNCTION( QString, setRemoteSettingsURL, remoteSettingsURL, remoteSettingsURLChanged, Settings::REMOTE_SETTINGS_URL ) + void PreferencesModel::setGithubPatCustom(QString newPat) { diff --git a/Desktop/gui/remotesettings.cpp b/Desktop/gui/remotesettings.cpp index ee32b00d13..c9467c1e49 100644 --- a/Desktop/gui/remotesettings.cpp +++ b/Desktop/gui/remotesettings.cpp @@ -7,54 +7,113 @@ #include #include #include +#include RemoteSettings::RemoteSettings(QObject *parent) : QObject{parent} { //connect(&_networkManager, &QNetworkAccessManager::finished, this, &RemoteSettings::downloadFinished); - versionRE.setPattern("Version:\\s*(?\\d+)"); +// versionRE.setPattern("Version:\\s*(?\\d+)"); } void RemoteSettings::processRemoteSettings() { //read & parse local QString confPath = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); - QFile local(confPath + configurationFilename); - if (!local.open(QIODevice::ReadWrite | QIODevice::Text)) + QDir confDir; + confDir.mkpath(confPath); + + QString settingsPath = confPath + "/" + configurationFilename; + QFile localSettingsFile(settingsPath); + if (!localSettingsFile.open(QIODevice::ReadWrite | QIODevice::Text)) { - Log::log() << "Could not open local configuration file!"; + Log::log() << "!!!Could not open local configuration file " << settingsPath.toStdString() << ": " << localSettingsFile.errorString().toStdString() << std::endl; return; } - QTextStream in(&local); + QTextStream in(&localSettingsFile); parse(in.readAll()); + localSettingsFile.close(); + + + //read,parse & save remote settings + auto conn = std::make_shared(); + *conn = connect(&_networkManager, &QNetworkAccessManager::finished, this, [&, this, settingsPath, conn](QNetworkReply* reply) { + QObject::disconnect(*conn); + reply->deleteLater(); + + if(reply->error()) + { + Log::log() << "!!!Error fetching remote settings file " << reply->request().url().toString().toStdString() << " : " << reply->errorString().toStdString() << std::endl; + emit this->remoteSettingsProcessed("FAIL"); + return; + } + QString payload = reply->readAll(); + parse(payload); + QFile localSettingsFile(settingsPath); + if (!localSettingsFile.open(QIODevice::ReadWrite | QIODevice::Text)) + { + Log::log() << "!!!Could not open local configuration file " << settingsPath.toStdString() << ": " << localSettingsFile.errorString().toStdString() << std::endl; + emit this->remoteSettingsProcessed("FAIL"); + return; + } + localSettingsFile.write(payload.toUtf8()); + localSettingsFile.close(); - //read & parse remote + Log::log() << "!!!Updated local copy of remote settings" << std::endl; + emit this->remoteSettingsProcessed("OK"); + }); + + //make the request + QNetworkRequest request(Settings::value(Settings::REMOTE_SETTINGS_URL).toString()); + QNetworkReply* reply = _networkManager.get(request); + connect(reply, &QNetworkReply::sslErrors, this, &RemoteSettings::sslErrors); } -void RemoteSettings::parse(const QString &settings) +void RemoteSettings::remoteChanged(QString remoteURL) { - _version = getVersion(settings); + processRemoteSettings(); +} +void RemoteSettings::parse(const QString &settings) +{ _patches.clear(); QStringList lines = settings.split("\n"); for(auto& line : lines) { - auto match = versionRE.match(line); - if(!match.hasMatch()) - _patches.append(line); + _patches.insert(line); } } -int32_t RemoteSettings::getVersion(const QString& settings) +void RemoteSettings::sslErrors(const QList &errors) { - auto match = versionRE.match(settings); - if(match.hasCaptured("versionNum")) - { - bool ok; - int32_t version = match.captured("versionNum").toULong(&ok); - return ok ? version : -1; - } - return -1; + for (const QSslError &error : errors) + Log::log() << "!!!Error fetching remote settings:" << error.errorString().toStdString() << std::endl; } + +//void RemoteSettings::parse(const QString &settings) +//{ +// _version = getVersion(settings); + +// _patches.clear(); +// QStringList lines = settings.split("\n"); +// for(auto& line : lines) +// { +// auto match = versionRE.match(line); +// if(!match.hasMatch()) +// _patches.append(line); +// } +//} + +//int32_t RemoteSettings::getVersion(const QString& settings) +//{ +// auto match = versionRE.match(settings); +// if(match.hasCaptured("versionNum")) +// { +// bool ok; +// int32_t version = match.captured("versionNum").toULong(&ok); +// return ok ? version : -1; +// } +// return -1; +//} diff --git a/Desktop/gui/remotesettings.h b/Desktop/gui/remotesettings.h index 194863dd84..838579a4d9 100644 --- a/Desktop/gui/remotesettings.h +++ b/Desktop/gui/remotesettings.h @@ -26,17 +26,14 @@ private slots: private: void processTasks(); void parse(const QString& settings); - int32_t getVersion(const QString& settings); - QNetworkReply* download(const QString& path); - bool saveToDisk(const QString& path, QNetworkReply* data); - +// int32_t getVersion(const QString& settings); QNetworkAccessManager _networkManager; - int32_t _version = -1; - QList _patches; +// int32_t _version = -1; + QSet _patches; - const QString configurationFilename = "jasp.conf"; - static QRegularExpression versionRE; + const QString configurationFilename = "remote_settings.conf"; +// static QRegularExpression versionRE; }; diff --git a/Desktop/mainwindow.cpp b/Desktop/mainwindow.cpp index 6d5ee38d6f..bc85473c2c 100644 --- a/Desktop/mainwindow.cpp +++ b/Desktop/mainwindow.cpp @@ -148,6 +148,7 @@ MainWindow::MainWindow(QApplication * application) : QObject(application), _appl _resultMenuModel = new ResultMenuModel(this); _plotEditorModel = new PlotEditorModel(); _columnTypesModel = new ColumnTypesModel(this); + _remoteSettings = new RemoteSettings(this); #ifdef WIN32 _windowsWorkaroundCPs = new CodePagesWindows(this); @@ -159,6 +160,8 @@ MainWindow::MainWindow(QApplication * application) : QObject(application), _appl makeConnections(); + _remoteSettings->processRemoteSettings(); + qmlRegisterUncreatableType ("JASP", 1, 0 ,"JASP", "Impossible to create JASP Object" ); //This is here to keep JASP.enum short I guess? qmlRegisterUncreatableType ("JASP", 1, 0, "MessageForwarder", "You can't touch this" ); @@ -372,6 +375,7 @@ void MainWindow::makeConnections() connect(_preferences, &PreferencesModel::missingValuesChanged, _package, &DataSetPackage::emptyValuesChangedHandler ); connect(_preferences, &PreferencesModel::dataLabelNAChanged, _package, &DataSetPackage::refresh, Qt::QueuedConnection); connect(_preferences, &PreferencesModel::dataAutoSynchronizationChanged, _package, &DataSetPackage::synchingExternallyChanged ); + connect(_preferences, &PreferencesModel::remoteSettingsURLChanged, _remoteSettings, &RemoteSettings::remoteChanged ); connect(_preferences, &PreferencesModel::plotBackgroundChanged, this, &MainWindow::setImageBackgroundHandler ); connect(_preferences, &PreferencesModel::plotPPIChanged, this, &MainWindow::plotPPIChangedHandler ); connect(_preferences, &PreferencesModel::dataAutoSynchronizationChanged, _fileMenu, &FileMenu::dataAutoSynchronizationChanged ); diff --git a/Desktop/mainwindow.h b/Desktop/mainwindow.h index 1a3f83339b..60528db358 100644 --- a/Desktop/mainwindow.h +++ b/Desktop/mainwindow.h @@ -40,6 +40,7 @@ #include "gui/aboutmodel.h" #include "gui/columntypesmodel.h" #include "gui/preferencesmodel.h" +#include "gui/remotesettings.h" #include "modules/dynamicmodule.h" #include "modules/ribbonbutton.h" #include "modules/ribbonmodelfiltered.h" @@ -281,6 +282,7 @@ private slots: Upgrader * _upgrader = nullptr; Reporter * _reporter = nullptr; CodePagesWindows * _windowsWorkaroundCPs = nullptr; + RemoteSettings * _remoteSettings = nullptr; QSettings _settings; From bef03ee3514ad17f3e4e3aeb13f7ccf607eb895a Mon Sep 17 00:00:00 2001 From: Rens Dofferhoff Date: Wed, 5 Apr 2023 12:02:31 +0200 Subject: [PATCH 14/38] progress --- Desktop/gui/jaspconfiguration.cpp | 152 ++++++++++++++++++++++++++++++ Desktop/gui/jaspconfiguration.h | 49 ++++++++++ Desktop/gui/remotesettings.cpp | 119 ----------------------- Desktop/gui/remotesettings.h | 40 -------- Desktop/mainwindow.cpp | 8 +- Desktop/mainwindow.h | 2 +- 6 files changed, 207 insertions(+), 163 deletions(-) create mode 100644 Desktop/gui/jaspconfiguration.cpp create mode 100644 Desktop/gui/jaspconfiguration.h delete mode 100644 Desktop/gui/remotesettings.cpp delete mode 100644 Desktop/gui/remotesettings.h diff --git a/Desktop/gui/jaspconfiguration.cpp b/Desktop/gui/jaspconfiguration.cpp new file mode 100644 index 0000000000..c65abc70fa --- /dev/null +++ b/Desktop/gui/jaspconfiguration.cpp @@ -0,0 +1,152 @@ +#include "jaspconfiguration.h" +#include "utilities/settings.h" +#include "log.h" +#include +#include +#include +#include +#include +#include +#include + + +JASPConfiguration::JASPConfiguration(QObject *parent) + : QObject{parent} +{ + +} + +bool JASPConfiguration::exists(const QString &constant, const QString &module, const QString &analysis) +{ + +} + +QVariant JASPConfiguration::get(const QString &constant, QVariant defaultValue, const QString &module, const QString &analysis) +{ + +} + +bool JASPConfiguration::isSet(const QString &module, const QString &analysis, const QString &optionName) +{ + +} + +QString &JASPConfiguration::getAnalysisOptionValue(const QString &module, const QString &analysis, const QString &optionName, const QString &defaultValue) +{ + +} + +void JASPConfiguration::processSettings() +{ + //read & parse local + QString confPath = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); + QDir confDir; + confDir.mkpath(confPath); + + QString settingsPath = confPath + "/" + configurationFilename; + QFile localSettingsFile(settingsPath); + if (!localSettingsFile.open(QIODevice::ReadWrite | QIODevice::Text)) + { + Log::log() << "!!!Could not open local configuration file " << settingsPath.toStdString() << ": " << localSettingsFile.errorString().toStdString() << std::endl; + return; + } + QTextStream in(&localSettingsFile); + parse(in.readAll()); + localSettingsFile.close(); + + + //read, parse & save remote settings + if(Settings::value(Settings::REMOTE_SETTINGS_URL).toString() != "") + { + auto conn = std::make_shared(); + *conn = connect(&_networkManager, &QNetworkAccessManager::finished, this, [&, this, settingsPath, conn](QNetworkReply* reply) { + QObject::disconnect(*conn); + reply->deleteLater(); + + if(reply->error()) + { + Log::log() << "!!!Error fetching remote settings file " << reply->request().url().toString().toStdString() << " : " << reply->errorString().toStdString() << std::endl; + emit this->settingsProcessed("FAIL"); + return; + } + QString payload = reply->readAll(); + parse(payload); + + QFile localSettingsFile(settingsPath); + if (!localSettingsFile.open(QIODevice::ReadWrite | QIODevice::Text)) + { + Log::log() << "!!!Could not open local configuration file " << settingsPath.toStdString() << ": " << localSettingsFile.errorString().toStdString() << std::endl; + emit this->settingsProcessed("FAIL"); + return; + } + localSettingsFile.write(payload.toUtf8()); + localSettingsFile.close(); + + Log::log() << "!!!Updated local copy of remote settings" << std::endl; + emit this->settingsProcessed("OK"); + }); + + //make the request + QNetworkRequest request(Settings::value(Settings::REMOTE_SETTINGS_URL).toString()); + QNetworkReply* reply = _networkManager.get(request); + connect(reply, &QNetworkReply::sslErrors, this, &JASPConfiguration::sslErrors); + } + else + emit settingsProcessed("OK"); +} + +void JASPConfiguration::remoteChanged(QString remoteURL) +{ + processSettings(); +} + +void JASPConfiguration::parse(const QString &settings) +{ + _patches.clear(); + QStringList lines = settings.split("\n"); + for(auto& line : lines) + { + _patches.insert(line); + } +} + +void JASPConfiguration::sslErrors(const QList &errors) +{ + for (const QSslError &error : errors) + Log::log() << "!!!Error fetching remote settings:" << error.errorString().toStdString() << std::endl; +} + +//void RemoteSettings::parse(const QString &settings) +//{ +// _version = getVersion(settings); + +// _patches.clear(); +// QStringList lines = settings.split("\n"); +// for(auto& line : lines) +// { +// auto match = versionRE.match(line); +// if(!match.hasMatch()) +// _patches.append(line); +// } +//} + +bool JASPConfiguration::getVersion(const QString& settings) +{ + static QRegularExpression versionRE("JASP_Version:\\s*(?\\w+)\\s*$", QRegularExpression::MultilineOption); + auto match = versionRE.match(settings); + if(match.hasCaptured("versionNum")) + { + try + { + jaspVersion = Version(match.captured("versionNum").toStdString()); + return true; + } + catch (std::runtime_error& e) + { + Log::log() << "Could not parse JASP Version number in configuration file: " << e.what() << std::endl; + return false; + } + } + Log::log() << "No JASP Version number present in configuration file" << std::endl; + return false; +} diff --git a/Desktop/gui/jaspconfiguration.h b/Desktop/gui/jaspconfiguration.h new file mode 100644 index 0000000000..1695433714 --- /dev/null +++ b/Desktop/gui/jaspconfiguration.h @@ -0,0 +1,49 @@ +#ifndef JASPSETTINGS_H +#define JASPSETTINGS_H + +#include +#include +#include +#include "qregularexpression.h" +#include "version.h" + +class JASPConfiguration : public QObject +{ + Q_OBJECT +public: + explicit JASPConfiguration(QObject *parent = nullptr); + + //QML programming constants interface + Q_INVOKABLE bool exists(const QString& constant, const QString& module = "", const QString& analysis = ""); + Q_INVOKABLE QVariant get(const QString& constant, QVariant defaultValue = QVariant(), const QString& module = "", const QString& analysis = ""); + + //Predefined analysis options interface + bool isSet(const QString& module, const QString& analysis, const QString& optionName); + QString& getAnalysisOptionValue(const QString& module, const QString& analysis, const QString& optionName, const QString& defaultValue = ""); + + //read and parse local and remote settings + void processSettings(); + +public slots: + void remoteChanged(QString remoteURL); + +signals: + void settingsProcessed(QString results); + +private slots: + void sslErrors(const QList &errors); + +private: + void processTasks(); + void parse(const QString& settings); + bool getVersion(const QString& settings); + + QNetworkAccessManager _networkManager; + Version jaspVersion; + QSet _patches; + + const QString configurationFilename = "userConfiguration.conf"; + +}; + +#endif // JASPSETTINGS_H diff --git a/Desktop/gui/remotesettings.cpp b/Desktop/gui/remotesettings.cpp deleted file mode 100644 index c9467c1e49..0000000000 --- a/Desktop/gui/remotesettings.cpp +++ /dev/null @@ -1,119 +0,0 @@ -#include "remotesettings.h" -#include "utilities/settings.h" -#include "log.h" -#include -#include -#include -#include -#include -#include -#include - - -RemoteSettings::RemoteSettings(QObject *parent) - : QObject{parent} -{ - //connect(&_networkManager, &QNetworkAccessManager::finished, this, &RemoteSettings::downloadFinished); -// versionRE.setPattern("Version:\\s*(?\\d+)"); -} - -void RemoteSettings::processRemoteSettings() -{ - //read & parse local - QString confPath = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); - QDir confDir; - confDir.mkpath(confPath); - - QString settingsPath = confPath + "/" + configurationFilename; - QFile localSettingsFile(settingsPath); - if (!localSettingsFile.open(QIODevice::ReadWrite | QIODevice::Text)) - { - Log::log() << "!!!Could not open local configuration file " << settingsPath.toStdString() << ": " << localSettingsFile.errorString().toStdString() << std::endl; - return; - } - QTextStream in(&localSettingsFile); - parse(in.readAll()); - localSettingsFile.close(); - - - //read,parse & save remote settings - auto conn = std::make_shared(); - *conn = connect(&_networkManager, &QNetworkAccessManager::finished, this, [&, this, settingsPath, conn](QNetworkReply* reply) { - QObject::disconnect(*conn); - reply->deleteLater(); - - if(reply->error()) - { - Log::log() << "!!!Error fetching remote settings file " << reply->request().url().toString().toStdString() << " : " << reply->errorString().toStdString() << std::endl; - emit this->remoteSettingsProcessed("FAIL"); - return; - } - QString payload = reply->readAll(); - parse(payload); - - QFile localSettingsFile(settingsPath); - if (!localSettingsFile.open(QIODevice::ReadWrite | QIODevice::Text)) - { - Log::log() << "!!!Could not open local configuration file " << settingsPath.toStdString() << ": " << localSettingsFile.errorString().toStdString() << std::endl; - emit this->remoteSettingsProcessed("FAIL"); - return; - } - localSettingsFile.write(payload.toUtf8()); - localSettingsFile.close(); - - Log::log() << "!!!Updated local copy of remote settings" << std::endl; - emit this->remoteSettingsProcessed("OK"); - }); - - //make the request - QNetworkRequest request(Settings::value(Settings::REMOTE_SETTINGS_URL).toString()); - QNetworkReply* reply = _networkManager.get(request); - connect(reply, &QNetworkReply::sslErrors, this, &RemoteSettings::sslErrors); -} - -void RemoteSettings::remoteChanged(QString remoteURL) -{ - processRemoteSettings(); -} - -void RemoteSettings::parse(const QString &settings) -{ - _patches.clear(); - QStringList lines = settings.split("\n"); - for(auto& line : lines) - { - _patches.insert(line); - } -} - -void RemoteSettings::sslErrors(const QList &errors) -{ - for (const QSslError &error : errors) - Log::log() << "!!!Error fetching remote settings:" << error.errorString().toStdString() << std::endl; -} - -//void RemoteSettings::parse(const QString &settings) -//{ -// _version = getVersion(settings); - -// _patches.clear(); -// QStringList lines = settings.split("\n"); -// for(auto& line : lines) -// { -// auto match = versionRE.match(line); -// if(!match.hasMatch()) -// _patches.append(line); -// } -//} - -//int32_t RemoteSettings::getVersion(const QString& settings) -//{ -// auto match = versionRE.match(settings); -// if(match.hasCaptured("versionNum")) -// { -// bool ok; -// int32_t version = match.captured("versionNum").toULong(&ok); -// return ok ? version : -1; -// } -// return -1; -//} diff --git a/Desktop/gui/remotesettings.h b/Desktop/gui/remotesettings.h deleted file mode 100644 index 838579a4d9..0000000000 --- a/Desktop/gui/remotesettings.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef REMOTESETTINGS_H -#define REMOTESETTINGS_H - -#include -#include - -class RemoteSettings : public QObject -{ - Q_OBJECT -public: - explicit RemoteSettings(QObject *parent = nullptr); - - void processRemoteSettings(); - bool patchPresent(const QString module, const QString analysis, QString& path); - -public slots: - void remoteChanged(QString remoteURL); - -signals: - void remoteSettingsProcessed(QString results); - -private slots: -// void downloadFinished(QNetworkReply* reply); - void sslErrors(const QList &errors); - -private: - void processTasks(); - void parse(const QString& settings); -// int32_t getVersion(const QString& settings); - - QNetworkAccessManager _networkManager; -// int32_t _version = -1; - QSet _patches; - - const QString configurationFilename = "remote_settings.conf"; -// static QRegularExpression versionRE; - -}; - -#endif // REMOTESETTINGS_H diff --git a/Desktop/mainwindow.cpp b/Desktop/mainwindow.cpp index bc85473c2c..c236db1c59 100644 --- a/Desktop/mainwindow.cpp +++ b/Desktop/mainwindow.cpp @@ -148,7 +148,7 @@ MainWindow::MainWindow(QApplication * application) : QObject(application), _appl _resultMenuModel = new ResultMenuModel(this); _plotEditorModel = new PlotEditorModel(); _columnTypesModel = new ColumnTypesModel(this); - _remoteSettings = new RemoteSettings(this); + _remoteSettings = new JASPSettings(this); #ifdef WIN32 _windowsWorkaroundCPs = new CodePagesWindows(this); @@ -160,7 +160,7 @@ MainWindow::MainWindow(QApplication * application) : QObject(application), _appl makeConnections(); - _remoteSettings->processRemoteSettings(); + _remoteSettings->processSettings(); qmlRegisterUncreatableType ("JASP", 1, 0 ,"JASP", "Impossible to create JASP Object" ); //This is here to keep JASP.enum short I guess? qmlRegisterUncreatableType ("JASP", 1, 0, "MessageForwarder", "You can't touch this" ); @@ -375,7 +375,9 @@ void MainWindow::makeConnections() connect(_preferences, &PreferencesModel::missingValuesChanged, _package, &DataSetPackage::emptyValuesChangedHandler ); connect(_preferences, &PreferencesModel::dataLabelNAChanged, _package, &DataSetPackage::refresh, Qt::QueuedConnection); connect(_preferences, &PreferencesModel::dataAutoSynchronizationChanged, _package, &DataSetPackage::synchingExternallyChanged ); - connect(_preferences, &PreferencesModel::remoteSettingsURLChanged, _remoteSettings, &RemoteSettings::remoteChanged ); + + + connect(_preferences, &PreferencesModel::remoteSettingsURLChanged, _remoteSettings, &JASPConfiguration::remoteChanged ); connect(_preferences, &PreferencesModel::plotBackgroundChanged, this, &MainWindow::setImageBackgroundHandler ); connect(_preferences, &PreferencesModel::plotPPIChanged, this, &MainWindow::plotPPIChangedHandler ); connect(_preferences, &PreferencesModel::dataAutoSynchronizationChanged, _fileMenu, &FileMenu::dataAutoSynchronizationChanged ); diff --git a/Desktop/mainwindow.h b/Desktop/mainwindow.h index 60528db358..61e1a468f6 100644 --- a/Desktop/mainwindow.h +++ b/Desktop/mainwindow.h @@ -282,7 +282,7 @@ private slots: Upgrader * _upgrader = nullptr; Reporter * _reporter = nullptr; CodePagesWindows * _windowsWorkaroundCPs = nullptr; - RemoteSettings * _remoteSettings = nullptr; + JASPSettings * _remoteSettings = nullptr; QSettings _settings; From a6a1bf208ee572c912d0876c6da93b076ec0a5e8 Mon Sep 17 00:00:00 2001 From: Rens Dofferhoff Date: Wed, 5 Apr 2023 21:03:53 +0200 Subject: [PATCH 15/38] progress --- .../JASP/Widgets/FileMenu/PrefsAdvanced.qml | 10 +- Desktop/gui/jaspconfiguration.cpp | 175 +++++++++--------- Desktop/gui/jaspconfiguration.h | 29 ++- Desktop/gui/preferencesmodel.cpp.in | 4 +- Desktop/gui/preferencesmodel.h | 11 +- Desktop/mainwindow.cpp | 7 +- Desktop/mainwindow.h | 4 +- Desktop/utilities/settings.h | 2 +- 8 files changed, 128 insertions(+), 114 deletions(-) diff --git a/Desktop/components/JASP/Widgets/FileMenu/PrefsAdvanced.qml b/Desktop/components/JASP/Widgets/FileMenu/PrefsAdvanced.qml index 4670ceddc5..3481f02255 100644 --- a/Desktop/components/JASP/Widgets/FileMenu/PrefsAdvanced.qml +++ b/Desktop/components/JASP/Widgets/FileMenu/PrefsAdvanced.qml @@ -205,14 +205,14 @@ ScrollView toolTip: qsTr("This will erase the 'renv' and 'Modules' folders in the appdata.") onClicked: mainWindow.clearModulesFoldersUser(); - KeyNavigation.tab: remoteSettingsURL + KeyNavigation.tab: remoteConfURL activeFocusOnTab: true } Item { - id: remoteSettingsItem + id: remoteConfItem width: parent.width height: cranRepoUrl.height @@ -231,10 +231,10 @@ ScrollView PrefsTextInput { - id: remoteSettingsURL + id: remoteConfURL - text: preferencesModel.remoteSettingsURL - onEditingFinished: preferencesModel.remoteSettingsURL = text + text: preferencesModel.remoteConfigurationURL + onEditingFinished: preferencesModel.remoteConfigurationURL = text height: browseDeveloperFolderButton.height anchors diff --git a/Desktop/gui/jaspconfiguration.cpp b/Desktop/gui/jaspconfiguration.cpp index c65abc70fa..23056ea048 100644 --- a/Desktop/gui/jaspconfiguration.cpp +++ b/Desktop/gui/jaspconfiguration.cpp @@ -13,6 +13,10 @@ JASPConfiguration::JASPConfiguration(QObject *parent) : QObject{parent} { + versionRE.setPattern(versionPattern); + versionRE.setPatternOptions(QRegularExpression::MultilineOption); + + keyValueRE.setPattern(keyValuePattern); } @@ -36,117 +40,120 @@ QString &JASPConfiguration::getAnalysisOptionValue(const QString &module, const } -void JASPConfiguration::processSettings() +void JASPConfiguration::processConfiguration() { - //read & parse local - QString confPath = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); - QDir confDir; - confDir.mkpath(confPath); - - QString settingsPath = confPath + "/" + configurationFilename; - QFile localSettingsFile(settingsPath); - if (!localSettingsFile.open(QIODevice::ReadWrite | QIODevice::Text)) - { - Log::log() << "!!!Could not open local configuration file " << settingsPath.toStdString() << ": " << localSettingsFile.errorString().toStdString() << std::endl; - return; - } - QTextStream in(&localSettingsFile); - parse(in.readAll()); - localSettingsFile.close(); + bool localOK = processLocal(); - - //read, parse & save remote settings - if(Settings::value(Settings::REMOTE_SETTINGS_URL).toString() != "") + //read, parse & save remote settings + if(Settings::value(Settings::REMOTE_CONFIGURATION_URL).toString() != "") { auto conn = std::make_shared(); - *conn = connect(&_networkManager, &QNetworkAccessManager::finished, this, [&, this, settingsPath, conn](QNetworkReply* reply) { - QObject::disconnect(*conn); - reply->deleteLater(); - - if(reply->error()) - { - Log::log() << "!!!Error fetching remote settings file " << reply->request().url().toString().toStdString() << " : " << reply->errorString().toStdString() << std::endl; - emit this->settingsProcessed("FAIL"); - return; - } - QString payload = reply->readAll(); - parse(payload); - - QFile localSettingsFile(settingsPath); - if (!localSettingsFile.open(QIODevice::ReadWrite | QIODevice::Text)) - { - Log::log() << "!!!Could not open local configuration file " << settingsPath.toStdString() << ": " << localSettingsFile.errorString().toStdString() << std::endl; - emit this->settingsProcessed("FAIL"); - return; - } - localSettingsFile.write(payload.toUtf8()); - localSettingsFile.close(); - - Log::log() << "!!!Updated local copy of remote settings" << std::endl; - emit this->settingsProcessed("OK"); + *conn = connect(&_networkManager, &QNetworkAccessManager::finished, this, [=, this](QNetworkReply* reply) { + QObject::disconnect(*conn); + reply->deleteLater(); + + try + { + if(reply->error()) + throw std::runtime_error("Error fetching remote configuration file " + reply->request().url().toString().toStdString() + " : " + reply->errorString().toStdString()); + QByteArray payload = reply->readAll(); + parse(payload); + + auto localConfFile = getLocalConfFile(); + localConfFile->write(payload); + localConfFile->close(); + Log::log() << "!!!Updated local copy of remote configuration" << std::endl; + emit this->configurationProcessed("OK"); + } + catch (std::runtime_error& e) + { + Log::log() << "!!!Failed to process remote configuration: " << e.what() << std::endl; + if(!localOK) + emit this->configurationProcessed("FAIL"); + else + emit this->configurationProcessed("OK"); + return; + } }); //make the request - QNetworkRequest request(Settings::value(Settings::REMOTE_SETTINGS_URL).toString()); + QNetworkRequest request(Settings::value(Settings::REMOTE_CONFIGURATION_URL).toString()); QNetworkReply* reply = _networkManager.get(request); connect(reply, &QNetworkReply::sslErrors, this, &JASPConfiguration::sslErrors); } else - emit settingsProcessed("OK"); + emit configurationProcessed("OK"); +} + +bool JASPConfiguration::processLocal() +{ + try + { + auto localConfFile = getLocalConfFile(); + QTextStream in(localConfFile.get()); + parse(in.readAll()); + } + catch(std::runtime_error& e) + { + Log::log() << "!!!Could not parse local configuration: " << e.what() << std::endl; + return false; + } + return true; } void JASPConfiguration::remoteChanged(QString remoteURL) { - processSettings(); + processConfiguration(); } -void JASPConfiguration::parse(const QString &settings) +void JASPConfiguration::parse(const QString &conf) { + getVersion(conf); _patches.clear(); - QStringList lines = settings.split("\n"); + QStringList lines = conf.split("\n"); for(auto& line : lines) { _patches.insert(line); } } -void JASPConfiguration::sslErrors(const QList &errors) +void JASPConfiguration::getVersion(const QString& conf) { - for (const QSslError &error : errors) - Log::log() << "!!!Error fetching remote settings:" << error.errorString().toStdString() << std::endl; + + auto match = versionRE.match(conf); + if(match.hasCaptured("versionNum")) + { + try + { + jaspVersion = Version(match.captured("versionNum").toStdString()); + return; + } + catch (std::runtime_error& e) + { + throw std::runtime_error("Could not parse JASP Version number in configuration file: " + std::string(e.what())); + } + } + throw std::runtime_error("No JASP Version number present in configuration file"); +} + +std::shared_ptr JASPConfiguration::getLocalConfFile() { + QString confPath = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); + QDir confDir; + if(!confDir.mkpath(confPath)) + throw std::runtime_error("Could not Access app configuration path"); + + QString configurationFilePath = confPath + "/" + configurationFilename; + std::shared_ptr localConfFile = std::make_shared(configurationFilePath); + if (!localConfFile->open(QIODevice::ReadWrite | QIODevice::Text)) + throw std::runtime_error("Could not open local configuration file " + configurationFilePath.toStdString() + ": " + localConfFile->errorString().toStdString()); + return localConfFile; } -//void RemoteSettings::parse(const QString &settings) -//{ -// _version = getVersion(settings); - -// _patches.clear(); -// QStringList lines = settings.split("\n"); -// for(auto& line : lines) -// { -// auto match = versionRE.match(line); -// if(!match.hasMatch()) -// _patches.append(line); -// } -//} - -bool JASPConfiguration::getVersion(const QString& settings) +void JASPConfiguration::sslErrors(const QList &errors) { - static QRegularExpression versionRE("JASP_Version:\\s*(?\\w+)\\s*$", QRegularExpression::MultilineOption); - auto match = versionRE.match(settings); - if(match.hasCaptured("versionNum")) - { - try - { - jaspVersion = Version(match.captured("versionNum").toStdString()); - return true; - } - catch (std::runtime_error& e) - { - Log::log() << "Could not parse JASP Version number in configuration file: " << e.what() << std::endl; - return false; - } - } - Log::log() << "No JASP Version number present in configuration file" << std::endl; - return false; + for (const QSslError &error : errors) + Log::log() << "Error fetching remote settings:" << error.errorString().toStdString() << std::endl; } + + + diff --git a/Desktop/gui/jaspconfiguration.h b/Desktop/gui/jaspconfiguration.h index 1695433714..975843e13b 100644 --- a/Desktop/gui/jaspconfiguration.h +++ b/Desktop/gui/jaspconfiguration.h @@ -1,10 +1,11 @@ -#ifndef JASPSETTINGS_H -#define JASPSETTINGS_H +#ifndef JASPCONFIGURATION_H +#define JASPCONFIGURATION_H #include #include #include -#include "qregularexpression.h" +#include +#include #include "version.h" class JASPConfiguration : public QObject @@ -21,22 +22,25 @@ class JASPConfiguration : public QObject bool isSet(const QString& module, const QString& analysis, const QString& optionName); QString& getAnalysisOptionValue(const QString& module, const QString& analysis, const QString& optionName, const QString& defaultValue = ""); - //read and parse local and remote settings - void processSettings(); + //read and parse local and remote configuration + void processConfiguration(); public slots: void remoteChanged(QString remoteURL); signals: - void settingsProcessed(QString results); + void configurationProcessed(QString results); private slots: void sslErrors(const QList &errors); private: + bool processLocal(); + void processTasks(); - void parse(const QString& settings); - bool getVersion(const QString& settings); + void parse(const QString& conf); + void getVersion(const QString& conf); + std::shared_ptr getLocalConfFile(); QNetworkAccessManager _networkManager; Version jaspVersion; @@ -44,6 +48,13 @@ private slots: const QString configurationFilename = "userConfiguration.conf"; + + const QString versionPattern = "JASP_Version:\\s*(?[\\S]+)\\s*$"; + const QString keyValuePattern = "\\s*(?[\\S+)\\s*=\\s*(?[\\S+)\\s*$"; + + QRegularExpression versionRE; + QRegularExpression keyValueRE; + }; -#endif // JASPSETTINGS_H +#endif // JASPCONFIGURATION_H diff --git a/Desktop/gui/preferencesmodel.cpp.in b/Desktop/gui/preferencesmodel.cpp.in index f9d3081359..b782446dd6 100644 --- a/Desktop/gui/preferencesmodel.cpp.in +++ b/Desktop/gui/preferencesmodel.cpp.in @@ -127,7 +127,7 @@ GET_PREF_FUNC_BOOL( reportingMode, Settings::REPORT_SHOW ) GET_PREF_FUNC_BOOL( showRSyntax, Settings::SHOW_RSYNTAX ) GET_PREF_FUNC_BOOL( showAllROptions, Settings::SHOW_ALL_R_OPTIONS ) GET_PREF_FUNC_BOOL( showRSyntaxInResults, Settings::SHOW_RSYNTAX_IN_RESULTS ) -GET_PREF_FUNC_STR( remoteSettingsURL, Settings::REMOTE_SETTINGS_URL ) +GET_PREF_FUNC_STR( remoteConfigurationURL, Settings::REMOTE_CONFIGURATION_URL ) int PreferencesModel::maxEngines() const { @@ -308,7 +308,7 @@ SET_PREF_FUNCTION( bool, setReportingMode, reportingMode, reportingMode SET_PREF_FUNCTION_EMIT_NO_ARG( bool, setShowRSyntax, showRSyntax, showRSyntaxChanged, Settings::SHOW_RSYNTAX ) SET_PREF_FUNCTION_EMIT_NO_ARG( bool, setShowAllROptions, showAllROptions, showAllROptionsChanged, Settings::SHOW_ALL_R_OPTIONS ) SET_PREF_FUNCTION( bool, setShowRSyntaxInResults, showRSyntaxInResults, showRSyntaxInResultsChanged, Settings::SHOW_RSYNTAX_IN_RESULTS ) -SET_PREF_FUNCTION( QString, setRemoteSettingsURL, remoteSettingsURL, remoteSettingsURLChanged, Settings::REMOTE_SETTINGS_URL ) +SET_PREF_FUNCTION( QString, setRemoteConfigurationURL, remoteConfigurationURL, remoteConfigurationURLChanged, Settings::REMOTE_CONFIGURATION_URL ) void PreferencesModel::setGithubPatCustom(QString newPat) diff --git a/Desktop/gui/preferencesmodel.h b/Desktop/gui/preferencesmodel.h index 66a7c59668..d6e81d4016 100644 --- a/Desktop/gui/preferencesmodel.h +++ b/Desktop/gui/preferencesmodel.h @@ -68,11 +68,8 @@ class PreferencesModel : public PreferencesModelBase Q_PROPERTY(bool reportingMode READ reportingMode WRITE setReportingMode NOTIFY reportingModeChanged ) Q_PROPERTY(bool showRSyntax READ showRSyntax WRITE setShowRSyntax NOTIFY showRSyntaxChanged ) Q_PROPERTY(bool showAllROptions READ showAllROptions WRITE setShowAllROptions NOTIFY showAllROptionsChanged ) -<<<<<<< HEAD Q_PROPERTY(bool showRSyntaxInResults READ showRSyntaxInResults WRITE setShowRSyntaxInResults NOTIFY showRSyntaxInResultsChanged ) -======= - Q_PROPERTY(QString remoteSettingsURL READ remoteSettingsURL WRITE setRemoteSettingsURL NOTIFY remoteSettingsURLChanged ) ->>>>>>> 9536009ae (start) + Q_PROPERTY(QString remoteConfigurationURL READ remoteConfigurationURL WRITE setRemoteConfigurationURL NOTIFY remoteConfigurationURLChanged ) public: @@ -139,7 +136,7 @@ class PreferencesModel : public PreferencesModelBase void zoomReset(); int maxEnginesAdmin() const; bool developerMode() const; - QString remoteSettingsURL() const; + QString remoteConfigurationURL() const; public slots: void setUiScale( double uiScale); @@ -198,7 +195,7 @@ public slots: void setShowAllROptions( bool showAllROptions) override; void setShowRSyntaxInResults( bool showRSyntax); void currentThemeNameHandler(); - void setRemoteSettingsURL( QString URL); + void setRemoteConfigurationURL( QString URL); signals: void fixedDecimalsChanged( bool fixedDecimals); @@ -248,7 +245,7 @@ public slots: void guiQtTextRenderChanged( bool guiQtTextRender); void reportingModeChanged( bool reportingMode); void showRSyntaxInResultsChanged( bool showRSyntax); - void remoteSettingsURLChanged( QString remoteSettingsURL); + void remoteConfigurationURLChanged( QString remoteConfigurationURL); private slots: void dataLabelNAChangedSlot(QString label); diff --git a/Desktop/mainwindow.cpp b/Desktop/mainwindow.cpp index c236db1c59..0a638aca84 100644 --- a/Desktop/mainwindow.cpp +++ b/Desktop/mainwindow.cpp @@ -148,7 +148,7 @@ MainWindow::MainWindow(QApplication * application) : QObject(application), _appl _resultMenuModel = new ResultMenuModel(this); _plotEditorModel = new PlotEditorModel(); _columnTypesModel = new ColumnTypesModel(this); - _remoteSettings = new JASPSettings(this); + _remoteSettings = new JASPConfiguration(this); #ifdef WIN32 _windowsWorkaroundCPs = new CodePagesWindows(this); @@ -160,7 +160,7 @@ MainWindow::MainWindow(QApplication * application) : QObject(application), _appl makeConnections(); - _remoteSettings->processSettings(); + _remoteSettings->processConfiguration(); qmlRegisterUncreatableType ("JASP", 1, 0 ,"JASP", "Impossible to create JASP Object" ); //This is here to keep JASP.enum short I guess? qmlRegisterUncreatableType ("JASP", 1, 0, "MessageForwarder", "You can't touch this" ); @@ -376,8 +376,7 @@ void MainWindow::makeConnections() connect(_preferences, &PreferencesModel::dataLabelNAChanged, _package, &DataSetPackage::refresh, Qt::QueuedConnection); connect(_preferences, &PreferencesModel::dataAutoSynchronizationChanged, _package, &DataSetPackage::synchingExternallyChanged ); - - connect(_preferences, &PreferencesModel::remoteSettingsURLChanged, _remoteSettings, &JASPConfiguration::remoteChanged ); + connect(_preferences, &PreferencesModel::remoteConfigurationURLChanged, _remoteSettings, &JASPConfiguration::remoteChanged ); connect(_preferences, &PreferencesModel::plotBackgroundChanged, this, &MainWindow::setImageBackgroundHandler ); connect(_preferences, &PreferencesModel::plotPPIChanged, this, &MainWindow::plotPPIChangedHandler ); connect(_preferences, &PreferencesModel::dataAutoSynchronizationChanged, _fileMenu, &FileMenu::dataAutoSynchronizationChanged ); diff --git a/Desktop/mainwindow.h b/Desktop/mainwindow.h index 61e1a468f6..5fa10085a3 100644 --- a/Desktop/mainwindow.h +++ b/Desktop/mainwindow.h @@ -40,7 +40,7 @@ #include "gui/aboutmodel.h" #include "gui/columntypesmodel.h" #include "gui/preferencesmodel.h" -#include "gui/remotesettings.h" +#include "gui/jaspconfiguration.h" #include "modules/dynamicmodule.h" #include "modules/ribbonbutton.h" #include "modules/ribbonmodelfiltered.h" @@ -282,7 +282,7 @@ private slots: Upgrader * _upgrader = nullptr; Reporter * _reporter = nullptr; CodePagesWindows * _windowsWorkaroundCPs = nullptr; - JASPSettings * _remoteSettings = nullptr; + JASPConfiguration * _remoteSettings = nullptr; QSettings _settings; diff --git a/Desktop/utilities/settings.h b/Desktop/utilities/settings.h index 3f9de42c98..83c39d5838 100644 --- a/Desktop/utilities/settings.h +++ b/Desktop/utilities/settings.h @@ -74,7 +74,7 @@ class Settings { SHOW_RSYNTAX, SHOW_ALL_R_OPTIONS, SHOW_RSYNTAX_IN_RESULTS, - REMOTE_SETTINGS_URL + REMOTE_CONFIGURATION_URL }; static QVariant value(Settings::Type key); From a44a8937a0258127e5e92bafd18ba747c5ba742b Mon Sep 17 00:00:00 2001 From: RDOFFERHOFF Date: Thu, 6 Apr 2023 13:00:44 +0200 Subject: [PATCH 16/38] progress --- Desktop/gui/jaspconfiguration.cpp | 23 +++++++++++++++++------ Desktop/gui/jaspconfiguration.h | 2 +- Desktop/mainwindow.cpp | 12 +++++++----- Desktop/mainwindow.h | 4 ++-- Tools/CMake/Modules.cmake | 2 +- 5 files changed, 28 insertions(+), 15 deletions(-) diff --git a/Desktop/gui/jaspconfiguration.cpp b/Desktop/gui/jaspconfiguration.cpp index 23056ea048..4edc9796fc 100644 --- a/Desktop/gui/jaspconfiguration.cpp +++ b/Desktop/gui/jaspconfiguration.cpp @@ -22,12 +22,14 @@ JASPConfiguration::JASPConfiguration(QObject *parent) bool JASPConfiguration::exists(const QString &constant, const QString &module, const QString &analysis) { - + return _keyValueConstants.contains(constant); } QVariant JASPConfiguration::get(const QString &constant, QVariant defaultValue, const QString &module, const QString &analysis) { - + if(exists(constant, module, analysis)) + return _keyValueConstants[constant]; + return defaultValue; } bool JASPConfiguration::isSet(const QString &module, const QString &analysis, const QString &optionName) @@ -109,18 +111,27 @@ void JASPConfiguration::remoteChanged(QString remoteURL) void JASPConfiguration::parse(const QString &conf) { getVersion(conf); - _patches.clear(); + QMap keyValuePairs; QStringList lines = conf.split("\n"); for(auto& line : lines) - { - _patches.insert(line); + { + auto match = versionRE.match(conf); + if(match.hasMatch()) + { + QString key = match.captured("key"); + QVariant value = QVariant(match.captured("value")); + keyValuePairs.insert(key, value); + } + Log::log() << "invalid line in configuration: " + line.toStdString() << std::endl; } + + _keyValueConstants = keyValuePairs; } void JASPConfiguration::getVersion(const QString& conf) { - auto match = versionRE.match(conf); + auto match = versionRE.match(conf); if(match.hasCaptured("versionNum")) { try diff --git a/Desktop/gui/jaspconfiguration.h b/Desktop/gui/jaspconfiguration.h index 975843e13b..da1b83a8f1 100644 --- a/Desktop/gui/jaspconfiguration.h +++ b/Desktop/gui/jaspconfiguration.h @@ -44,7 +44,7 @@ private slots: QNetworkAccessManager _networkManager; Version jaspVersion; - QSet _patches; + QMap _keyValueConstants; const QString configurationFilename = "userConfiguration.conf"; diff --git a/Desktop/mainwindow.cpp b/Desktop/mainwindow.cpp index 0a638aca84..3d48afa977 100644 --- a/Desktop/mainwindow.cpp +++ b/Desktop/mainwindow.cpp @@ -147,8 +147,8 @@ MainWindow::MainWindow(QApplication * application) : QObject(application), _appl _aboutModel = new AboutModel(this); _resultMenuModel = new ResultMenuModel(this); _plotEditorModel = new PlotEditorModel(); - _columnTypesModel = new ColumnTypesModel(this); - _remoteSettings = new JASPConfiguration(this); + _columnTypesModel = new ColumnTypesModel(this); + _jaspConfiguration = new JASPConfiguration(this); #ifdef WIN32 _windowsWorkaroundCPs = new CodePagesWindows(this); @@ -160,7 +160,7 @@ MainWindow::MainWindow(QApplication * application) : QObject(application), _appl makeConnections(); - _remoteSettings->processConfiguration(); + _jaspConfiguration->processConfiguration(); qmlRegisterUncreatableType ("JASP", 1, 0 ,"JASP", "Impossible to create JASP Object" ); //This is here to keep JASP.enum short I guess? qmlRegisterUncreatableType ("JASP", 1, 0, "MessageForwarder", "You can't touch this" ); @@ -376,7 +376,7 @@ void MainWindow::makeConnections() connect(_preferences, &PreferencesModel::dataLabelNAChanged, _package, &DataSetPackage::refresh, Qt::QueuedConnection); connect(_preferences, &PreferencesModel::dataAutoSynchronizationChanged, _package, &DataSetPackage::synchingExternallyChanged ); - connect(_preferences, &PreferencesModel::remoteConfigurationURLChanged, _remoteSettings, &JASPConfiguration::remoteChanged ); + connect(_preferences, &PreferencesModel::remoteConfigurationURLChanged, _jaspConfiguration, &JASPConfiguration::remoteChanged ); connect(_preferences, &PreferencesModel::plotBackgroundChanged, this, &MainWindow::setImageBackgroundHandler ); connect(_preferences, &PreferencesModel::plotPPIChanged, this, &MainWindow::plotPPIChangedHandler ); connect(_preferences, &PreferencesModel::dataAutoSynchronizationChanged, _fileMenu, &FileMenu::dataAutoSynchronizationChanged ); @@ -481,7 +481,9 @@ void MainWindow::loadQML() _qml->rootContext()->setContextProperty("columnTypeScale", int(columnType::scale) ); _qml->rootContext()->setContextProperty("columnTypeOrdinal", int(columnType::ordinal) ); _qml->rootContext()->setContextProperty("columnTypeNominal", int(columnType::nominal) ); - _qml->rootContext()->setContextProperty("columnTypeNominalText", int(columnType::nominalText) ); + _qml->rootContext()->setContextProperty("columnTypeNominalText", int(columnType::nominalText) ); + _qml->rootContext()->setContextProperty("jaspConfiguration", _jaspConfiguration ); + bool debug = false, isMac = false, diff --git a/Desktop/mainwindow.h b/Desktop/mainwindow.h index 5fa10085a3..62ea453238 100644 --- a/Desktop/mainwindow.h +++ b/Desktop/mainwindow.h @@ -281,8 +281,8 @@ private slots: JaspTheme * _jaspTheme = nullptr; Upgrader * _upgrader = nullptr; Reporter * _reporter = nullptr; - CodePagesWindows * _windowsWorkaroundCPs = nullptr; - JASPConfiguration * _remoteSettings = nullptr; + CodePagesWindows * _windowsWorkaroundCPs = nullptr; + JASPConfiguration * _jaspConfiguration = nullptr; QSettings _settings; diff --git a/Tools/CMake/Modules.cmake b/Tools/CMake/Modules.cmake index 85757b3966..bbdcaafcab 100644 --- a/Tools/CMake/Modules.cmake +++ b/Tools/CMake/Modules.cmake @@ -81,7 +81,7 @@ if(("jaspMetaAnalysis" IN_LIST JASP_EXTRA_MODULES) OR ("jaspJags" IN_LIST endif() message(CHECK_START "Looking for libjags.so") - find_file(LIBJAGS libjags.so HINTS ${jags_HOME}/lib REQUIRED) + find_file(LIBJAGS libjags.so HINTS ${jags_HOME}/lib ${JAGS_HOME} REQUIRED) if(EXISTS ${LIBJAGS}) message(CHECK_PASS "found") message(STATUS " ${LIBJAGS}") From 84807bc91b0931a219e1914a5785fba92eb546bf Mon Sep 17 00:00:00 2001 From: RDOFFERHOFF Date: Fri, 7 Apr 2023 10:05:26 +0200 Subject: [PATCH 17/38] progress --- Desktop/gui/jaspconfiguration.cpp | 5 +++-- Desktop/gui/jaspconfiguration.h | 7 +++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Desktop/gui/jaspconfiguration.cpp b/Desktop/gui/jaspconfiguration.cpp index 4edc9796fc..63b3bf3db1 100644 --- a/Desktop/gui/jaspconfiguration.cpp +++ b/Desktop/gui/jaspconfiguration.cpp @@ -115,14 +115,15 @@ void JASPConfiguration::parse(const QString &conf) QStringList lines = conf.split("\n"); for(auto& line : lines) { - auto match = versionRE.match(conf); + auto match = keyValueRE.match(line); if(match.hasMatch()) { QString key = match.captured("key"); QVariant value = QVariant(match.captured("value")); keyValuePairs.insert(key, value); } - Log::log() << "invalid line in configuration: " + line.toStdString() << std::endl; + else + Log::log() << "!!!invalid line in configuration: " + line.toStdString() << std::endl; } _keyValueConstants = keyValuePairs; diff --git a/Desktop/gui/jaspconfiguration.h b/Desktop/gui/jaspconfiguration.h index da1b83a8f1..d2e43b83ab 100644 --- a/Desktop/gui/jaspconfiguration.h +++ b/Desktop/gui/jaspconfiguration.h @@ -49,8 +49,11 @@ private slots: const QString configurationFilename = "userConfiguration.conf"; - const QString versionPattern = "JASP_Version:\\s*(?[\\S]+)\\s*$"; - const QString keyValuePattern = "\\s*(?[\\S+)\\s*=\\s*(?[\\S+)\\s*$"; + const QString versionPattern = "JASP_Version:\\s*(?\\S+)\\s*$"; + const QString keyValuePattern = "\\s*(?\\S+)\\s*=\\s*(?\\S+)\\s*$"; +// const QString keyValuePattern = "\\s*\\S+\\s*=\\s*\\S+\\s*"; + + QRegularExpression versionRE; QRegularExpression keyValueRE; From f286ea0b2efeb30e6e5cb32ec8a384d81073ec75 Mon Sep 17 00:00:00 2001 From: Rens Dofferhoff Date: Mon, 10 Apr 2023 11:11:05 +0200 Subject: [PATCH 18/38] before overhaul --- Desktop/gui/jaspconfiguration.cpp | 108 ++++++++++++++++++++++-------- Desktop/gui/jaspconfiguration.h | 28 +++++--- 2 files changed, 100 insertions(+), 36 deletions(-) diff --git a/Desktop/gui/jaspconfiguration.cpp b/Desktop/gui/jaspconfiguration.cpp index 63b3bf3db1..ca6f9d31f2 100644 --- a/Desktop/gui/jaspconfiguration.cpp +++ b/Desktop/gui/jaspconfiguration.cpp @@ -15,20 +15,24 @@ JASPConfiguration::JASPConfiguration(QObject *parent) { versionRE.setPattern(versionPattern); versionRE.setPatternOptions(QRegularExpression::MultilineOption); - + loadModulesRE.setPattern(loadModulesPattern); + loadModulesRE.setPatternOptions(QRegularExpression::MultilineOption); + moduleStatementRE.setPattern(moduleSectionPattern); + moduleStatementRE.setPatternOptions(QRegularExpression::MultilineOption | QRegularExpression::DotMatchesEverythingOption); + analysisStatementRE.setPattern(analysisSectionPattern); + analysisStatementRE.setPatternOptions(QRegularExpression::MultilineOption | QRegularExpression::DotMatchesEverythingOption); keyValueRE.setPattern(keyValuePattern); - } bool JASPConfiguration::exists(const QString &constant, const QString &module, const QString &analysis) { - return _keyValueConstants.contains(constant); + return _definedConstants.contains(module) && _definedConstants[module].contains(analysis) && _definedConstants[module][analysis].contains(constant); } QVariant JASPConfiguration::get(const QString &constant, QVariant defaultValue, const QString &module, const QString &analysis) { if(exists(constant, module, analysis)) - return _keyValueConstants[constant]; + return _definedConstants[module][analysis][constant]; return defaultValue; } @@ -61,7 +65,7 @@ void JASPConfiguration::processConfiguration() QByteArray payload = reply->readAll(); parse(payload); - auto localConfFile = getLocalConfFile(); + auto localConfFile = getLocalConfFile(true); localConfFile->write(payload); localConfFile->close(); Log::log() << "!!!Updated local copy of remote configuration" << std::endl; @@ -108,36 +112,23 @@ void JASPConfiguration::remoteChanged(QString remoteURL) processConfiguration(); } -void JASPConfiguration::parse(const QString &conf) +void JASPConfiguration::parse(QString conf) { - getVersion(conf); - QMap keyValuePairs; - QStringList lines = conf.split("\n"); - for(auto& line : lines) - { - auto match = keyValueRE.match(line); - if(match.hasMatch()) - { - QString key = match.captured("key"); - QVariant value = QVariant(match.captured("value")); - keyValuePairs.insert(key, value); - } - else - Log::log() << "!!!invalid line in configuration: " + line.toStdString() << std::endl; - } - - _keyValueConstants = keyValuePairs; + parseVersion(conf); + parseModulesToLoad(conf); + parseModuleStatements(conf); + parseKeyValuePairs(conf); } -void JASPConfiguration::getVersion(const QString& conf) +void JASPConfiguration::parseVersion(QString& conf) { - auto match = versionRE.match(conf); if(match.hasCaptured("versionNum")) { try { - jaspVersion = Version(match.captured("versionNum").toStdString()); + _jaspVersion = Version(match.captured("versionNum").toStdString()); + conf.remove(match.capturedStart(), match.capturedLength()); return; } catch (std::runtime_error& e) @@ -148,7 +139,67 @@ void JASPConfiguration::getVersion(const QString& conf) throw std::runtime_error("No JASP Version number present in configuration file"); } -std::shared_ptr JASPConfiguration::getLocalConfFile() { +void JASPConfiguration::parseModulesToLoad(QString &conf) +{ + auto match = loadModulesRE.match(conf); + if(match.hasCaptured("list")) + { + QStringList list = match.captured("list").split(",", Qt::SkipEmptyParts); + for(const QString& item : list) + _modulesToLoad.push_back(item.trimmed()); + conf.remove(match.capturedStart(), match.capturedLength()); + } +} + +void JASPConfiguration::parseModuleStatements(QString &conf) +{ + auto match = moduleStatementRE.match(conf); + while (match.hasMatch()) { + QString section = match.captured("section"); + QString moduleName = match.captured("name"); + parseAnalysisStatements(section, moduleName); + parseKeyValuePairs(section, moduleName); + + conf.remove(match.capturedStart(), match.capturedLength()); + match = moduleStatementRE.match(conf); + } +} + +void JASPConfiguration::parseAnalysisStatements(QString &conf, const QString& moduleName) +{ + auto match = analysisStatementRE.match(conf); + while (match.hasMatch()) { + QString section = match.captured("section"); + QString analysisName = match.captured("name"); + //TODO: parse analysis options + parseKeyValuePairs(section, moduleName, analysisName); + + conf.remove(match.capturedStart(), match.capturedLength()); + match = analysisStatementRE.match(conf); + } +} + +void JASPConfiguration::parseKeyValuePairs(const QString &conf, const QString moduleName, const QString analysisName) +{ + + QStringList lines = conf.split("\n"); + for(auto& line : lines) + { + auto match = keyValueRE.match(line); + if(match.hasMatch()) + { + QString key = match.captured("key"); + QVariant value = QVariant(match.captured("value")); + _definedConstants[moduleName][analysisName][key] = value; + + Log::log() << "!!!: " << moduleName.toStdString() << " " << analysisName.toStdString() << " " << key.toStdString() << ": " << match.captured("value").toStdString() << std::endl; + } + else + Log::log() << "!!!invalid line in configuration: " + line.toStdString() << std::endl; + } +} + +std::shared_ptr JASPConfiguration::getLocalConfFile(bool truncate) { QString confPath = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); QDir confDir; if(!confDir.mkpath(confPath)) @@ -156,7 +207,8 @@ std::shared_ptr JASPConfiguration::getLocalConfFile() { QString configurationFilePath = confPath + "/" + configurationFilename; std::shared_ptr localConfFile = std::make_shared(configurationFilePath); - if (!localConfFile->open(QIODevice::ReadWrite | QIODevice::Text)) + QIODeviceBase::OpenMode flags = QIODeviceBase::ReadWrite | QIODeviceBase::Text | (truncate ? QIODeviceBase::Truncate : QIODeviceBase::NotOpen); + if (!localConfFile->open(flags)) throw std::runtime_error("Could not open local configuration file " + configurationFilePath.toStdString() + ": " + localConfFile->errorString().toStdString()); return localConfFile; } diff --git a/Desktop/gui/jaspconfiguration.h b/Desktop/gui/jaspconfiguration.h index d2e43b83ab..3f883a9dce 100644 --- a/Desktop/gui/jaspconfiguration.h +++ b/Desktop/gui/jaspconfiguration.h @@ -38,25 +38,37 @@ private slots: bool processLocal(); void processTasks(); - void parse(const QString& conf); - void getVersion(const QString& conf); - std::shared_ptr getLocalConfFile(); + std::shared_ptr getLocalConfFile(bool truncate = false); + + void parse(QString conf); + void parseVersion(QString& conf); + void parseModulesToLoad(QString& conf); + void parseModuleStatements(QString& conf); + void parseAnalysisStatements(QString& conf, const QString& moduleName); + void parseKeyValuePairs(const QString& conf, const QString moduleName = "", const QString analysisName = ""); QNetworkAccessManager _networkManager; - Version jaspVersion; - QMap _keyValueConstants; + + Version _jaspVersion; + QMap>> _definedConstants; + QStringList _modulesToLoad; const QString configurationFilename = "userConfiguration.conf"; - const QString versionPattern = "JASP_Version:\\s*(?\\S+)\\s*$"; - const QString keyValuePattern = "\\s*(?\\S+)\\s*=\\s*(?\\S+)\\s*$"; -// const QString keyValuePattern = "\\s*\\S+\\s*=\\s*\\S+\\s*"; + const QString versionPattern = "^\\s*JASP_Version:\\s*(?\\S+)\\s*$"; + const QString keyValuePattern = "^\\s*(?\\S+)\\s*=\\s*(?\\S+)\\s*$"; + const QString moduleSectionPattern = "^\\s*Module\\s*(?\\S+)\\s*$(?
.*)\\s*END\\s*Module\\s*$"; + const QString analysisSectionPattern = "^\\s*Analysis\\s*(?\\S+)\\s*$(?
.*)\\s*END\\s*Analysis\\s*$"; + const QString loadModulesPattern = "^\\s*Load\\s*Modules\\s*:\\s*(?\\w+(\\s*,\\s*\\w*)*)?\\s*$"; QRegularExpression versionRE; QRegularExpression keyValueRE; + QRegularExpression loadModulesRE; + QRegularExpression moduleStatementRE; + QRegularExpression analysisStatementRE; }; From b0c3a3884c1ab186a05f6bc535c76ae58adf9734 Mon Sep 17 00:00:00 2001 From: Rens Dofferhoff Date: Wed, 12 Apr 2023 11:09:33 +0200 Subject: [PATCH 19/38] functional --- Desktop/gui/jaspconfiguration.cpp | 223 ------------------------------ Desktop/gui/jaspconfiguration.h | 75 ---------- Desktop/mainwindow.cpp | 18 ++- Desktop/mainwindow.h | 4 +- 4 files changed, 20 insertions(+), 300 deletions(-) delete mode 100644 Desktop/gui/jaspconfiguration.cpp delete mode 100644 Desktop/gui/jaspconfiguration.h diff --git a/Desktop/gui/jaspconfiguration.cpp b/Desktop/gui/jaspconfiguration.cpp deleted file mode 100644 index ca6f9d31f2..0000000000 --- a/Desktop/gui/jaspconfiguration.cpp +++ /dev/null @@ -1,223 +0,0 @@ -#include "jaspconfiguration.h" -#include "utilities/settings.h" -#include "log.h" -#include -#include -#include -#include -#include -#include -#include - - -JASPConfiguration::JASPConfiguration(QObject *parent) - : QObject{parent} -{ - versionRE.setPattern(versionPattern); - versionRE.setPatternOptions(QRegularExpression::MultilineOption); - loadModulesRE.setPattern(loadModulesPattern); - loadModulesRE.setPatternOptions(QRegularExpression::MultilineOption); - moduleStatementRE.setPattern(moduleSectionPattern); - moduleStatementRE.setPatternOptions(QRegularExpression::MultilineOption | QRegularExpression::DotMatchesEverythingOption); - analysisStatementRE.setPattern(analysisSectionPattern); - analysisStatementRE.setPatternOptions(QRegularExpression::MultilineOption | QRegularExpression::DotMatchesEverythingOption); - keyValueRE.setPattern(keyValuePattern); -} - -bool JASPConfiguration::exists(const QString &constant, const QString &module, const QString &analysis) -{ - return _definedConstants.contains(module) && _definedConstants[module].contains(analysis) && _definedConstants[module][analysis].contains(constant); -} - -QVariant JASPConfiguration::get(const QString &constant, QVariant defaultValue, const QString &module, const QString &analysis) -{ - if(exists(constant, module, analysis)) - return _definedConstants[module][analysis][constant]; - return defaultValue; -} - -bool JASPConfiguration::isSet(const QString &module, const QString &analysis, const QString &optionName) -{ - -} - -QString &JASPConfiguration::getAnalysisOptionValue(const QString &module, const QString &analysis, const QString &optionName, const QString &defaultValue) -{ - -} - -void JASPConfiguration::processConfiguration() -{ - bool localOK = processLocal(); - - //read, parse & save remote settings - if(Settings::value(Settings::REMOTE_CONFIGURATION_URL).toString() != "") - { - auto conn = std::make_shared(); - *conn = connect(&_networkManager, &QNetworkAccessManager::finished, this, [=, this](QNetworkReply* reply) { - QObject::disconnect(*conn); - reply->deleteLater(); - - try - { - if(reply->error()) - throw std::runtime_error("Error fetching remote configuration file " + reply->request().url().toString().toStdString() + " : " + reply->errorString().toStdString()); - QByteArray payload = reply->readAll(); - parse(payload); - - auto localConfFile = getLocalConfFile(true); - localConfFile->write(payload); - localConfFile->close(); - Log::log() << "!!!Updated local copy of remote configuration" << std::endl; - emit this->configurationProcessed("OK"); - } - catch (std::runtime_error& e) - { - Log::log() << "!!!Failed to process remote configuration: " << e.what() << std::endl; - if(!localOK) - emit this->configurationProcessed("FAIL"); - else - emit this->configurationProcessed("OK"); - return; - } - }); - - //make the request - QNetworkRequest request(Settings::value(Settings::REMOTE_CONFIGURATION_URL).toString()); - QNetworkReply* reply = _networkManager.get(request); - connect(reply, &QNetworkReply::sslErrors, this, &JASPConfiguration::sslErrors); - } - else - emit configurationProcessed("OK"); -} - -bool JASPConfiguration::processLocal() -{ - try - { - auto localConfFile = getLocalConfFile(); - QTextStream in(localConfFile.get()); - parse(in.readAll()); - } - catch(std::runtime_error& e) - { - Log::log() << "!!!Could not parse local configuration: " << e.what() << std::endl; - return false; - } - return true; -} - -void JASPConfiguration::remoteChanged(QString remoteURL) -{ - processConfiguration(); -} - -void JASPConfiguration::parse(QString conf) -{ - parseVersion(conf); - parseModulesToLoad(conf); - parseModuleStatements(conf); - parseKeyValuePairs(conf); -} - -void JASPConfiguration::parseVersion(QString& conf) -{ - auto match = versionRE.match(conf); - if(match.hasCaptured("versionNum")) - { - try - { - _jaspVersion = Version(match.captured("versionNum").toStdString()); - conf.remove(match.capturedStart(), match.capturedLength()); - return; - } - catch (std::runtime_error& e) - { - throw std::runtime_error("Could not parse JASP Version number in configuration file: " + std::string(e.what())); - } - } - throw std::runtime_error("No JASP Version number present in configuration file"); -} - -void JASPConfiguration::parseModulesToLoad(QString &conf) -{ - auto match = loadModulesRE.match(conf); - if(match.hasCaptured("list")) - { - QStringList list = match.captured("list").split(",", Qt::SkipEmptyParts); - for(const QString& item : list) - _modulesToLoad.push_back(item.trimmed()); - conf.remove(match.capturedStart(), match.capturedLength()); - } -} - -void JASPConfiguration::parseModuleStatements(QString &conf) -{ - auto match = moduleStatementRE.match(conf); - while (match.hasMatch()) { - QString section = match.captured("section"); - QString moduleName = match.captured("name"); - parseAnalysisStatements(section, moduleName); - parseKeyValuePairs(section, moduleName); - - conf.remove(match.capturedStart(), match.capturedLength()); - match = moduleStatementRE.match(conf); - } -} - -void JASPConfiguration::parseAnalysisStatements(QString &conf, const QString& moduleName) -{ - auto match = analysisStatementRE.match(conf); - while (match.hasMatch()) { - QString section = match.captured("section"); - QString analysisName = match.captured("name"); - //TODO: parse analysis options - parseKeyValuePairs(section, moduleName, analysisName); - - conf.remove(match.capturedStart(), match.capturedLength()); - match = analysisStatementRE.match(conf); - } -} - -void JASPConfiguration::parseKeyValuePairs(const QString &conf, const QString moduleName, const QString analysisName) -{ - - QStringList lines = conf.split("\n"); - for(auto& line : lines) - { - auto match = keyValueRE.match(line); - if(match.hasMatch()) - { - QString key = match.captured("key"); - QVariant value = QVariant(match.captured("value")); - _definedConstants[moduleName][analysisName][key] = value; - - Log::log() << "!!!: " << moduleName.toStdString() << " " << analysisName.toStdString() << " " << key.toStdString() << ": " << match.captured("value").toStdString() << std::endl; - } - else - Log::log() << "!!!invalid line in configuration: " + line.toStdString() << std::endl; - } -} - -std::shared_ptr JASPConfiguration::getLocalConfFile(bool truncate) { - QString confPath = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); - QDir confDir; - if(!confDir.mkpath(confPath)) - throw std::runtime_error("Could not Access app configuration path"); - - QString configurationFilePath = confPath + "/" + configurationFilename; - std::shared_ptr localConfFile = std::make_shared(configurationFilePath); - QIODeviceBase::OpenMode flags = QIODeviceBase::ReadWrite | QIODeviceBase::Text | (truncate ? QIODeviceBase::Truncate : QIODeviceBase::NotOpen); - if (!localConfFile->open(flags)) - throw std::runtime_error("Could not open local configuration file " + configurationFilePath.toStdString() + ": " + localConfFile->errorString().toStdString()); - return localConfFile; -} - -void JASPConfiguration::sslErrors(const QList &errors) -{ - for (const QSslError &error : errors) - Log::log() << "Error fetching remote settings:" << error.errorString().toStdString() << std::endl; -} - - - diff --git a/Desktop/gui/jaspconfiguration.h b/Desktop/gui/jaspconfiguration.h deleted file mode 100644 index 3f883a9dce..0000000000 --- a/Desktop/gui/jaspconfiguration.h +++ /dev/null @@ -1,75 +0,0 @@ -#ifndef JASPCONFIGURATION_H -#define JASPCONFIGURATION_H - -#include -#include -#include -#include -#include -#include "version.h" - -class JASPConfiguration : public QObject -{ - Q_OBJECT -public: - explicit JASPConfiguration(QObject *parent = nullptr); - - //QML programming constants interface - Q_INVOKABLE bool exists(const QString& constant, const QString& module = "", const QString& analysis = ""); - Q_INVOKABLE QVariant get(const QString& constant, QVariant defaultValue = QVariant(), const QString& module = "", const QString& analysis = ""); - - //Predefined analysis options interface - bool isSet(const QString& module, const QString& analysis, const QString& optionName); - QString& getAnalysisOptionValue(const QString& module, const QString& analysis, const QString& optionName, const QString& defaultValue = ""); - - //read and parse local and remote configuration - void processConfiguration(); - -public slots: - void remoteChanged(QString remoteURL); - -signals: - void configurationProcessed(QString results); - -private slots: - void sslErrors(const QList &errors); - -private: - bool processLocal(); - - void processTasks(); - std::shared_ptr getLocalConfFile(bool truncate = false); - - void parse(QString conf); - void parseVersion(QString& conf); - void parseModulesToLoad(QString& conf); - void parseModuleStatements(QString& conf); - void parseAnalysisStatements(QString& conf, const QString& moduleName); - void parseKeyValuePairs(const QString& conf, const QString moduleName = "", const QString analysisName = ""); - - QNetworkAccessManager _networkManager; - - Version _jaspVersion; - QMap>> _definedConstants; - QStringList _modulesToLoad; - - const QString configurationFilename = "userConfiguration.conf"; - - - const QString versionPattern = "^\\s*JASP_Version:\\s*(?\\S+)\\s*$"; - const QString keyValuePattern = "^\\s*(?\\S+)\\s*=\\s*(?\\S+)\\s*$"; - const QString moduleSectionPattern = "^\\s*Module\\s*(?\\S+)\\s*$(?
.*)\\s*END\\s*Module\\s*$"; - const QString analysisSectionPattern = "^\\s*Analysis\\s*(?\\S+)\\s*$(?
.*)\\s*END\\s*Analysis\\s*$"; - const QString loadModulesPattern = "^\\s*Load\\s*Modules\\s*:\\s*(?\\w+(\\s*,\\s*\\w*)*)?\\s*$"; - - - - QRegularExpression versionRE; - QRegularExpression keyValueRE; - QRegularExpression loadModulesRE; - QRegularExpression moduleStatementRE; - QRegularExpression analysisStatementRE; - -}; - -#endif // JASPCONFIGURATION_H diff --git a/Desktop/mainwindow.cpp b/Desktop/mainwindow.cpp index 3d48afa977..d334b0a860 100644 --- a/Desktop/mainwindow.cpp +++ b/Desktop/mainwindow.cpp @@ -148,7 +148,7 @@ MainWindow::MainWindow(QApplication * application) : QObject(application), _appl _resultMenuModel = new ResultMenuModel(this); _plotEditorModel = new PlotEditorModel(); _columnTypesModel = new ColumnTypesModel(this); - _jaspConfiguration = new JASPConfiguration(this); + _jaspConfiguration = JASPConfiguration::getInstance(this); #ifdef WIN32 _windowsWorkaroundCPs = new CodePagesWindows(this); @@ -434,6 +434,8 @@ void MainWindow::makeConnections() connect(_qml, &QQmlApplicationEngine::warnings, this, &MainWindow::printQmlWarnings ); connect(_plotEditorModel, &PlotEditorModel::saveImage, this, &MainWindow::analysisSaveImageHandler ); + + connect(_jaspConfiguration, &JASPConfiguration::configurationProcessed, this, &MainWindow::loadModulesFromUserConfiguration ); } void MainWindow::printQmlWarnings(const QList &warnings) @@ -1903,3 +1905,17 @@ QString MainWindow::versionString() #endif ; } + + +void MainWindow::loadModulesFromUserConfiguration(QString state) +{ + if(state == "FAIL") + return; + + for(const QString& moduleName : *_jaspConfiguration->getAdditionalModules()) + { + auto button = _ribbonModel->ribbonButtonModel(moduleName.toStdString()); + int index = _ribbonModel->ribbonButtonModelIndex(button); + _ribbonModel->setModuleEnabled(index, true); + } +} diff --git a/Desktop/mainwindow.h b/Desktop/mainwindow.h index 62ea453238..64da0c7178 100644 --- a/Desktop/mainwindow.h +++ b/Desktop/mainwindow.h @@ -40,7 +40,7 @@ #include "gui/aboutmodel.h" #include "gui/columntypesmodel.h" #include "gui/preferencesmodel.h" -#include "gui/jaspconfiguration.h" +#include "gui/jaspConfiguration/jaspconfiguration.h" #include "modules/dynamicmodule.h" #include "modules/ribbonbutton.h" #include "modules/ribbonmodelfiltered.h" @@ -242,6 +242,8 @@ private slots: void printQmlWarnings(const QList &warnings); void setQmlImportPaths(); + void loadModulesFromUserConfiguration(QString state); + private: void _analysisSaveImageHandler(Analysis* analysis, QString options); void makeAppleMenu(); From 72b965f747c71c9d38b3f3aa4b6a36ab5c0a63df Mon Sep 17 00:00:00 2001 From: Rens Dofferhoff Date: Wed, 12 Apr 2023 11:12:39 +0200 Subject: [PATCH 20/38] giant oops --- .../jaspConfigurationParser.cpp | 208 + .../jaspConfigurationParser.h | 82 + .../jaspConfiguration/jaspconfiguration.cpp | 179 + .../gui/jaspConfiguration/jaspconfiguration.h | 70 + Desktop/gui/jaspConfiguration/peglib/LICENSE | 22 + Desktop/gui/jaspConfiguration/peglib/peglib.h | 4838 +++++++++++++++++ 6 files changed, 5399 insertions(+) create mode 100644 Desktop/gui/jaspConfiguration/jaspConfigurationParser.cpp create mode 100644 Desktop/gui/jaspConfiguration/jaspConfigurationParser.h create mode 100644 Desktop/gui/jaspConfiguration/jaspconfiguration.cpp create mode 100644 Desktop/gui/jaspConfiguration/jaspconfiguration.h create mode 100644 Desktop/gui/jaspConfiguration/peglib/LICENSE create mode 100644 Desktop/gui/jaspConfiguration/peglib/peglib.h diff --git a/Desktop/gui/jaspConfiguration/jaspConfigurationParser.cpp b/Desktop/gui/jaspConfiguration/jaspConfigurationParser.cpp new file mode 100644 index 0000000000..c27ba4d083 --- /dev/null +++ b/Desktop/gui/jaspConfiguration/jaspConfigurationParser.cpp @@ -0,0 +1,208 @@ +#include "jaspConfigurationParser.h" +#include "log.h" + +#include + +JASPConfigurationParser* JASPConfigurationParser::_instance = nullptr; + +JASPConfigurationParser* JASPConfigurationParser::getInstance() +{ + if(!_instance) + _instance = new JASPConfigurationParser(); + return _instance; +} + +JASPConfigurationParser::JASPConfigurationParser() +{ + _parser.set_logger([](size_t line, size_t col, const std::string& msg, const std::string &rule) { + Log::log() << "JASPConfiguration parse error: line" << line << " column:" << col << ": " << msg << "\n"; + }); + + _validGrammar = _parser.load_grammar(_jaspConfigGrammar); + if(!_validGrammar) + Log::log() << "Specified Grammar not valid. Recommend peglint to investigate" << std::endl; + + _parser["JASPConf"] = parseJASPConf; + _parser["ModuleStmt"] = parseModuleStmt; + _parser["AnalysisStmt"] = parseAnalysisStmt; + _parser["OptionStmt"] = parseOptionStmt; + _parser["OptionDef"] = parseOptionDef; + _parser["LoadModuleList"] = parseLoadModuleList; + _parser["Version"] = parseVersion; + _parser["KeyValuePair"] = parseKeyValuePair; + _parser["Value"] = parseValue; + _parser["Name"] = parseName; + _parser["StringLiteral"] = parseString; + _parser["Int"] = parseInt; + _parser["Float"] = parseFloat; + _parser["Bool"] = parseBool; +} + +std::any JASPConfigurationParser::parseBool(const peg::SemanticValues &vs, std::any &dt) +{ + switch (vs.choice()) { + case 0: //true + return true; + default: //false + return false; + } +} + +bool JASPConfigurationParser::parse(JASPConfiguration* target, const QString &input) +{ + try { + std::any dt = target; + return _parser.parse(input.toStdString(), dt); + } + catch (std::exception& e) { + Log::log() << e.what() << std::endl; + } + return false; +} + +std::any JASPConfigurationParser::parseInt(const peg::SemanticValues &vs, std::any &dt) +{ + return vs.token_to_number(); +} + +std::any JASPConfigurationParser::parseFloat(const peg::SemanticValues &vs, std::any &dt) +{ + return vs.token_to_number(); +} + +std::any JASPConfigurationParser::parseString(const peg::SemanticValues &vs, std::any &dt) +{ + return QString(vs.token_to_string().c_str()); +} + +std::any JASPConfigurationParser::parseName(const peg::SemanticValues &vs, std::any &dt) +{ + return QString(vs.token_to_string().c_str()); +} + +std::any JASPConfigurationParser::parseValue(const peg::SemanticValues &vs, std::any &dt) +{ + switch (vs.choice()) { + case 0: //Bool + return QVariant(any_cast(vs[0])); + case 1: //Float + return QVariant(any_cast(vs[0])); + case 2: //Int + return QVariant(any_cast(vs[0])); + case 3: //StringLiteral + return QVariant(any_cast(vs[0])); + case 4: //Name + return QVariant(any_cast(vs[0])); + default: + return QVariant(any_cast(vs[0])); + } +} + +std::any JASPConfigurationParser::parseKeyValuePair(const peg::SemanticValues &vs, std::any& dt) +{ + return KeyValue{any_cast(vs[0]), any_cast(vs[1])}; +} + +std::any JASPConfigurationParser::parseVersion(const peg::SemanticValues &vs, std::any &dt) +{ + try + { + Version v(vs.token_to_string()); + return v; + } + catch (std::runtime_error& e) + { + throw std::runtime_error("Could not parse JASP Version number in configuration file: " + std::string(e.what())); + } +} + +std::any JASPConfigurationParser::parseLoadModuleList(const peg::SemanticValues &vs, std::any &dt) +{ + QStringList list; + for(int i = 0; i < vs.size(); i++) + list.push_back(any_cast(vs[i])); + return list; +} + +std::any JASPConfigurationParser::parseOptionDef(const peg::SemanticValues &vs, std::any &dt) +{ + switch (vs.choice()) { + case 0: //no lock + return Option{any_cast(vs[0]), false}; + default: //lock + return Option{any_cast(vs[0]), true}; + } +} + +std::any JASPConfigurationParser::parseOptionStmt(const peg::SemanticValues &vs, std::any &dt) +{ + std::vector