From 61a1f1c1126d7c0afb461f99edb16e40167c5267 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 9 Dec 2016 18:21:14 +0200 Subject: [PATCH 01/32] RedisDesktopManager 0.9.0-alpha4 - Add initial implementation of Native Formatters (#3534) - Remove build-in zlib compression/decompression - Remove php-unserialize.js formatter - Remove msgpack.js formatter - Update translations --- src/app/app.cpp | 5 + src/app/app.h | 2 + src/app/models/configmanager.cpp | 26 +- src/app/models/configmanager.h | 3 +- src/modules/value-editor/compression.cpp | 151 ------- src/modules/value-editor/compression.h | 19 - .../value-editor/formattersmanager.cpp | 254 +++++++++++ src/modules/value-editor/formattersmanager.h | 53 +++ src/modules/value-editor/valueviewmodel.cpp | 42 +- src/modules/value-editor/valueviewmodel.h | 1 - src/qml/GlobalSettings.qml | 40 +- .../bulk-operations/BulkOperationsDialog.qml | 2 +- src/qml/connections-tree/menu/key.qml | 4 +- src/qml/connections-tree/menu/namespace.qml | 4 +- src/qml/console/QConsole.qml | 2 +- src/qml/qml.qrc | 2 - src/qml/value-editor/AddKeyDialog.qml | 2 +- src/qml/value-editor/ValueTabs.qml | 4 +- .../value-editor/editors/HashItemEditor.qml | 2 +- .../value-editor/editors/MultilineEditor.qml | 110 ++--- .../editors/formatters/formatters.js | 171 +++----- .../value-editor/editors/formatters/hexy.js | 125 +----- .../editors/formatters/msgpack.js | 397 ------------------ .../editors/formatters/php-unserialize.js | 5 - src/resources/translations/rdm.ts | 68 ++- 25 files changed, 573 insertions(+), 921 deletions(-) delete mode 100644 src/modules/value-editor/compression.cpp delete mode 100644 src/modules/value-editor/compression.h create mode 100644 src/modules/value-editor/formattersmanager.cpp create mode 100644 src/modules/value-editor/formattersmanager.h delete mode 100644 src/qml/value-editor/editors/formatters/msgpack.js delete mode 100644 src/qml/value-editor/editors/formatters/php-unserialize.js diff --git a/src/app/app.cpp b/src/app/app.cpp index a86bbf455..e2464053a 100644 --- a/src/app/app.cpp +++ b/src/app/app.cpp @@ -21,6 +21,7 @@ #include "modules/value-editor/valueviewmodel.h" #include "modules/value-editor/viewmodel.h" #include "modules/value-editor/sortfilterproxymodel.h" +#include "modules/value-editor/formattersmanager.h" #include "modules/console/consolemodel.h" #include "modules/server-stats/serverstatsmodel.h" #include "modules/bulk-operations/bulkoperationsmanager.h" @@ -65,6 +66,9 @@ void Application::initModels() connect(m_connections.data(), &ConnectionsManager::openServerStats, m_serverStatsModel.data(), &TabViewModel::openTab); + + m_formattersManager = QSharedPointer(new ValueEditor::FormattersManager()); + m_formattersManager->loadFormatters(); } void Application::initAppInfo() @@ -124,6 +128,7 @@ void Application::registerQmlRootObjects() m_engine.rootContext()->setContextProperty("connectionsManager", m_connections.data()); m_engine.rootContext()->setContextProperty("viewModel", m_keyValues.data()); // TODO: Remove legacy name usage in qml m_engine.rootContext()->setContextProperty("valuesModel", m_keyValues.data()); + m_engine.rootContext()->setContextProperty("formattersManager", m_formattersManager.data()); m_engine.rootContext()->setContextProperty("consoleModel", m_consoleModel.data()); m_engine.rootContext()->setContextProperty("serverStatsModel", m_serverStatsModel.data()); m_engine.rootContext()->setContextProperty("appLogger", m_logger); diff --git a/src/app/app.h b/src/app/app.h index a8a63f671..31cd17753 100644 --- a/src/app/app.h +++ b/src/app/app.h @@ -16,6 +16,7 @@ class Updater; class LogHandler; class TabViewModel; namespace ValueEditor { class ViewModel; } +namespace ValueEditor { class FormattersManager; } namespace BulkOperations { class Manager; } @@ -48,6 +49,7 @@ private slots: QSharedPointer m_connections; QSharedPointer m_updater; QSharedPointer m_keyValues; + QSharedPointer m_formattersManager; QSharedPointer m_bulkOperations; QSharedPointer m_consoleModel; QSharedPointer m_serverStatsModel; diff --git a/src/app/models/configmanager.cpp b/src/app/models/configmanager.cpp index 09b6a644b..1cfe70361 100644 --- a/src/app/models/configmanager.cpp +++ b/src/app/models/configmanager.cpp @@ -19,16 +19,7 @@ ConfigManager::ConfigManager(const QString &basePath) QString ConfigManager::getApplicationConfigPath(const QString &configFile, bool checkPath) { - QString configDir; -#ifdef Q_OS_MACX - configDir = QDir::toNativeSeparators( - QString("%1/%2").arg(m_basePath).arg("/Library/Preferences/rdm/") - ); -#else - configDir = QDir::toNativeSeparators( - QString("%1/%2").arg(m_basePath).arg(".rdm") - ); -#endif + QString configDir = getConfigPath(); QDir settingsPath(configDir); if (!settingsPath.exists() && settingsPath.mkpath(configDir)) { @@ -189,6 +180,21 @@ void ConfigManager::setPermissions(QFile &file) #endif } +QString ConfigManager::getConfigPath() +{ + QString configDir; +#ifdef Q_OS_MACX + configDir = QDir::toNativeSeparators( + QString("%1/%2").arg(QDir::homePath()).arg("/Library/Preferences/rdm/") + ); +#else + configDir = QDir::toNativeSeparators( + QString("%1/%2").arg(QDir::homePath()).arg(".rdm") + ); +#endif + return configDir; +} + bool saveJsonArrayToFile(const QJsonArray &c, const QString &f) { QJsonDocument config(c); diff --git a/src/app/models/configmanager.h b/src/app/models/configmanager.h index c7bf8fc36..65b749591 100644 --- a/src/app/models/configmanager.h +++ b/src/app/models/configmanager.h @@ -11,11 +11,12 @@ class ConfigManager QString getApplicationConfigPath(const QString &, bool checkPath=true); bool migrateOldConfig(const QString &oldFileName, const QString &newFileName); public: + static QString getConfigPath(); static QJsonArray xmlConfigToJsonArray(const QString &xmlConfigPath); private: static bool chechPath(const QString&); - static void setPermissions(QFile&); + static void setPermissions(QFile&); private: QString m_basePath; }; diff --git a/src/modules/value-editor/compression.cpp b/src/modules/value-editor/compression.cpp deleted file mode 100644 index c093e22a5..000000000 --- a/src/modules/value-editor/compression.cpp +++ /dev/null @@ -1,151 +0,0 @@ -#include "compression.h" - -/** - * @brief Attempt to detect if the blob is compressed using the standard GZIP algorithm. May return false positives - * @param input The buffer to be compressed - * @return @c true if the input buffer is compressed. - */ -bool ValueEditor::Compression::isCompressed(QByteArray input) -{ - return !input.isEmpty() && input.size() > 2 && (unsigned char)input.at(0) == 0x1f && (unsigned char)input.at(1) == 0x8b; -} - -/** - * @brief Compresses the given buffer using the standard GZIP algorithm - * @param input The buffer to be compressed - * @param output The result of the compression - * @param level The compression level to be used (@c 0 = no compression, @c 9 = max, @c -1 = default) - * @return @c true if the compression was successful, @c false otherwise - */ -bool ValueEditor::Compression::compress(QByteArray input, QByteArray &output, int level) -{ - output.clear(); - - if(input.length()) - { - int flush = 0; - - z_stream strm; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - strm.avail_in = 0; - strm.next_in = Z_NULL; - - int ret = deflateInit2(&strm, qMax(-1, qMin(9, level)), Z_DEFLATED, WINDOW_BIT, 8, Z_DEFAULT_STRATEGY); - - if (ret != Z_OK) - return false; - - char *input_data = input.data(); - int input_data_left = input.length(); - - do { - int chunk_size = qMin(CHUNK_SIZE, input_data_left); - - strm.next_in = (unsigned char*)input_data; - strm.avail_in = chunk_size; - - input_data += chunk_size; - input_data_left -= chunk_size; - - flush = (input_data_left <= 0 ? Z_FINISH : Z_NO_FLUSH); - - do { - char out[CHUNK_SIZE]; - - strm.next_out = (unsigned char*)out; - strm.avail_out = CHUNK_SIZE; - - ret = deflate(&strm, flush); - - if(ret == Z_STREAM_ERROR) - { - deflateEnd(&strm); - return false; - } - - int have = (CHUNK_SIZE - strm.avail_out); - - if(have > 0) - output.append((char*)out, have); - } while (strm.avail_out == 0); - } while (flush != Z_FINISH); - - (void)deflateEnd(&strm); - - return ret == Z_STREAM_END; - } - return true; -} - -/** - * @brief Decompresses the given buffer using the standard GZIP algorithm - * @param input The buffer to be decompressed - * @param output The result of the decompression - * @return @c true if the decompression was successfull, @c false otherwise - */ -bool ValueEditor::Compression::decompress(QByteArray input, QByteArray &output) -{ - output.clear(); - - if(input.length() > 0) - { - z_stream strm; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - strm.avail_in = 0; - strm.next_in = Z_NULL; - - int ret = inflateInit2(&strm, WINDOW_BIT); - - if (ret != Z_OK) - return false; - - char *input_data = input.data(); - int input_data_left = input.length(); - - do { - int chunk_size = qMin(CHUNK_SIZE, input_data_left); - - if(chunk_size <= 0) - break; - - strm.next_in = (unsigned char*)input_data; - strm.avail_in = chunk_size; - - input_data += chunk_size; - input_data_left -= chunk_size; - - do { - char out[CHUNK_SIZE]; - - strm.next_out = (unsigned char*)out; - strm.avail_out = CHUNK_SIZE; - - ret = inflate(&strm, Z_NO_FLUSH); - - switch (ret) { - case Z_NEED_DICT: - ret = Z_DATA_ERROR; - case Z_DATA_ERROR: - case Z_MEM_ERROR: - case Z_STREAM_ERROR: - inflateEnd(&strm); - return false; - } - - int have = (CHUNK_SIZE - strm.avail_out); - - if(have > 0) - output.append((char*)out, have); - } while (strm.avail_out == 0); - } while (ret != Z_STREAM_END); - - inflateEnd(&strm); - - return ret == Z_STREAM_END; - } - return true; -} diff --git a/src/modules/value-editor/compression.h b/src/modules/value-editor/compression.h deleted file mode 100644 index f8184d3fa..000000000 --- a/src/modules/value-editor/compression.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include -#include - -#define WINDOW_BIT 15 + 16 -#define CHUNK_SIZE 32 * 1024 - -namespace ValueEditor { - -class Compression -{ -public: - static bool isCompressed(QByteArray input); - static bool compress(QByteArray input, QByteArray &output, int level = -1); - static bool decompress(QByteArray input, QByteArray &output); -}; - -} diff --git a/src/modules/value-editor/formattersmanager.cpp b/src/modules/value-editor/formattersmanager.cpp new file mode 100644 index 000000000..0dcabeef0 --- /dev/null +++ b/src/modules/value-editor/formattersmanager.cpp @@ -0,0 +1,254 @@ +#include "formattersmanager.h" +#include "app/models/configmanager.h" + +#include +#include +#include +#include +#include +#include +#include + +ValueEditor::FormattersManager::FormattersManager() +{ + +} + +QByteArray readStdoutFromExternalProcess(const QStringList& cmd, const QString& wd) +{ + QProcess formatterProcess; + formatterProcess.setWorkingDirectory(wd); + formatterProcess.start(cmd[0], cmd.mid(1)); + + if (!formatterProcess.waitForStarted(3000)) { + LOG(ERROR) << QString("Cannot start process %1: ").arg(cmd.join(" ")) + << formatterProcess.errorString(); + return QByteArray(); + } + + if (!formatterProcess.waitForFinished(3000)) { + formatterProcess.kill(); + LOG(ERROR) << QString("Process %1 was killed by timeout: ").arg(cmd.join(" ")) + << formatterProcess.errorString(); + return QByteArray(); + } + + return formatterProcess.readAllStandardOutput(); +} + + +QJsonObject readJsonFromExternalProcess(const QStringList& cmd, const QString& wd) +{ + QByteArray result = readStdoutFromExternalProcess(cmd, wd); + + if (result.isEmpty()) + return QJsonObject(); + + QJsonParseError err; + QJsonDocument output = QJsonDocument::fromJson(result, &err); + + if (err.error != QJsonParseError::NoError + || !output.isObject()) { + LOG(ERROR) << QString("Formatter returned invalid json"); + return QJsonObject(); + } + + return output.object(); +} + + +void ValueEditor::FormattersManager::loadFormatters(bool ignoreCache) +{ + if (!ignoreCache) { + // Try to load data from cache + //fillMapping(); + // if still empty - continue loading + } + + // Load formatters from file system + + QDir fd(formattersPath()); + fd.setFilter(QDir::NoDotAndDotDot | QDir::Dirs); + + if (!fd.exists() && fd.mkpath(formattersPath())) { + qDebug() << "Formatters Dir created"; + } + + QDirIterator it(fd); + + while (it.hasNext()) { + it.next(); + + QFile usageFile(QString("%1/%2").arg(it.filePath()).arg("usage.json")); + + if (usageFile.exists() && usageFile.open(QIODevice::ReadOnly)) { + QString usageRawJson = QString(usageFile.readAll()).simplified(); + + QJsonParseError err; + QJsonDocument cmd = QJsonDocument::fromJson(usageRawJson.toUtf8(), &err); + + if (err.error != QJsonParseError::NoError + || !cmd.isArray()) { + LOG(ERROR) << QString("Formatter %1 has invalid usage.json file"); + continue; + } + + QStringList fullCmd = cmd.toVariant().toStringList(); + QStringList versionCmd(fullCmd); + versionCmd.append("--version"); + + QByteArray result = readStdoutFromExternalProcess(versionCmd, it.filePath()); + + if (result.isEmpty()) + continue; + + QVariantMap data; + data["name"] = it.fileName(); + data["version"] = result; + data["cmd"] = fullCmd.join(" "); + data["cmd_list"] = fullCmd; + data["cwd"] = it.filePath(); + + m_formattersData.append(data); + usageFile.close(); + } + } + + fillMapping(); +} + +int ValueEditor::FormattersManager::rowCount(const QModelIndex &) const +{ + return m_formattersData.size(); +} + +QVariant ValueEditor::FormattersManager::data(const QModelIndex &index, int role) const +{ + if (!(0 <= index.row() && index.row() < rowCount())) { + return QVariant(); + } + + QVariantMap data = m_formattersData[index.row()]; + + if (role == name) { + return data["name"]; + } else if (role == version) { + return data["version"]; + } else if (role == cmd) { + return data["cmd"]; + } + + return QVariant(); +} + +QHash ValueEditor::FormattersManager::roleNames() const +{ + QHash roles; + roles[name] = "name"; + roles[version] = "version"; + roles[cmd] = "cmd"; + return roles; +} + +void ValueEditor::FormattersManager::decode(const QString &formatterName, const QByteArray &data, + QJSValue jsCallback) +{ + if (!m_mapping.contains(formatterName)) { + emit error(QObject::tr("Can't find formatter with name: %1").arg(formatterName)); + return; + } + + QVariantMap formatter = m_formattersData[m_mapping[formatterName]]; + + QStringList cmd = formatter["cmd_list"].toStringList(); + cmd.append("decode"); + cmd.append(data.toBase64()); + + QJsonObject outputObj = readJsonFromExternalProcess(cmd, formatter["cwd"].toString()); + + if (outputObj.isEmpty()) { + emit error(QObject::tr("Cannot decode value using %1 formatter. See log for more details.").arg(formatterName)); + return; + } + + if (jsCallback.isCallable()) { + jsCallback.call(QJSValueList { outputObj["output"].toString(), + outputObj["read-only"].toBool(), + outputObj["format"].toString() }); + } +} + +void ValueEditor::FormattersManager::isValid(const QString &formatterName, const QByteArray &data, + QJSValue jsCallback) +{ + if (!m_mapping.contains(formatterName)) { + emit error(QObject::tr("Can't find formatter with name: %1").arg(formatterName)); + return; + } + + QVariantMap formatter = m_formattersData[m_mapping[formatterName]]; + + QStringList cmd = formatter["cmd_list"].toStringList(); + cmd.append("is_valid"); + cmd.append(data.toBase64()); + + QJsonObject outputObj = readJsonFromExternalProcess(cmd, formatter["cwd"].toString()); + + if (outputObj.isEmpty()) { + emit error(QObject::tr("Cannot validate value using %1 formatter. See log for more details.").arg(formatterName)); + return; + } + + if (jsCallback.isCallable()) { + jsCallback.call(QJSValueList { outputObj["valid"].toBool(), + outputObj["message"].toString() }); + } +} + +void ValueEditor::FormattersManager::encode(const QString &formatterName, const QByteArray &data, + QJSValue jsCallback) +{ + if (!m_mapping.contains(formatterName)) { + emit error(QObject::tr("Can't find formatter with name: %1").arg(formatterName)); + return; + } + + QVariantMap formatter = m_formattersData[m_mapping[formatterName]]; + + QStringList cmd = formatter["cmd_list"].toStringList(); + cmd.append("encode"); + cmd.append(data.toBase64()); + + QByteArray output = readStdoutFromExternalProcess(cmd, formatter["cwd"].toString()); + + if (output.isEmpty()) { + emit error(QObject::tr("Cannot encode value using %1 formatter. See log for more details.").arg(formatterName)); + return; + } + + if (jsCallback.isCallable()) { + jsCallback.call(QJSValueList { QString::fromUtf8(output) }); + } +} + +QStringList ValueEditor::FormattersManager::getPlainList() +{ + return m_mapping.keys(); +} + +QString ValueEditor::FormattersManager::formattersPath() +{ + return QDir::toNativeSeparators( + QString("%1/%2").arg(ConfigManager::getConfigPath()).arg("formatters") + ); +} + +void ValueEditor::FormattersManager::fillMapping() +{ + int index = 0; + + for (QVariantMap f : m_formattersData) { + m_mapping[f["name"].toString()] = index; + index++; + } +} diff --git a/src/modules/value-editor/formattersmanager.h b/src/modules/value-editor/formattersmanager.h new file mode 100644 index 000000000..7fff05917 --- /dev/null +++ b/src/modules/value-editor/formattersmanager.h @@ -0,0 +1,53 @@ +#pragma once +#include +#include + +namespace ValueEditor { + +class FormattersManager : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { name = Qt::UserRole + 1, version, cmd }; + +public: + FormattersManager(); + + void loadFormatters(bool ignoreCache=false); // TODO make async with callback & invokable + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + QVariant data(const QModelIndex &index, int role) const; + + QHash roleNames() const; + +signals: + void error(const QString& msg); + +public: + Q_INVOKABLE void decode(const QString& formatterName, + const QByteArray& data, + QJSValue jsCallback); + + Q_INVOKABLE void isValid(const QString& formatterName, + const QByteArray& data, + QJSValue jsCallback); + + Q_INVOKABLE void encode(const QString& formatterName, + const QByteArray& data, + QJSValue jsCallback); + + Q_INVOKABLE QStringList getPlainList(); + + Q_INVOKABLE QString formattersPath(); + +private: + void fillMapping(); + +private: + QList m_formattersData; + QHash m_mapping; +}; + +} diff --git a/src/modules/value-editor/valueviewmodel.cpp b/src/modules/value-editor/valueviewmodel.cpp index 25611e924..156ed3321 100644 --- a/src/modules/value-editor/valueviewmodel.cpp +++ b/src/modules/value-editor/valueviewmodel.cpp @@ -124,35 +124,12 @@ void ValueEditor::ValueViewModel::updateRow(int i, const QVariantMap &row) if (targetRow < 0 || !m_model->isRowLoaded(targetRow)) return; - QVariantMap res = getRowRaw(i); - // check original value to avoid maintaining state - bool updated = false; - - if (ValueEditor::Compression::isCompressed(res["value"].toByteArray())) { - QByteArray output; - if (!ValueEditor::Compression::compress(row["value"].toByteArray(), output)) { - qDebug() << "Compression failed"; - } else { - qDebug() << "Compression succeeded"; - QVariantMap compressedRow; - compressedRow["value"] = QVariant(output); - - try { - m_model->updateRow(targetRow, compressedRow); - updated = true; - } catch(const Model::Exception& e) { - emit error(QString(e.what())); - } - } + try { + m_model->updateRow(targetRow, row); + } catch(const Model::Exception& e) { + emit error(QString(e.what())); } - if (!updated) { - try { - m_model->updateRow(targetRow, row); - } catch(const Model::Exception& e) { - emit error(QString(e.what())); - } - } emit dataChanged(index(i, 0), index(i, 0)); } @@ -196,17 +173,6 @@ QVariantMap ValueEditor::ValueViewModel::getRow(int row) return QVariantMap(); QVariantMap res = getRowRaw(row); - bool compressed = ValueEditor::Compression::isCompressed(res["value"].toByteArray()); - qDebug() << "Is compressed: " << compressed; - if (compressed) { - QByteArray output; - if (!ValueEditor::Compression::decompress(res["value"].toByteArray(), output)) { - qDebug() << "Decompression failed"; - } else { - qDebug() << "Decompression succeeded"; - res["value"] = QVariant(output); - } - } return res; } diff --git a/src/modules/value-editor/valueviewmodel.h b/src/modules/value-editor/valueviewmodel.h index 5c44485c9..2ead9352f 100644 --- a/src/modules/value-editor/valueviewmodel.h +++ b/src/modules/value-editor/valueviewmodel.h @@ -3,7 +3,6 @@ #include #include #include "keymodel.h" -#include "compression.h" namespace ValueEditor { diff --git a/src/qml/GlobalSettings.qml b/src/qml/GlobalSettings.qml index 6562064e0..951bb3224 100644 --- a/src/qml/GlobalSettings.qml +++ b/src/qml/GlobalSettings.qml @@ -12,7 +12,7 @@ Dialog { contentItem: Item { implicitWidth: 800 - implicitHeight: 600 + implicitHeight: 750 ColumnLayout { anchors.fill: parent @@ -117,6 +117,37 @@ Dialog { description: "" } + + Text { + text: qsTr("Custom Value View Formatters") + font.pixelSize: 20 + } + + Text { + text: qsTr("Formatters path: %0").arg(formattersManager.formattersPath()) + font.pixelSize: 12 + color: "grey" + } + + TableView { + Layout.fillWidth: true + + TableViewColumn { + role: "name" + title: "Name" + } + TableViewColumn { + role: "version" + title: "Version" + } + TableViewColumn { + role: "cmd" + title: "Command" + } + + model: formattersManager + } + Item { Layout.fillHeight: true } @@ -145,4 +176,11 @@ Dialog { property alias appFontSize: appFontSize.value property alias locale: appLang.value } + + Settings { + id: customFormatters + category: "formatters" + + property var formatters + } } diff --git a/src/qml/bulk-operations/BulkOperationsDialog.qml b/src/qml/bulk-operations/BulkOperationsDialog.qml index d796ef24e..a34778404 100644 --- a/src/qml/bulk-operations/BulkOperationsDialog.qml +++ b/src/qml/bulk-operations/BulkOperationsDialog.qml @@ -5,7 +5,7 @@ import QtQuick.Dialogs 1.2 Dialog { id: root - title: "Bulk Operations Manager" + title: qsTr("Bulk Operations Manager") modality: Qt.ApplicationModal property string operationName: "delete_keys" diff --git a/src/qml/connections-tree/menu/key.qml b/src/qml/connections-tree/menu/key.qml index 0e91f956c..801750715 100644 --- a/src/qml/connections-tree/menu/key.qml +++ b/src/qml/connections-tree/menu/key.qml @@ -23,8 +23,8 @@ RowLayout { model: [ - {'icon': "qrc:/images/copy.svg", "callback": "copy", "help": "Copy Key Name"}, - {'icon': "qrc:/images/delete.svg", "event": "delete", "help": "Delete Key"} + {'icon': "qrc:/images/copy.svg", "callback": "copy", "help": qsTr("Copy Key Name")}, + {'icon': "qrc:/images/delete.svg", "event": "delete", "help": qsTr("Delete Key")} ] } diff --git a/src/qml/connections-tree/menu/namespace.qml b/src/qml/connections-tree/menu/namespace.qml index 25a10371d..4cc4468f2 100644 --- a/src/qml/connections-tree/menu/namespace.qml +++ b/src/qml/connections-tree/menu/namespace.qml @@ -23,8 +23,8 @@ RowLayout { model: [ - {'icon': "qrc:/images/copy.svg", "callback": "copy", "help": "Copy Namespace Pattern"}, - {'icon': "qrc:/images/delete.svg", "event": "delete", "help": "Delete Namespace"} + {'icon': "qrc:/images/copy.svg", "callback": "copy", "help": qsTr("Copy Namespace Pattern")}, + {'icon': "qrc:/images/delete.svg", "event": "delete", "help": qsTr("Delete Namespace")} ] } diff --git a/src/qml/console/QConsole.qml b/src/qml/console/QConsole.qml index 7bfa88945..0899d2d14 100644 --- a/src/qml/console/QConsole.qml +++ b/src/qml/console/QConsole.qml @@ -14,7 +14,7 @@ Rectangle { property string initText: "RDM Redis Console
" + - "Connecting ..." + qsTr("Connecting...") function setPrompt(txt, display) { diff --git a/src/qml/qml.qrc b/src/qml/qml.qrc index 2c75ba492..e955fd1ef 100644 --- a/src/qml/qml.qrc +++ b/src/qml/qml.qrc @@ -24,8 +24,6 @@ value-editor/editors/AbstractEditor.qml value-editor/editors/MultilineEditor.qml value-editor/editors/formatters/formatters.js - value-editor/editors/formatters/msgpack.js - value-editor/editors/formatters/php-unserialize.js value-editor/editors/formatters/hexy.js value-editor/editors/editor.js 3rdparty/php-unserialize-js/phpUnserialize.js diff --git a/src/qml/value-editor/AddKeyDialog.qml b/src/qml/value-editor/AddKeyDialog.qml index 63eedbc5f..d49338a84 100644 --- a/src/qml/value-editor/AddKeyDialog.qml +++ b/src/qml/value-editor/AddKeyDialog.qml @@ -8,7 +8,7 @@ import "./editors/editor.js" as Editor Dialog { id: root - title: "Add New Key" + title: qsTr("Add New Key") width: 550 height: 500 modality: Qt.ApplicationModal diff --git a/src/qml/value-editor/ValueTabs.qml b/src/qml/value-editor/ValueTabs.qml index d2a06f1fd..fef314302 100644 --- a/src/qml/value-editor/ValueTabs.qml +++ b/src/qml/value-editor/ValueTabs.qml @@ -130,11 +130,11 @@ Repeater { Item { Layout.preferredWidth: 5} Button { - text: "Rename" + text: qsTr("Rename") Dialog { id: renameConfirmation - title: "Rename key" + title: qsTr("Rename key") width: 520 diff --git a/src/qml/value-editor/editors/HashItemEditor.qml b/src/qml/value-editor/editors/HashItemEditor.qml index 456826935..d4fefec55 100644 --- a/src/qml/value-editor/editors/HashItemEditor.qml +++ b/src/qml/value-editor/editors/HashItemEditor.qml @@ -12,7 +12,7 @@ AbstractEditor { MultilineEditor { id: keyText - fieldLabel: "Key:" + fieldLabel: qsTr("Key:") Layout.fillWidth: true Layout.minimumHeight: 80 Layout.preferredHeight: 90 diff --git a/src/qml/value-editor/editors/MultilineEditor.qml b/src/qml/value-editor/editors/MultilineEditor.qml index 24ccdf507..3e940373c 100644 --- a/src/qml/value-editor/editors/MultilineEditor.qml +++ b/src/qml/value-editor/editors/MultilineEditor.qml @@ -13,29 +13,53 @@ ColumnLayout property alias textColor: textArea.textColor property alias style: textArea.style property bool showFormatters: true - property string fieldLabel: "Value:" + property string fieldLabel: qsTr("Value:") property var value function getText() { - if (textArea.formatter.binary) - return binaryUtils.binaryListToValue(textArea.formatter.getRaw(textArea.text)) - else - return textArea.formatter.getRaw(textArea.text) + return textArea.formatter.getRaw(textArea.text) } function setValue(val) { value = val - var isBin = binaryUtils.isBinaryString(val) + loadFormattedValue() + } + + function loadFormattedValue() { + var isBin = binaryUtils.isBinaryString(root.value) binaryFlag.visible = false + textArea.textFormat = TextEdit.PlainText - if (isBin) binaryFlag.visible = true + if (isBin) binaryFlag.visible = true - autoDetectFormatter(isBin) - } + // FIXME: autoDetectFormatter + + var formatter = formatterSelector.model[formatterSelector.currentIndex] + + uiBlocker.visible = true + + formatter.instance.getFormatted(root.value, function (formatted, isReadOnly, format) { + + if (isReadOnly) { + textArea.readOnlyValue = true + } - function autoDetectFormatter(isBinary) { - formatterSelector.currentIndex = Formatters.guessFormatter(isBinary, value) + if (format == "json") { + // 1 is JSON + return formatterSelector.model[1].instance.getFormatted(formatted, function (formattedJson, r, f) { + textArea.text = formattedJson + uiBlocker.visible = false + }) + } else { + if (format == "html") + textArea.textFormat = TextEdit.RichText + + textArea.text = formatted + } + + uiBlocker.visible = false + }) } RowLayout{ @@ -43,32 +67,22 @@ ColumnLayout Layout.fillWidth: true Text { text: root.fieldLabel } - Text { id: binaryFlag; text: qsTr("[Binary]"); visible: false; color: "green"; } - Text { id: compressedFlag; text: qsTr("[GZIP compressed]"); visible: false; color: "red"; } // TBD + Text { id: binaryFlag; text: qsTr("[Binary]"); visible: false; color: "green"; } Item { Layout.fillWidth: true } Text { text: "View as:" } ComboBox { id: formatterSelector width: 200 - model: formattersModel + model: Formatters.buildFormattersModel() textRole: "name" - onCurrentIndexChanged: Formatters.defaultFormatterIndex = currentIndex - Component.onCompleted: currentIndex = Formatters.defaultFormatterIndex - } - - ListModel { - id: formattersModel - - Component.onCompleted: { - for (var index in Formatters.enabledFormatters) { - var f = Formatters.enabledFormatters[index] - var title = f.readOnly ? f.title + " (READ ONLY)" : f.title - append({'name': title}) - } + onCurrentIndexChanged: { + Formatters.defaultFormatterIndex = currentIndex + loadFormattedValue() } - } + Component.onCompleted: currentIndex = Formatters.defaultFormatterIndex + } } TextArea @@ -77,33 +91,29 @@ ColumnLayout Layout.fillWidth: true Layout.fillHeight: true Layout.preferredHeight: 100 - textFormat: formatter && formatter.htmlOutput ? TextEdit.RichText : TextEdit.PlainText - readOnly: (formatter)? formatter.readOnly : enabled ? true : false - - onEnabledChanged: { - console.log("Text editor was disabled") - } - text: { - if (!formatter || !value) - return '' + readOnly: (readOnlyValue)? readOnlyValue : enabled ? true : false - if (formatter.binary === true) { - return formatter.getFormatted(binaryUtils.valueToBinary(value)) || '' - } else { - return formatter.getFormatted(binaryUtils.toUtf(value)) || '' - } - } + property bool readOnlyValue: false - property var formatter: { - var index = formatterSelector.currentIndex ? formatterSelector.currentIndex : Formatters.defaultFormatterIndex - return Formatters.enabledFormatters[index] - } + style: TextAreaStyle { renderType: Text.QtRendering } - style: TextAreaStyle { - renderType: Text.QtRendering - } font { family: monospacedFont.name; pointSize: 12 } + wrapMode: TextEdit.WrapAnywhere } + + Rectangle { + id: uiBlocker + visible: false + anchors.fill: parent + color: Qt.rgba(0, 0, 0, 0.1) + + Item { + anchors.fill: parent + BusyIndicator { anchors.centerIn: parent; running: true } + } + + MouseArea { anchors.fill: parent } + } } diff --git a/src/qml/value-editor/editors/formatters/formatters.js b/src/qml/value-editor/editors/formatters/formatters.js index 67444ef61..f976b3024 100644 --- a/src/qml/value-editor/editors/formatters/formatters.js +++ b/src/qml/value-editor/editors/formatters/formatters.js @@ -1,68 +1,56 @@ -.import "./msgpack.js" as MsgPack .import "./hexy.js" as Hexy -.import "./php-unserialize.js" as PHPUnserialize .import "./json-tools.js" as JSONFormatter +var FORMAT_PLAIN_TEXT = "plain_text" +var FORMAT_JSON = "json" +var FORMAT_HTML = "html" + /** Plain formatter **/ var plain = { title: "Plain Text", - readOnly: false, - binary: false, - htmlOutput: false, - getFormatted: function (raw) { - return raw + getFormatted: function (raw, callback) { + return callback(raw, false, FORMAT_PLAIN_TEXT) }, - isValid: function (raw) { - return true + isValid: function (raw, callback) { + return callback(true, "") }, - getRaw: function (formatted) { - return formatted + getRaw: function (formatted, callback) { + return callback(formatted) } } var hex = { title: "HEX", - readOnly: false, - binary: true, - htmlOutput: false, - getFormatted: function (raw) { - return binaryUtils.printable(binaryUtils.binaryListToValue(raw)) + getFormatted: function (raw, callback) { + return callback(binaryUtils.printable(raw), false, FORMAT_PLAIN_TEXT) }, - isValid: function (raw) { - return binaryUtils.isBinaryString(binaryUtils.binaryListToValue(raw)) + isValid: function (raw, callback) { + return callback(binaryUtils.isBinaryString(raw), "") }, - getRaw: function (formatted) { - return binaryUtils.valueToBinary(binaryUtils.printableToValue(formatted)) + getRaw: function (formatted, callback) { + return callback(binaryUtils.printableToValue(formatted)) } } var hexTable = { title: "HEX TABLE", - readOnly: true, - binary: true, - htmlOutput: true, - getFormatted: function (raw) { - var format = {'html': true} - return Hexy.hexy(raw, format) + getFormatted: function (raw, callback) { + return callback(Hexy.hexy(binaryUtils.valueToBinary(raw), {'html': true}), true, FORMAT_HTML) }, - isValid: function (raw) { - return true + isValid: function (raw, callback) { + return callback(binaryUtils.isBinaryString(raw), "") }, - - getRaw: function (formatted) { - return '' - } } /** @@ -70,119 +58,86 @@ var hexTable = { **/ var json = { title: "JSON", - readOnly: false, - binary: false, - htmlOutput: false, - getFormatted: function (raw) { + getFormatted: function (raw, callback) { try { - return JSONFormatter.prettyPrint(raw) + return callback(JSONFormatter.prettyPrint(raw), false, FORMAT_PLAIN_TEXT) } catch (e) { - return "Error: Invalid JSON" + return callback("Error: Invalid JSON") } }, - isValid: function (raw) { + isValid: function (raw, callback) { try { JSON.parse(raw) - return true + return callback(true) } catch (e) { - return false + return callback(false) } }, - getRaw: function (formatted) { + getRaw: function (formatted, callback) { try { - return JSONFormatter.minify(formatted) + return callback(JSONFormatter.minify(formatted)) } catch (e) { - return formatted + return callback(formatted) } } } -/** - MsgPack formatter -**/ -var msgpack = { - title: "MSGPACK", - readOnly: false, - binary: true, - htmlOutput: false, - - getFormatted: function (raw) { - try { - var parsed = MsgPack.msgpack().unpack(raw) - console.log('parsed msgpack:', parsed) - return JSON.stringify(parsed, undefined, 4) +var defaultFormatterIndex = 0; +var enabledFormatters = [plain, json, hex, hexTable] - } catch (e) { - return "Error: Invalid MSGPack or JSON" + e - } - }, - isValid: function (raw) { - try { - return MsgPack.msgpack().unpack(raw) !== undefined - } catch (e) { - return false - } - }, +function buildFormattersModel() +{ + var formatters = [] - getRaw: function (formatted) { - var obj = JSON.parse(formatted) - var compressed = MsgPack.msgpack().pack(obj) - return compressed + for (var index in enabledFormatters) { + var f = enabledFormatters[index] + formatters.push({'name': f.title, 'type': "buildin", "instance": f}) } -} - -/** - PHP Serialize formatter -**/ -var phpserialized = { - title: "PHP Serializer", - readOnly: true, - binary: false, - htmlOutput: false, - getFormatted: function (raw) { + var nativeFormatters = formattersManager.getPlainList(); - try { - var parsed = PHPUnserialize.unserialize(raw) - console.log('parsed php serialized:', JSON.stringify(parsed)) - return JSON.stringify(parsed, undefined, 4) + function createWrapperForNativeFormatter(formatterName) { + return { + getFormatted: function (raw, callback) { + return formattersManager.decode(formatterName, raw, callback) + }, - } catch (e) { - return "Error: Invalid PHP Serialized String: " + JSON.stringify(e) - } - }, + isValid: function (raw, callback) { + return formattersManager.isValid(formatterName, raw, callback) + }, - isValid: function (raw) { - try { - PHPUnserialize.unserialize(raw) - return true - } catch (e) { - return false + getRaw: function (formatted, callback) { + return formattersManager.encode(formatterName, formatted, callback) + } } - }, + } - getRaw: function (formatted) { - var obj = JSON.parse(formatted) - return PHPSerialize.serialize(obj) + for (var index in nativeFormatters) { + formatters.push({ + 'name': nativeFormatters[index], + 'type': "external", + 'instance': createWrapperForNativeFormatter(nativeFormatters[index]) + }) } -} -var defaultFormatterIndex = 0; -var enabledFormatters = [plain, json, msgpack, hex, hexTable, phpserialized] + return formatters +} -function guessFormatter(isBinary, value) +function guessFormatter(formatters, isBinary, value) { // NOTE(u_glide): Use hex or plain formatter if value is large if (binaryUtils.binaryStringLength(value) > 100000) { - return isBinary? 3 : 0 + return isBinary? 2 : 0 } - var tryFormatters = isBinary? [2, 5, 3, 4] : [1, 5, 2] + // TODO: use native formatters to guess value format + + var tryFormatters = isBinary? [2] : [1] for (var index in tryFormatters) { var val = (enabledFormatters[tryFormatters[index]].binary) ? diff --git a/src/qml/value-editor/editors/formatters/hexy.js b/src/qml/value-editor/editors/formatters/hexy.js index 5572f5a18..edc133744 100644 --- a/src/qml/value-editor/editors/formatters/hexy.js +++ b/src/qml/value-editor/editors/formatters/hexy.js @@ -1,127 +1,5 @@ -//= hexy.js -- utility to create hex dumps -// -// `hexy` is a javascript (node) library that's easy to use to create hex -// dumps from within node. It contains a number of options to configure -// how the hex dump will end up looking. -// -// It should create a pleasant looking hex dumb by default: -// -// var hexy = require('hexy'), -// b = new Buffer("\000\001\003\005\037\012\011bcdefghijklmnopqrstuvwxyz0123456789") -// -// console.log(hexy.hexy(b)) -// -// results in this dump: -// -// 00000000: 0001 0305 1f0a 0962 6364 6566 6768 696a .......bcdefghij -// 00000010: 6b6c 6d6e 6f70 7172 7374 7576 7778 797a klmnopqrstuvwxyz -// 00000020: 3031 3233 3435 3637 3839 0123456789 -// -// but it's also possible to configure: -// -// * Line numbering -// * Line width -// * Format of byte grouping -// * Case of hex decimals -// * Presence of the ASCII annotation in the right column. -// -// This mean it's easy to generate exciting dumps like: -// -// 0000000: 0001 0305 1f0a 0962 .... ...b -// 0000008: 6364 6566 6768 696a cdef ghij -// 0000010: 6b6c 6d6e 6f70 7172 klmn opqr -// 0000018: 7374 7576 7778 797a stuv wxyz -// 0000020: 3031 3233 3435 3637 0123 4567 -// 0000028: 3839 89 -// -// or even: -// -// 0000000: 00 01 03 05 1f 0a 09 62 63 64 65 66 67 68 69 6a -// 0000010: 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a -// 0000020: 30 31 32 33 34 35 36 37 38 39 -// -// with hexy! -// -// Formatting options are configured by passing a `format` object to the `hexy` function: -// -// var format = {} -// format.width = width // how many bytes per line, default 16 -// format.numbering = n // ["hex_bytes" | "none"], default "hex_bytes" -// format.format = f // ["fours"|"twos"|"none"], how many nibbles per group -// // default "fours" -// format.caps = c // ["lower"|"upper"], default lower -// format.annotate=a // ["ascii"|"none"], ascii annotation at end of line? -// // default "ascii" -// format.prefix=p // something pretty to put in front of each line -// // default "" -// format.indent=i // number of spaces to indent -// // default 0 -// format.offset // offset into the buffer to start -// format.length // number of bytes to display -// format.display_offset // modifiy the starting address by the indicated -// // number of bytes -// format.html=true // funky html divs 'n stuff! experimental. -// // default: false -// -// console.log(hexy.hexy(buffer, format)) -// -// In case you're really nerdy, you'll have noticed that the defaults correspond -// to how `xxd` formats it's output. -// -// -//== Installing -// -// Either use `npm`: -// -// npm install hexy -// -// This will install the lib which you'll be able to use like so: -// -// var hexy = require("hexy"), -// buf = // get Buffer from somewhere, -// str = hexy.hexy(buf) -// -// It will also install `hexy` into your path in case you're totally fed up -// with using `xxd`. -// -// -// If you don't like `npm`, grab the source from github: -// +// BASED ON: = hexy.js -- utility to create hex dumps // http://github.com/a2800276/hexy.js -// -//== TODOS -// -// The current version only pretty prints node Buffer and JS Strings. This -// should be expanded to also do typed arrays, Streams/series of Buffers -// which would be nice so you don't have to collect the whole things you -// want to pretty print in memory, and such. -// -// I'd like to improve html rendering, e.g. to be able to mouse over the -// ascii annotation and highlight the hex byte and vice versa, improve -// browser integration and set up a proper build & packaging system. -// -// -//== Thanks -// -//* Thanks to Isaac Schlueter [isaacs] for gratiously lending a hand and -//cheering me up. -//* dodo (http://coderwall.com/dodo) -// -// -//== History -// -// This is a fairly straightforward port of `hexy.rb` which does more or less the -// same thing. You can find it here: -// -// http://github.com/a2800276/hexy -// -// in case these sorts of things interest you. -// -//== Mail -// -// In case you discover bugs, spelling errors, offer suggestions for -// improvements or would like to help out with the project, you can contact -// me directly (tim@kuriositaet.de). var hexy = function (buffer, config) { var h = new Hexy(buffer, config) @@ -282,4 +160,3 @@ var Hexy = function (buffer, config) { return str } } - diff --git a/src/qml/value-editor/editors/formatters/msgpack.js b/src/qml/value-editor/editors/formatters/msgpack.js deleted file mode 100644 index 96aab01f2..000000000 --- a/src/qml/value-editor/editors/formatters/msgpack.js +++ /dev/null @@ -1,397 +0,0 @@ -/*!{id:msgpack.js,ver:1.05,license:"MIT",author:"uupaa.js@gmail.com"}*/ - -// === msgpack === -// MessagePack -> http://msgpack.sourceforge.net/ - -function msgpack() { - - var _bin2num = {}, // BinaryStringToNumber { "\00": 0, ... "\ff": 255 } - _num2bin = {}, // NumberToBinaryString { 0: "\00", ... 255: "\ff" } - _num2b64 = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ" + - "abcdefghijklmnopqrstuvwxyz0123456789+/").split(""), - _buf = [], // decode buffer - _idx = 0, // decode buffer[index] - _error = 0, // msgpack.pack() error code. 1 = CYCLIC_REFERENCE_ERROR - _isArray = Array.isArray || (function(mix) { - return Object.prototype.toString.call(mix) === "[object Array]"; - }), - _toString = String.fromCharCode, // CharCode/ByteArray to String - _MAX_DEPTH = 512; - - // msgpack.pack - function msgpackpack(data // @param Mix: - ) { - // @return ByteArray/false: - // false is error return - - _error = 0; - - var byteArray = encode([], data, 0); - console.log("msgpack array:", byteArray) - - return _error ? false : byteArray; - } - - // msgpack.unpack - function msgpackunpack(data) { // @param BinaryString/ByteArray: - // @return Mix/undefined: - // undefined is error return - // [1][String to mix] msgpack.unpack("...") -> {} - // [2][ByteArray to mix] msgpack.unpack([...]) -> {} - - _buf = data; - _idx = -1; - var result = decode(); // mix or undefined - - // Strict parsing - all data should be consumed - if (result !== undefined && _idx != data.length - 1) - return undefined - - return result - } - - // inner - encoder - function encode(rv, // @param ByteArray: result - mix, // @param Mix: source data - depth) { // @param Number: depth - var size, i, iz, c, pos, // for UTF8.encode, Array.encode, Hash.encode - high, low, sign, exp, frac; // for IEEE754 - - if (mix == null) { // null or undefined -> 0xc0 ( null ) - rv.push(0xc0); - } else if (mix === false) { // false -> 0xc2 ( false ) - rv.push(0xc2); - } else if (mix === true) { // true -> 0xc3 ( true ) - rv.push(0xc3); - } else { - switch (typeof mix) { - case "number": - if (mix !== mix) { // isNaN - rv.push(0xcb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff); // quiet NaN - } else if (mix === Infinity) { - rv.push(0xcb, 0x7f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); // positive infinity - } else if (Math.floor(mix) === mix) { // int or uint - if (mix < 0) { - // int - if (mix >= -32) { // negative fixnum - rv.push(0xe0 + mix + 32); - } else if (mix > -0x80) { - rv.push(0xd0, mix + 0x100); - } else if (mix > -0x8000) { - mix += 0x10000; - rv.push(0xd1, mix >> 8, mix & 0xff); - } else if (mix > -0x80000000) { - mix += 0x100000000; - rv.push(0xd2, mix >>> 24, (mix >> 16) & 0xff, - (mix >> 8) & 0xff, mix & 0xff); - } else { - high = Math.floor(mix / 0x100000000); - low = mix & 0xffffffff; - rv.push(0xd3, (high >> 24) & 0xff, (high >> 16) & 0xff, - (high >> 8) & 0xff, high & 0xff, - (low >> 24) & 0xff, (low >> 16) & 0xff, - (low >> 8) & 0xff, low & 0xff); - } - } else { - // uint - if (mix < 0x80) { - rv.push(mix); // positive fixnum - } else if (mix < 0x100) { // uint 8 - rv.push(0xcc, mix); - } else if (mix < 0x10000) { // uint 16 - rv.push(0xcd, mix >> 8, mix & 0xff); - } else if (mix < 0x100000000) { // uint 32 - rv.push(0xce, mix >>> 24, (mix >> 16) & 0xff, - (mix >> 8) & 0xff, mix & 0xff); - } else { - high = Math.floor(mix / 0x100000000); - low = mix & 0xffffffff; - rv.push(0xcf, (high >> 24) & 0xff, (high >> 16) & 0xff, - (high >> 8) & 0xff, high & 0xff, - (low >> 24) & 0xff, (low >> 16) & 0xff, - (low >> 8) & 0xff, low & 0xff); - } - } - } else { // double - // THX!! @edvakf - // http://javascript.g.hatena.ne.jp/edvakf/20101128/1291000731 - sign = mix < 0; - if (sign) mix *= -1; - - // add offset 1023 to ensure positive - // 0.6931471805599453 = Math.LN2; - exp = ((Math.log(mix) / 0.6931471805599453) + 1023) | 0; - - // shift 52 - (exp - 1023) bits to make integer part exactly 53 bits, - // then throw away trash less than decimal point - frac = mix * Math.pow(2, 52 + 1023 - exp); - - // S+-Exp(11)--++-----------------Fraction(52bits)-----------------------+ - // || || | - // v+----------++--------------------------------------------------------+ - // 00000000|00000000|00000000|00000000|00000000|00000000|00000000|00000000 - // 6 5 55 4 4 3 2 1 8 0 - // 3 6 21 8 0 2 4 6 - // - // +----------high(32bits)-----------+ +----------low(32bits)------------+ - // | | | | - // +---------------------------------+ +---------------------------------+ - // 3 2 21 1 8 0 - // 1 4 09 6 - low = frac & 0xffffffff; - if (sign) exp |= 0x800; - high = ((frac / 0x100000000) & 0xfffff) | (exp << 20); - - rv.push(0xcb, (high >> 24) & 0xff, (high >> 16) & 0xff, - (high >> 8) & 0xff, high & 0xff, - (low >> 24) & 0xff, (low >> 16) & 0xff, - (low >> 8) & 0xff, low & 0xff); - } - break; - case "string": - // http://d.hatena.ne.jp/uupaa/20101128 - iz = mix.length; - pos = rv.length; // keep rewrite position - - rv.push(0); // placeholder - - // utf8.encode - for (i = 0; i < iz; ++i) { - c = mix.charCodeAt(i); - if (c < 0x80) { // ASCII(0x00 ~ 0x7f) - rv.push(c & 0x7f); - } else if (c < 0x0800) { - rv.push(((c >>> 6) & 0x1f) | 0xc0, (c & 0x3f) | 0x80); - } else if (c < 0x10000) { - rv.push(((c >>> 12) & 0x0f) | 0xe0, - ((c >>> 6) & 0x3f) | 0x80, (c & 0x3f) | 0x80); - } - } - size = rv.length - pos - 1; - - if (size < 32) { - rv[pos] = 0xa0 + size; // rewrite - } else if (size < 0x10000) { // 16 - rv.splice(pos, 1, 0xda, size >> 8, size & 0xff); - } else if (size < 0x100000000) { // 32 - rv.splice(pos, 1, 0xdb, - size >>> 24, (size >> 16) & 0xff, - (size >> 8) & 0xff, size & 0xff); - } - break; - default: // array or hash - if (++depth >= _MAX_DEPTH) { - _error = 1; // CYCLIC_REFERENCE_ERROR - return rv = []; // clear - } - if (_isArray(mix)) { - size = mix.length; - if (size < 16) { - rv.push(0x90 + size); - } else if (size < 0x10000) { // 16 - rv.push(0xdc, size >> 8, size & 0xff); - } else if (size < 0x100000000) { // 32 - rv.push(0xdd, size >>> 24, (size >> 16) & 0xff, - (size >> 8) & 0xff, size & 0xff); - } - for (i = 0; i < size; ++i) { - encode(rv, mix[i], depth); - } - } else { // hash - // http://d.hatena.ne.jp/uupaa/20101129 - pos = rv.length; // keep rewrite position - rv.push(0); // placeholder - size = 0; - for (i in mix) { - ++size; - encode(rv, i, depth); - encode(rv, mix[i], depth); - } - if (size < 16) { - rv[pos] = 0x80 + size; // rewrite - } else if (size < 0x10000) { // 16 - rv.splice(pos, 1, 0xde, size >> 8, size & 0xff); - } else if (size < 0x100000000) { // 32 - rv.splice(pos, 1, 0xdf, - size >>> 24, (size >> 16) & 0xff, - (size >> 8) & 0xff, size & 0xff); - } - } - } - } - return rv; - } - - // inner - decoder - function decode() { // @return Mix: - var size, i, iz, c, num = 0, - sign, exp, frac, ary, hash, - buf = _buf, type = buf[++_idx]; - - if (type >= 0xe0) { // Negative FixNum (111x xxxx) (-32 ~ -1) - return type - 0x100; - } - if (type < 0xc0) { - if (type < 0x80) { // Positive FixNum (0xxx xxxx) (0 ~ 127) - return type; - } - if (type < 0x90) { // FixMap (1000 xxxx) - num = type - 0x80; - type = 0x80; - } else if (type < 0xa0) { // FixArray (1001 xxxx) - num = type - 0x90; - type = 0x90; - } else { // if (type < 0xc0) { // FixRaw (101x xxxx) - num = type - 0xa0; - type = 0xa0; - } - } - switch (type) { - case 0xc0: return null; - case 0xc2: return false; - case 0xc3: return true; - case 0xc6: // bin32 - size = buf[++_idx] * 0x1000000 + (buf[++_idx] << 16); - case 0xc5: // bin16 - size += buf[++_idx] << 8; - case 0xc4: // bin8 - size += buf[++_idx]; - //console.log(_idx, "bin", size); - for (ary = [], i = _idx, iz = i + size; i < iz; ) { - c = buf[++i]; // lead byte - ary.push(c < 0x80 ? c : // ASCII(0x00 ~ 0x7f) - c < 0xe0 ? ((c & 0x1f) << 6 | (buf[++i] & 0x3f)) : - ((c & 0x0f) << 12 | (buf[++i] & 0x3f) << 6 - | (buf[++i] & 0x3f))); - } - _idx = i; - //console.log(_idx, "bin str", _toString.apply(null, ary)); - return _toString.apply(null, ary); - case 0xca: // float - num = buf[++_idx] * 0x1000000 + (buf[++_idx] << 16) + - (buf[++_idx] << 8) + buf[++_idx]; - sign = num & 0x80000000; // 1bit - exp = (num >> 23) & 0xff; // 8bits - frac = num & 0x7fffff; // 23bits - if (!num || num === 0x80000000) { // 0.0 or -0.0 - return 0; - } - if (exp === 0xff) { // NaN or Infinity - return frac ? NaN : Infinity; - } - return (sign ? -1 : 1) * - (frac | 0x800000) * Math.pow(2, exp - 127 - 23); // 127: bias - case 0xcb: // double - num = buf[++_idx] * 0x1000000 + (buf[++_idx] << 16) + - (buf[++_idx] << 8) + buf[++_idx]; - sign = num & 0x80000000; // 1bit - exp = (num >> 20) & 0x7ff; // 11bits - frac = num & 0xfffff; // 52bits - 32bits (high word) - if (!num || num === 0x80000000) { // 0.0 or -0.0 - _idx += 4; - return 0; - } - if (exp === 0x7ff) { // NaN or Infinity - _idx += 4; - return frac ? NaN : Infinity; - } - num = buf[++_idx] * 0x1000000 + (buf[++_idx] << 16) + - (buf[++_idx] << 8) + buf[++_idx]; - return (sign ? -1 : 1) * - ((frac | 0x100000) * Math.pow(2, exp - 1023 - 20) // 1023: bias - + num * Math.pow(2, exp - 1023 - 52)); - // 0xcf: uint64, 0xce: uint32, 0xcd: uint16 - case 0xcf: num = buf[++_idx] * 0x1000000 + (buf[++_idx] << 16) + - (buf[++_idx] << 8) + buf[++_idx]; - return num * 0x100000000 + - buf[++_idx] * 0x1000000 + (buf[++_idx] << 16) + - (buf[++_idx] << 8) + buf[++_idx]; - case 0xce: num += buf[++_idx] * 0x1000000 + (buf[++_idx] << 16); - case 0xcd: num += buf[++_idx] << 8; - case 0xcc: return num + buf[++_idx]; - // 0xd3: int64, 0xd2: int32, 0xd1: int16, 0xd0: int8 - case 0xd3: num = buf[++_idx]; - if (num & 0x80) { // sign -> avoid overflow - return ((num ^ 0xff) * 0x100000000000000 + - (buf[++_idx] ^ 0xff) * 0x1000000000000 + - (buf[++_idx] ^ 0xff) * 0x10000000000 + - (buf[++_idx] ^ 0xff) * 0x100000000 + - (buf[++_idx] ^ 0xff) * 0x1000000 + - (buf[++_idx] ^ 0xff) * 0x10000 + - (buf[++_idx] ^ 0xff) * 0x100 + - (buf[++_idx] ^ 0xff) + 1) * -1; - } - return num * 0x100000000000000 + - buf[++_idx] * 0x1000000000000 + - buf[++_idx] * 0x10000000000 + - buf[++_idx] * 0x100000000 + - buf[++_idx] * 0x1000000 + - buf[++_idx] * 0x10000 + - buf[++_idx] * 0x100 + - buf[++_idx]; - case 0xd2: num = buf[++_idx] * 0x1000000 + (buf[++_idx] << 16) + - (buf[++_idx] << 8) + buf[++_idx]; - return num < 0x80000000 ? num : num - 0x100000000; // 0x80000000 * 2 - case 0xd1: num = (buf[++_idx] << 8) + buf[++_idx]; - return num < 0x8000 ? num : num - 0x10000; // 0x8000 * 2 - case 0xd0: num = buf[++_idx]; - return num < 0x80 ? num : num - 0x100; // 0x80 * 2 - // 0xdb: raw32, 0xda: raw16, 0xa0: raw ( string ) - case 0xdb: num += buf[++_idx] * 0x1000000 + (buf[++_idx] << 16); - case 0xda: num += (buf[++_idx] << 8) + buf[++_idx]; - case 0xa0: // utf8.decode - for (ary = [], i = _idx, iz = i + num; i < iz; ) { - c = buf[++i]; // lead byte - ary.push(c < 0x80 ? c : // ASCII(0x00 ~ 0x7f) - c < 0xe0 ? ((c & 0x1f) << 6 | (buf[++i] & 0x3f)) : - ((c & 0x0f) << 12 | (buf[++i] & 0x3f) << 6 - | (buf[++i] & 0x3f))); - } - _idx = i; - return ary.length < 10240 ? _toString.apply(null, ary) - : byteArrayToByteString(ary); - // 0xdf: map32, 0xde: map16, 0x80: map - case 0xdf: num += buf[++_idx] * 0x1000000 + (buf[++_idx] << 16); - case 0xde: num += (buf[++_idx] << 8) + buf[++_idx]; - case 0x80: hash = {}; - while (num--) { - var k = decode(); - var v = decode(); - hash[k] = v; - } - return hash; - // 0xdd: array32, 0xdc: array16, 0x90: array - case 0xdd: num += buf[++_idx] * 0x1000000 + (buf[++_idx] << 16); - case 0xdc: num += (buf[++_idx] << 8) + buf[++_idx]; - case 0x90: ary = []; - while (num--) { - ary.push(decode()); - } - return ary; - } - return; - } - - // --- init --- - function init() { - var i = 0, v; - - for (; i < 0x100; ++i) { - v = _toString(i); - _bin2num[v] = i; // "\00" -> 0x00 - _num2bin[i] = v; // 0 -> "\00" - } - // http://twitter.com/edvakf/statuses/15576483807 - for (i = 0x80; i < 0x100; ++i) { // [Webkit][Gecko] - _bin2num[_toString(0xf700 + i)] = i; // "\f780" -> 0x80 - } - }; - - init() - - return { - pack: msgpackpack, - unpack: msgpackunpack, - }; - -}; diff --git a/src/qml/value-editor/editors/formatters/php-unserialize.js b/src/qml/value-editor/editors/formatters/php-unserialize.js deleted file mode 100644 index 581a30783..000000000 --- a/src/qml/value-editor/editors/formatters/php-unserialize.js +++ /dev/null @@ -1,5 +0,0 @@ -.import "./../../../3rdparty/php-unserialize-js/phpUnserialize.js" as Parser - -function unserialize(data) { - return Parser.qt_phpUnserialize(data) -} diff --git a/src/resources/translations/rdm.ts b/src/resources/translations/rdm.ts index 65dd35a31..a97409fc1 100644 --- a/src/resources/translations/rdm.ts +++ b/src/resources/translations/rdm.ts @@ -3,6 +3,11 @@ AddKeyDialog + + + Add New Key + + Key: @@ -61,6 +66,11 @@ BulkOperationsDialog + + + Bulk Operations Manager + + Delete keys @@ -477,7 +487,17 @@ - + + Custom Value View Formatters + + + + + Formatters path: %0 + + + + OK @@ -503,6 +523,14 @@ + + QConsole + + + Connecting... + + + QObject @@ -516,17 +544,17 @@ - + Settings directory is not writable - + RDM can't save connections file to settings directory. Please change file permissions or restart RDM as administrator. - + Please download new version of Redis Desktop Manager: %1 @@ -719,6 +747,28 @@ Cannot load key value: %1 + + + + + Can't find formatter with name: %1 + + + + + Cannot decode value using %1 formatter. See log for more details. + + + + + Cannot validate value using %1 formatter. See log for more details. + + + + + Cannot encode value using %1 formatter. See log for more details. + + QuickStartDialog @@ -803,6 +853,16 @@ TTL: + + + Rename + + + + + Rename key + + New name: From 943f85bb21ac98c88872ee5ecacbb16e6b459ad4 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 9 Dec 2016 18:26:47 +0200 Subject: [PATCH 02/32] Remove php-unserialize submodule --- .gitmodules | 3 --- src/qml/3rdparty/.empty | 0 src/qml/3rdparty/php-unserialize-js | 1 - 3 files changed, 4 deletions(-) delete mode 100644 src/qml/3rdparty/.empty delete mode 160000 src/qml/3rdparty/php-unserialize-js diff --git a/.gitmodules b/.gitmodules index 2c0c86c9c..7ae056c1d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,6 +13,3 @@ [submodule "3rdparty/qgamp"] path = 3rdparty/qgamp url = https://github.com/uglide/qgamp.git -[submodule "src/qml/3rdparty/php-unserialize-js"] - path = src/qml/3rdparty/php-unserialize-js - url = https://github.com/uglide/php-unserialize-js.git diff --git a/src/qml/3rdparty/.empty b/src/qml/3rdparty/.empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/qml/3rdparty/php-unserialize-js b/src/qml/3rdparty/php-unserialize-js deleted file mode 160000 index 953bb171c..000000000 --- a/src/qml/3rdparty/php-unserialize-js +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 953bb171c9760b4bea1d360a86c7990423662b4f From 57d484d8e4be68751f22d91146a9c0f05f2b34fc Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 9 Dec 2016 18:35:34 +0200 Subject: [PATCH 03/32] Fix qml.qrc --- src/qml/qml.qrc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/qml/qml.qrc b/src/qml/qml.qrc index e955fd1ef..021cea8a8 100644 --- a/src/qml/qml.qrc +++ b/src/qml/qml.qrc @@ -25,8 +25,7 @@ value-editor/editors/MultilineEditor.qml value-editor/editors/formatters/formatters.js value-editor/editors/formatters/hexy.js - value-editor/editors/editor.js - 3rdparty/php-unserialize-js/phpUnserialize.js + value-editor/editors/editor.js console/QConsole.qml console/BaseConsole.qml console/Consoles.qml From c22a57653221607652519af72b0acc59f07fbc03 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 16 Dec 2016 10:34:31 +0200 Subject: [PATCH 04/32] Clean up code --- 3rdparty/qredisclient | 2 +- src/app/models/key-models/abstractkey.h | 4 +- src/app/models/key-models/stringkey.cpp | 2 +- src/modules/common/tabviewmodel.cpp | 5 +- tests/qml_tests/tst_formatters.qml | 72 ++++--------------- tests/unit_tests/main.cpp | 4 +- .../value-editor/mocks/fakeKeyFactory.h | 4 +- .../value-editor/test_compression.cpp | 29 -------- .../testcases/value-editor/test_compression.h | 11 --- tests/unit_tests/unit_tests.pro | 2 +- 10 files changed, 22 insertions(+), 113 deletions(-) delete mode 100644 tests/unit_tests/testcases/value-editor/test_compression.cpp delete mode 100644 tests/unit_tests/testcases/value-editor/test_compression.h diff --git a/3rdparty/qredisclient b/3rdparty/qredisclient index a28f13f38..47d130728 160000 --- a/3rdparty/qredisclient +++ b/3rdparty/qredisclient @@ -1 +1 @@ -Subproject commit a28f13f380717a89f21c6de41aea66ea5f3da4a5 +Subproject commit 47d1307281322a5d3537bf9fcee8c066d06b29e5 diff --git a/src/app/models/key-models/abstractkey.h b/src/app/models/key-models/abstractkey.h index ca873d035..fbe48cc4e 100644 --- a/src/app/models/key-models/abstractkey.h +++ b/src/app/models/key-models/abstractkey.h @@ -94,7 +94,7 @@ template < typename T > class KeyModel : public ValueEditor::Model virtual void setTTL(const long long ttl) override { RedisClient::Response result; - qDebug(QString("TTL=%1").arg(ttl).toLatin1().constData()); + qDebug() << QString("TTL=%1").arg(ttl); try { if (ttl >= 0) result = m_connection->commandSync({"EXPIRE", m_keyFullPath, QString::number(ttl).toLatin1()}, m_dbIndex); @@ -105,7 +105,7 @@ template < typename T > class KeyModel : public ValueEditor::Model } if (result.getValue().toInt() == 0) { - throw Exception("Not supprt TTL at this key"); + throw Exception("Not support TTL at this key"); } if (ttl >= 0) m_ttl = ttl; diff --git a/src/app/models/key-models/stringkey.cpp b/src/app/models/key-models/stringkey.cpp index 41951f066..f437134e8 100644 --- a/src/app/models/key-models/stringkey.cpp +++ b/src/app/models/key-models/stringkey.cpp @@ -73,7 +73,7 @@ void StringKeyModel::loadRows(unsigned long, unsigned long, std::functioncommand({"GET", m_keyFullPath}, getConnector().data(), [this, callback](RedisClient::Response r, QString e) { - if (r.getType() != RedisClient::Response::Bulk) { + if (r.getType() != RedisClient::Response::Bulk || !e.isEmpty()) { return callback(QString("Cannot load value")); } diff --git a/src/modules/common/tabviewmodel.cpp b/src/modules/common/tabviewmodel.cpp index 01cc00541..4713e704a 100644 --- a/src/modules/common/tabviewmodel.cpp +++ b/src/modules/common/tabviewmodel.cpp @@ -5,9 +5,8 @@ TabViewModel::TabViewModel(const ModelFactory& modelFactory) { } -QModelIndex TabViewModel::index(int row, int column, const QModelIndex &parent) const -{ - Q_UNUSED(column); +QModelIndex TabViewModel::index(int row, int, const QModelIndex&) const +{ return createIndex(row, 0); } diff --git a/tests/qml_tests/tst_formatters.qml b/tests/qml_tests/tst_formatters.qml index a3efe5c64..660438938 100644 --- a/tests/qml_tests/tst_formatters.qml +++ b/tests/qml_tests/tst_formatters.qml @@ -6,71 +6,23 @@ import "./../../src/qml/value-editor/editors/formatters/formatters.js" as Format TestCase { name: "FormatterTests" - function checkProperties(f, validBin, validRead) { - verify(f.title.length != 0, "title") - compare(f.binary, validBin) - compare(f.readOnly, validRead) - } - function test_plain() { // given var plain = Formatters.plain var testValue = "plain_text!" - // when - checkProperties(plain, false, false) - - //then - compare(plain.isValid(testValue), true) - compare(plain.getFormatted(testValue), testValue) - compare(plain.getRaw(testValue), testValue) - } - - function test_php_data() { - return [{ - value: 'a:6:{i:1;s:30:"PHP code tester Sandbox Online";' - +'s:3:"foo";s:3:"bar";i:2;i:5;i:5;i:89009;s:4:"case";' - +'s:12:"Random Stuff";s:11:"PHP Version";s:6:"5.5.18";}', - validResult: { - "1": "PHP code tester Sandbox Online", - "foo": "bar", - "2": 5, - "5": 89009, - "case": "Random Stuff", - "PHP Version": "5.5.18" - }, - valid: true - }, - {value: "C", valid: false, validResult: undefined}, - {value: "i", valid: false, validResult: undefined}, - {value: "d", valid: false, validResult: undefined}, - {value: "b", valid: false, validResult: undefined}, - {value: "s", valid: false, validResult: undefined}, - {value: "a", valid: false, validResult: undefined}, - {value: "O", valid: false, validResult: undefined}, - {value: "r", valid: false, validResult: undefined}, - {value: "R", valid: false, validResult: undefined}, - {value: "New", valid: false, validResult: undefined}, - {value: "New;", valid: false, validResult: undefined}, - {value: '
b:<i>;
', - valid: false, validResult: undefined}, - ] - } - - function test_php(data) { - // given - var phpFormatter = Formatters.phpserialized - var testValue = data.value - var validResult = data.validResult - var isValid = data.valid - - // when - checkProperties(phpFormatter, false, true) + // checks + verify(plain.title.length !== 0, "title") - // then - compare(phpFormatter.isValid(testValue), isValid) + plain.isValid(testValue, function (valid, err) { + compare(valid, true) + }) + plain.getFormatted(testValue, function (formatted, readOnly, format){ + compare(formatted, testValue) + }) - if (isValid) - compare(JSON.parse(phpFormatter.getFormatted(testValue)), validResult) - } + plain.getRaw(testValue, function (plain){ + compare(plain, testValue) + }) + } } diff --git a/tests/unit_tests/main.cpp b/tests/unit_tests/main.cpp index 4ffdc2aa4..994c8206e 100644 --- a/tests/unit_tests/main.cpp +++ b/tests/unit_tests/main.cpp @@ -17,7 +17,6 @@ INITIALIZE_EASYLOGGINGPP #include "testcases/connections-tree/test_model.h" #include "testcases/console/test_console.h" #include "testcases/console/test_consolemodel.h" -#include "testcases/value-editor/test_compression.h" #include "testcases/value-editor/test_viewmodel.h" int main(int argc, char *argv[]) @@ -43,8 +42,7 @@ int main(int argc, char *argv[]) + QTest::qExec(new TestKeyModels, argc, argv) + QTest::qExec(new TestAbstractKey, argc, argv) - // value-editor module - + QTest::qExec(new TestCompression, argc, argv) + // value-editor module + QTest::qExec(new TestViewModel, argc, argv) ; diff --git a/tests/unit_tests/testcases/value-editor/mocks/fakeKeyFactory.h b/tests/unit_tests/testcases/value-editor/mocks/fakeKeyFactory.h index d8f927d47..21b382258 100644 --- a/tests/unit_tests/testcases/value-editor/mocks/fakeKeyFactory.h +++ b/tests/unit_tests/testcases/value-editor/mocks/fakeKeyFactory.h @@ -62,7 +62,7 @@ class FakeKeyModel : public ValueEditor::Model { } - void updateRow(int rowIndex, const QVariantMap &) override + void updateRow(int, const QVariantMap &) override { } @@ -71,7 +71,7 @@ class FakeKeyModel : public ValueEditor::Model return 1; } - void loadRows(unsigned long rowStart, unsigned long count, std::function callback) override + void loadRows(unsigned long, unsigned long, std::function) override { } diff --git a/tests/unit_tests/testcases/value-editor/test_compression.cpp b/tests/unit_tests/testcases/value-editor/test_compression.cpp deleted file mode 100644 index d23c00c40..000000000 --- a/tests/unit_tests/testcases/value-editor/test_compression.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "test_compression.h" -#include "value-editor/compression.h" - -void TestCompression::testCompression() -{ -#ifdef Q_OS_WIN - QSKIP("SKIP ON Windows"); -#endif - unsigned char hello_world[] = { - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xcb, 0x48, - 0xcd, 0xc9, 0xc9, 0x57, 0x28, 0xcf, 0x2f, 0xca, 0x49, 0x51, 0x04, 0x00, - 0x6d, 0xc2, 0xb4, 0x03, 0x0c, 0x00, 0x00, 0x00 - }; - QByteArray plainText("hello world!"); - QByteArray compressedString((const char*)hello_world, 32); - - bool actualResult = ValueEditor::Compression::isCompressed(compressedString); - QCOMPARE(true, actualResult); - - QByteArray actualUncompressedResult; - actualResult = ValueEditor::Compression::decompress(compressedString, actualUncompressedResult); - QCOMPARE(true, actualResult); - QCOMPARE(plainText, actualUncompressedResult); - - QByteArray actualCompressedResult; - actualResult = ValueEditor::Compression::compress(plainText, actualCompressedResult); - QCOMPARE(true, actualResult); - QCOMPARE(compressedString, actualCompressedResult); -} diff --git a/tests/unit_tests/testcases/value-editor/test_compression.h b/tests/unit_tests/testcases/value-editor/test_compression.h deleted file mode 100644 index 53bab09db..000000000 --- a/tests/unit_tests/testcases/value-editor/test_compression.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include "basetestcase.h" - -class TestCompression : public BaseTestCase -{ - Q_OBJECT - -private slots: - void testCompression(); -}; diff --git a/tests/unit_tests/unit_tests.pro b/tests/unit_tests/unit_tests.pro index 1778e8ae9..8a937950e 100644 --- a/tests/unit_tests/unit_tests.pro +++ b/tests/unit_tests/unit_tests.pro @@ -29,7 +29,7 @@ INCLUDEPATH += $$SRC_DIR/modules/ \ $$PROJECT_ROOT/3rdparty/qredisclient/tests/unit_tests/ DEFINES += INTEGRATION_TESTS -DEFINES += ELPP_STL_LOGGING ELPP_DISABLE_DEFAULT_CRASH_HANDLING +DEFINES += ELPP_QT_LOGGING ELPP_STL_LOGGING ELPP_DISABLE_DEFAULT_CRASH_HANDLING #TEST CASES include($$PWD/testcases/app/app-tests.pri) From a80b2302efb72bb4236a94a5742781ce08447418 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 16 Dec 2016 10:57:59 +0200 Subject: [PATCH 05/32] Fix issues detected in unit tests --- src/app/models/configmanager.cpp | 8 ++++---- src/app/models/configmanager.h | 2 +- src/rdm.pro | 1 + tests/unit_tests/testcases/console/console-tests.pri | 3 +-- tests/unit_tests/unit_tests.pro | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/app/models/configmanager.cpp b/src/app/models/configmanager.cpp index 1cfe70361..f8eaded01 100644 --- a/src/app/models/configmanager.cpp +++ b/src/app/models/configmanager.cpp @@ -19,7 +19,7 @@ ConfigManager::ConfigManager(const QString &basePath) QString ConfigManager::getApplicationConfigPath(const QString &configFile, bool checkPath) { - QString configDir = getConfigPath(); + QString configDir = getConfigPath(m_basePath); QDir settingsPath(configDir); if (!settingsPath.exists() && settingsPath.mkpath(configDir)) { @@ -180,16 +180,16 @@ void ConfigManager::setPermissions(QFile &file) #endif } -QString ConfigManager::getConfigPath() +QString ConfigManager::getConfigPath(QString basePath) { QString configDir; #ifdef Q_OS_MACX configDir = QDir::toNativeSeparators( - QString("%1/%2").arg(QDir::homePath()).arg("/Library/Preferences/rdm/") + QString("%1/%2").arg(basePath).arg("/Library/Preferences/rdm/") ); #else configDir = QDir::toNativeSeparators( - QString("%1/%2").arg(QDir::homePath()).arg(".rdm") + QString("%1/%2").arg(basePath).arg(".rdm") ); #endif return configDir; diff --git a/src/app/models/configmanager.h b/src/app/models/configmanager.h index 65b749591..088da0082 100644 --- a/src/app/models/configmanager.h +++ b/src/app/models/configmanager.h @@ -11,7 +11,7 @@ class ConfigManager QString getApplicationConfigPath(const QString &, bool checkPath=true); bool migrateOldConfig(const QString &oldFileName, const QString &newFileName); public: - static QString getConfigPath(); + static QString getConfigPath(QString basePath = QDir::homePath()); static QJsonArray xmlConfigToJsonArray(const QString &xmlConfigPath); private: diff --git a/src/rdm.pro b/src/rdm.pro index 1e8cfcd8e..e8ac7a375 100644 --- a/src/rdm.pro +++ b/src/rdm.pro @@ -95,6 +95,7 @@ unix:!macx { # ubuntu & debian QTPLUGIN += qsvg qsvgicon + QMAKE_CXXFLAGS += -Wno-sign-compare QMAKE_LFLAGS += -static-libgcc -static-libstdc++ release: DESTDIR = ./../bin/linux/release diff --git a/tests/unit_tests/testcases/console/console-tests.pri b/tests/unit_tests/testcases/console/console-tests.pri index d62240612..cf23f2356 100644 --- a/tests/unit_tests/testcases/console/console-tests.pri +++ b/tests/unit_tests/testcases/console/console-tests.pri @@ -1,8 +1,7 @@ CONSOLE_SRC_DIR = $$PWD/../../../../src/modules/console/ HEADERS += \ - $$PWD/*.h \ - $$PWD/mocks/*.h \ + $$PWD/*.h \ $$CONSOLE_SRC_DIR/consolemodel.h \ $$CONSOLE_SRC_DIR/hex_utils.h \ diff --git a/tests/unit_tests/unit_tests.pro b/tests/unit_tests/unit_tests.pro index 8a937950e..51804142b 100644 --- a/tests/unit_tests/unit_tests.pro +++ b/tests/unit_tests/unit_tests.pro @@ -48,7 +48,7 @@ debug: DESTDIR = $$PROJECT_ROOT/bin/tests unix:!mac { #code coverage - QMAKE_CXXFLAGS += -g -fprofile-arcs -ftest-coverage -O0 + QMAKE_CXXFLAGS += -g -fprofile-arcs -ftest-coverage -O0 -Wno-sign-compare QMAKE_LFLAGS += -g -fprofile-arcs -ftest-coverage -O0 LIBS += -lgcov } From 4b1b46d47b5d20fa2cd106ee669b1f035c46d800 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 16 Dec 2016 12:36:58 +0200 Subject: [PATCH 06/32] Show human readable size of the value --- src/app/qmlutils.cpp | 18 +++++++++++++++ src/app/qmlutils.h | 1 + .../value-editor/editors/MultilineEditor.qml | 3 ++- .../editors/formatters/formatters.js | 23 +++---------------- 4 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/app/qmlutils.cpp b/src/app/qmlutils.cpp index 01360f5fe..cb7c09b72 100644 --- a/src/app/qmlutils.cpp +++ b/src/app/qmlutils.cpp @@ -24,6 +24,24 @@ long QmlUtils::binaryStringLength(const QVariant &value) return val.size(); } +QString QmlUtils::humanSize(long size) +{ + double num = size; + QStringList list; + list << "KB" << "MB" << "GB"; + + QStringListIterator i(list); + QString unit("bytes"); + + while(num >= 1024.0 && i.hasNext()) + { + unit = i.next(); + num /= 1024.0; + } + return QString().setNum(num,'f',2)+" "+unit; +} + + QVariant QmlUtils::valueToBinary(const QVariant &value) { if (!value.canConvert(QVariant::ByteArray)) { diff --git a/src/app/qmlutils.h b/src/app/qmlutils.h index cb7eb1207..fe675824c 100644 --- a/src/app/qmlutils.h +++ b/src/app/qmlutils.h @@ -11,6 +11,7 @@ class QmlUtils : public QObject public: Q_INVOKABLE bool isBinaryString(const QVariant &value); Q_INVOKABLE long binaryStringLength(const QVariant &value); + Q_INVOKABLE QString humanSize(long size); Q_INVOKABLE QVariant valueToBinary(const QVariant &value); Q_INVOKABLE QVariant binaryListToValue(const QVariantList& binaryList); Q_INVOKABLE QVariant printable(const QVariant &value); diff --git a/src/qml/value-editor/editors/MultilineEditor.qml b/src/qml/value-editor/editors/MultilineEditor.qml index 3e940373c..37ab6b70c 100644 --- a/src/qml/value-editor/editors/MultilineEditor.qml +++ b/src/qml/value-editor/editors/MultilineEditor.qml @@ -33,7 +33,7 @@ ColumnLayout if (isBin) binaryFlag.visible = true - // FIXME: autoDetectFormatter + formatterSelector.currentIndex = Formatters.guessFormatter(isBin) var formatter = formatterSelector.model[formatterSelector.currentIndex] @@ -67,6 +67,7 @@ ColumnLayout Layout.fillWidth: true Text { text: root.fieldLabel } + TextEdit { text: "size: " + binaryUtils.humanSize(binaryUtils.binaryStringLength(value)); readOnly: true; color: "#ccc" } Text { id: binaryFlag; text: qsTr("[Binary]"); visible: false; color: "green"; } Item { Layout.fillWidth: true } Text { text: "View as:" } diff --git a/src/qml/value-editor/editors/formatters/formatters.js b/src/qml/value-editor/editors/formatters/formatters.js index f976b3024..4ed91a050 100644 --- a/src/qml/value-editor/editors/formatters/formatters.js +++ b/src/qml/value-editor/editors/formatters/formatters.js @@ -128,24 +128,7 @@ function buildFormattersModel() return formatters } -function guessFormatter(formatters, isBinary, value) -{ - // NOTE(u_glide): Use hex or plain formatter if value is large - if (binaryUtils.binaryStringLength(value) > 100000) { - return isBinary? 2 : 0 - } - - // TODO: use native formatters to guess value format - - var tryFormatters = isBinary? [2] : [1] - - for (var index in tryFormatters) { - var val = (enabledFormatters[tryFormatters[index]].binary) ? - binaryUtils.valueToBinary(value) : binaryUtils.toUtf(value) - - if (enabledFormatters[tryFormatters[index]].isValid(val)){ - return tryFormatters[index] - } - } - return 0 // Plain text +function guessFormatter(isBinary) +{ + return isBinary? 2 : 0 } From bc95417087f63cb2c58a8f13f6cdc407b88f9e46 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 16 Dec 2016 16:57:27 +0200 Subject: [PATCH 07/32] Use WebEngine to render values --- src/app/app.cpp | 2 + src/app/qmlutils.cpp | 9 ++- src/app/qmlutils.h | 2 +- .../value-editor/editors/HashItemEditor.qml | 12 ++-- .../value-editor/editors/MultilineEditor.qml | 55 ++++++++++--------- src/rdm.pro | 2 +- 6 files changed, 44 insertions(+), 38 deletions(-) diff --git a/src/app/app.cpp b/src/app/app.cpp index e2464053a..94d33bfdf 100644 --- a/src/app/app.cpp +++ b/src/app/app.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -137,6 +138,7 @@ void Application::registerQmlRootObjects() void Application::initQml() { + QtWebEngine::initialize(); registerQmlTypes(); registerQmlRootObjects(); m_engine.load(QUrl(QStringLiteral("qrc:///app.qml"))); diff --git a/src/app/qmlutils.cpp b/src/app/qmlutils.cpp index cb7c09b72..e668d2760 100644 --- a/src/app/qmlutils.cpp +++ b/src/app/qmlutils.cpp @@ -66,13 +66,18 @@ QVariant QmlUtils::binaryListToValue(const QVariantList &binaryList) return value; } -QVariant QmlUtils::printable(const QVariant &value) +QVariant QmlUtils::printable(const QVariant &value, bool htmlEscaped) { if (!value.canConvert(QVariant::ByteArray)) { return QVariant(); } QByteArray val = value.toByteArray(); - return printableString(val); + + if (htmlEscaped) { + return printableString(val).toHtmlEscaped(); + } else { + return printableString(val); + } } QVariant QmlUtils::printableToValue(const QVariant &printable) diff --git a/src/app/qmlutils.h b/src/app/qmlutils.h index fe675824c..483a54480 100644 --- a/src/app/qmlutils.h +++ b/src/app/qmlutils.h @@ -14,7 +14,7 @@ class QmlUtils : public QObject Q_INVOKABLE QString humanSize(long size); Q_INVOKABLE QVariant valueToBinary(const QVariant &value); Q_INVOKABLE QVariant binaryListToValue(const QVariantList& binaryList); - Q_INVOKABLE QVariant printable(const QVariant &value); + Q_INVOKABLE QVariant printable(const QVariant &value, bool htmlEscaped=false); Q_INVOKABLE QVariant printableToValue(const QVariant &printable); Q_INVOKABLE QVariant toUtf(const QVariant &value); Q_INVOKABLE QString getPathFromUrl(const QUrl &url); diff --git a/src/qml/value-editor/editors/HashItemEditor.qml b/src/qml/value-editor/editors/HashItemEditor.qml index d4fefec55..e2698f9fd 100644 --- a/src/qml/value-editor/editors/HashItemEditor.qml +++ b/src/qml/value-editor/editors/HashItemEditor.qml @@ -22,10 +22,8 @@ AbstractEditor { property var originalValue: "" showFormatters: root.state != "new" - style: TextAreaStyle { - backgroundColor: (!keyText.value && keyText.enabled - && keyText.readOnly == false) ? "lightyellow" : "white" - } + backgroundColor: (!keyText.value && keyText.enabled + && keyText.readOnly == false) ? "lightyellow" : "white" } @@ -37,10 +35,8 @@ AbstractEditor { property var originalValue: "" showFormatters: root.state != "new" - style: TextAreaStyle { - backgroundColor: (!textArea.value && textArea.enabled - && textArea.readOnly == false) ? "lightyellow" : "white" - } + backgroundColor: (!textArea.value && textArea.enabled + && textArea.readOnly == false) ? "lightyellow" : "white" } function setValue(rowValue) { diff --git a/src/qml/value-editor/editors/MultilineEditor.qml b/src/qml/value-editor/editors/MultilineEditor.qml index 37ab6b70c..df3a7dc81 100644 --- a/src/qml/value-editor/editors/MultilineEditor.qml +++ b/src/qml/value-editor/editors/MultilineEditor.qml @@ -2,6 +2,7 @@ import QtQuick 2.0 import QtQuick.Controls 1.2 import QtQuick.Controls.Styles 1.2 import QtQuick.Layouts 1.1 +import QtWebEngine 1.0 import "./formatters/formatters.js" as Formatters @@ -9,15 +10,15 @@ ColumnLayout { id: root - property alias enabled: textArea.enabled - property alias textColor: textArea.textColor - property alias style: textArea.style + property bool enabled + property string textColor + property string backgroundColor property bool showFormatters: true property string fieldLabel: qsTr("Value:") property var value function getText() { - return textArea.formatter.getRaw(textArea.text) + // FIXME } function setValue(val) { @@ -28,12 +29,11 @@ ColumnLayout function loadFormattedValue() { var isBin = binaryUtils.isBinaryString(root.value) - binaryFlag.visible = false - textArea.textFormat = TextEdit.PlainText + binaryFlag.visible = false if (isBin) binaryFlag.visible = true - formatterSelector.currentIndex = Formatters.guessFormatter(isBin) + //formatterSelector.currentIndex = Formatters.guessFormatter(isBin) var formatter = formatterSelector.model[formatterSelector.currentIndex] @@ -41,21 +41,14 @@ ColumnLayout formatter.instance.getFormatted(root.value, function (formatted, isReadOnly, format) { - if (isReadOnly) { - textArea.readOnlyValue = true - } - if (format == "json") { // 1 is JSON return formatterSelector.model[1].instance.getFormatted(formatted, function (formattedJson, r, f) { - textArea.text = formattedJson + webView.setText(formattedJson, false, isReadOnly) uiBlocker.visible = false }) } else { - if (format == "html") - textArea.textFormat = TextEdit.RichText - - textArea.text = formatted + webView.setText(formatted, format === "html", isReadOnly) } uiBlocker.visible = false @@ -86,24 +79,34 @@ ColumnLayout } } - TextArea - { - id: textArea - Layout.fillWidth: true + WebEngineView { + id: webView + Layout.fillWidth: true Layout.fillHeight: true - Layout.preferredHeight: 100 + Layout.preferredHeight: 100 + + onJavaScriptConsoleMessage: { + console.log("Web:", message) + } - readOnly: (readOnlyValue)? readOnlyValue : enabled ? true : false + function setText(text, html, readOnly) { - property bool readOnlyValue: false + if (html) { + webView.loadHtml("" + text +"") + } else { + var attr = ""; - style: TextAreaStyle { renderType: Text.QtRendering } + if (readOnly) { + attr = "readonly"; + } - font { family: monospacedFont.name; pointSize: 12 } + webView.loadHtml("") + } - wrapMode: TextEdit.WrapAnywhere + } } + Rectangle { id: uiBlocker visible: false diff --git a/src/rdm.pro b/src/rdm.pro index e8ac7a375..23ca842ff 100644 --- a/src/rdm.pro +++ b/src/rdm.pro @@ -4,7 +4,7 @@ # #------------------------------------------------- -QT += core gui network concurrent widgets quick quickwidgets charts +QT += core gui network concurrent widgets quick quickwidgets charts webengine TARGET = rdm TEMPLATE = app From 8ea79f117cf234cba5e93cf8ebf5cabc7aa7c132 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 16 Dec 2016 16:58:25 +0200 Subject: [PATCH 08/32] Fix #3454: Add support for Pub/Sub commands in console --- 3rdparty/qredisclient | 2 +- src/modules/console/consolemodel.cpp | 44 ++++++++++++++++++++-------- src/qml/console/Consoles.qml | 4 +-- 3 files changed, 34 insertions(+), 16 deletions(-) diff --git a/3rdparty/qredisclient b/3rdparty/qredisclient index 47d130728..9081d1905 160000 --- a/3rdparty/qredisclient +++ b/3rdparty/qredisclient @@ -1 +1 @@ -Subproject commit 47d1307281322a5d3537bf9fcee8c066d06b29e5 +Subproject commit 9081d1905a34199da4883ef6f681ca19440f0389 diff --git a/src/modules/console/consolemodel.cpp b/src/modules/console/consolemodel.cpp index a6d361fcb..f712405aa 100644 --- a/src/modules/console/consolemodel.cpp +++ b/src/modules/console/consolemodel.cpp @@ -45,22 +45,40 @@ void Model::executeCommand(const QString & cmd) using namespace RedisClient; Command command(Command::splitCommandString(cmd), m_current_db); - Response result; - try { - result = m_connection->commandSync(command); - } catch (Connection::Exception& e) { - emit addOutput(QString(QObject::tr("Connection error:")) + QString(e.what()), "error"); - return; - } + if (command.isSubscriptionCommand()) { + emit addOutput("Switch to Pub/Sub mode. Close console tab to stop listen for messages.", "part"); + + command.setCallBack(this, [this](Response result, QString err){ - if (command.isSelectCommand() || m_connection->mode() == RedisClient::Connection::Mode::Cluster) - { - m_current_db = command.getPartAsString(1).toInt(); - updatePrompt(false); + if (!err.isEmpty()) { + emit addOutput(QObject::tr("Subscribe error: %1").arg(err), "error"); + return; + } + + QVariant value = result.getValue(); + emit addOutput(RedisClient::Response::valueToHumanReadString(value), "part"); + }); + + m_connection->command(command); + } else { + Response result; + + try { + result = m_connection->commandSync(command); + } catch (Connection::Exception& e) { + emit addOutput(QString(QObject::tr("Connection error:")) + QString(e.what()), "error"); + return; + } + + if (command.isSelectCommand() || m_connection->mode() == RedisClient::Connection::Mode::Cluster) + { + m_current_db = command.getPartAsString(1).toInt(); + updatePrompt(false); + } + QVariant value = result.getValue(); + emit addOutput(RedisClient::Response::valueToHumanReadString(value), "complete"); } - QVariant value = result.getValue(); - emit addOutput(RedisClient::Response::valueToHumanReadString(value), "complete"); } void Model::updatePrompt(bool showPrompt) diff --git a/src/qml/console/Consoles.qml b/src/qml/console/Consoles.qml index 72dfe4020..d8968cb43 100644 --- a/src/qml/console/Consoles.qml +++ b/src/qml/console/Consoles.qml @@ -14,7 +14,7 @@ Repeater { BetterTab { id: tab title: tabName - icon: "qrc:/images/console.png" + icon: "qrc:/images/console.svg" onClose: { consoleModel.closeTab(tabIndex) @@ -53,7 +53,7 @@ Repeater { Component.onCompleted: { tab.icon = Qt.binding(function() { - return redisConsole.busy ? "qrc:/images/loader.gif" : "qrc:/images/console.svg" + return redisConsole.busy ? "qrc:/images/wait.svg" : "qrc:/images/console.svg" }) initTimer.start() } From 31260b36ff9fa06de56af90dad901741d073165f Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 16 Dec 2016 16:58:59 +0200 Subject: [PATCH 09/32] Update formattersmanager --- src/modules/value-editor/formattersmanager.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/modules/value-editor/formattersmanager.cpp b/src/modules/value-editor/formattersmanager.cpp index 0dcabeef0..c6c025186 100644 --- a/src/modules/value-editor/formattersmanager.cpp +++ b/src/modules/value-editor/formattersmanager.cpp @@ -39,6 +39,8 @@ QByteArray readStdoutFromExternalProcess(const QStringList& cmd, const QString& QJsonObject readJsonFromExternalProcess(const QStringList& cmd, const QString& wd) { + qDebug() << cmd; + QByteArray result = readStdoutFromExternalProcess(cmd, wd); if (result.isEmpty()) @@ -104,7 +106,7 @@ void ValueEditor::FormattersManager::loadFormatters(bool ignoreCache) QVariantMap data; data["name"] = it.fileName(); - data["version"] = result; + data["version"] = result.simplified(); data["cmd"] = fullCmd.join(" "); data["cmd_list"] = fullCmd; data["cwd"] = it.filePath(); From cbee8a1b490f6211288b8afec6dea7bf63d81b77 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 16 Dec 2016 17:12:30 +0200 Subject: [PATCH 10/32] Fix issue #3732: RDM shows invalid error message when trying to connect to invalid host/port --- src/app/models/treeoperations.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/app/models/treeoperations.cpp b/src/app/models/treeoperations.cpp index 79fd6821f..c46e0d9ee 100644 --- a/src/app/models/treeoperations.cpp +++ b/src/app/models/treeoperations.cpp @@ -20,14 +20,21 @@ TreeOperations::TreeOperations(QSharedPointer connectio void TreeOperations::getDatabases(std::function callback) { + bool connected = false; + if (!m_connection->isConnected()) { try { - m_connection->connect(true); + connected = m_connection->connect(true); } catch (const RedisClient::Connection::Exception& e) { throw ConnectionsTree::Operations::Exception(QObject::tr("Connection error: ") + QString(e.what())); } } + if (!connected) { + throw ConnectionsTree::Operations::Exception( + QObject::tr("Cannot connect to server '%1'. Check log for details.").arg(m_connection->getConfig().name())); + } + if (m_connection->getServerVersion() < 2.8) throw ConnectionsTree::Operations::Exception(QObject::tr("RedisDesktopManager >= 0.9.0 doesn't support old versions of " "redis-server (< 2.8). Please use RedisDesktopManager 0.8.8 or upgrade your redis-server.")); From 6be67a58c0c37837ae3615994af1c595f53e8ab7 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 23 Dec 2016 09:28:07 +0200 Subject: [PATCH 11/32] Fix issue #3758: RDM Shows Key Values for One Key Only --- src/modules/common/tabviewmodel.cpp | 5 +++++ src/modules/common/tabviewmodel.h | 2 ++ src/modules/value-editor/viewmodel.cpp | 3 --- src/modules/value-editor/viewmodel.h | 1 - src/qml/app.qml | 31 +++++++++++++++++++++----- src/qml/common/BetterTab.qml | 1 + src/qml/server-info/ServerInfoTabs.qml | 1 + src/qml/value-editor/ValueTabs.qml | 1 + 8 files changed, 36 insertions(+), 9 deletions(-) diff --git a/src/modules/common/tabviewmodel.cpp b/src/modules/common/tabviewmodel.cpp index 4713e704a..35f2ff6df 100644 --- a/src/modules/common/tabviewmodel.cpp +++ b/src/modules/common/tabviewmodel.cpp @@ -65,6 +65,11 @@ QObject *TabViewModel::getValue(int i) return qobject_cast(m_models.at(i).data()); } +int TabViewModel::tabsCount() const +{ + return m_models.count(); +} + void TabViewModel::openTab(QSharedPointer connection) { beginInsertRows(QModelIndex(), m_models.count(), m_models.count()); diff --git a/src/modules/common/tabviewmodel.h b/src/modules/common/tabviewmodel.h index 95791639d..722abd0da 100644 --- a/src/modules/common/tabviewmodel.h +++ b/src/modules/common/tabviewmodel.h @@ -33,6 +33,8 @@ class TabViewModel : public QAbstractListModel Q_INVOKABLE QObject* getValue(int i); + Q_INVOKABLE int tabsCount() const; + signals: void changeCurrentTab(int i); diff --git a/src/modules/value-editor/viewmodel.cpp b/src/modules/value-editor/viewmodel.cpp index 16a51dd78..1047c240c 100644 --- a/src/modules/value-editor/viewmodel.cpp +++ b/src/modules/value-editor/viewmodel.cpp @@ -244,9 +244,6 @@ bool ValueEditor::ViewModel::isIndexValid(const QModelIndex &index) const void ValueEditor::ViewModel::loadModel(QSharedPointer model, bool openNewTab) { - if (m_valueModels.count() == 0) - emit closeWelcomeTab(); - if (openNewTab || m_valueModels.count() == 0) { beginInsertRows(QModelIndex(), m_valueModels.count(), m_valueModels.count()); m_valueModels.append(model); diff --git a/src/modules/value-editor/viewmodel.h b/src/modules/value-editor/viewmodel.h index 8c5bdd0de..3460404e6 100644 --- a/src/modules/value-editor/viewmodel.h +++ b/src/modules/value-editor/viewmodel.h @@ -53,7 +53,6 @@ class ViewModel : public QAbstractListModel signals: void keyError(int index, const QString& error); void replaceTab(int index); - void closeWelcomeTab(); void newKeyDialog(QString dbIdentificationString, QString keyPrefix); public slots: diff --git a/src/qml/app.qml b/src/qml/app.qml index 74b17f7d8..86a9c58f2 100644 --- a/src/qml/app.qml +++ b/src/qml/app.qml @@ -156,9 +156,27 @@ ApplicationWindow { Layout.minimumHeight: 30 onCurrentIndexChanged: { - var index = currentIndex - if (tabs.getTab(0).not_mapped) index -= 1 - viewModel.setCurrentTab(index) + + if (tabs.getTab(currentIndex).tabType) { + if (tabs.getTab(currentIndex).tabType == "value") { + + var realIndex = currentIndex - serverStatsModel.tabsCount(); + + if (welcomeTab) { + realIndex -= 1 + } + + viewModel.setCurrentTab(index); + } else if (tabs.getTab(currentIndex).tabType == "server_info") { + var realIndex = currentIndex; + + if (welcomeTab) { + realIndex -= 1 + } + + serverStatsModel.setCurrentTab(index); + } + } } WelcomeTab { @@ -185,7 +203,7 @@ ApplicationWindow { Connections { target: serverStatsModel - onRowsInserted: welcomeTab.closeIfOpened() + onRowsInserted: if (welcomeTab) welcomeTab.closeIfOpened() } ValueTabs { @@ -207,7 +225,10 @@ ApplicationWindow { notification.showError(error) } - onRowsInserted: welcomeTab.closeIfOpened() + onRowsInserted: { + if (welcomeTab) welcomeTab.closeIfOpened() + } + onNewKeyDialog: addNewKeyDialog.open() } } diff --git a/src/qml/common/BetterTab.qml b/src/qml/common/BetterTab.qml index 501320a85..a661b341b 100644 --- a/src/qml/common/BetterTab.qml +++ b/src/qml/common/BetterTab.qml @@ -6,6 +6,7 @@ import QtQuick.Controls.Styles 1.1 Tab { property string icon property bool closable: true + property string tabType signal close(int index) diff --git a/src/qml/server-info/ServerInfoTabs.qml b/src/qml/server-info/ServerInfoTabs.qml index 7124a1d1c..272e0935d 100644 --- a/src/qml/server-info/ServerInfoTabs.qml +++ b/src/qml/server-info/ServerInfoTabs.qml @@ -16,6 +16,7 @@ Repeater { id: tab title: tabName icon: "qrc:/images/console.svg" + tabType: "server_info" property var model diff --git a/src/qml/value-editor/ValueTabs.qml b/src/qml/value-editor/ValueTabs.qml index fef314302..5209ae62f 100644 --- a/src/qml/value-editor/ValueTabs.qml +++ b/src/qml/value-editor/ValueTabs.qml @@ -16,6 +16,7 @@ Repeater { width: approot.width height: approot.height icon: "qrc:/images/key.svg" + tabType: "value" closable: true onClose: { From a732a444e762be12b906e90d28d6b6934364b131 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 23 Dec 2016 12:00:29 +0200 Subject: [PATCH 12/32] Fix tree rendering with multi-char NS --- .../connections-tree/items/abstractnamespaceitem.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/modules/connections-tree/items/abstractnamespaceitem.cpp b/src/modules/connections-tree/items/abstractnamespaceitem.cpp index d0f4e1a8b..5193f5a97 100644 --- a/src/modules/connections-tree/items/abstractnamespaceitem.cpp +++ b/src/modules/connections-tree/items/abstractnamespaceitem.cpp @@ -77,10 +77,8 @@ void KeysTreeRenderer::renderLazily( QSharedPointer namespaceItem = parent->findChildNamespace(firstNamespaceName); if (namespaceItem.isNull()) { - long nsEndPos = (fullKey.size() - notProcessedKeyPart.size() - + firstNamespaceName.size() - + settings.nsSeparator.length() - 1); - QByteArray namespaceFullPath = fullKey.mid(0, nsEndPos); + long nsPos = fullKey.size() - notProcessedKeyPart.size() + firstNamespaceName.size(); + QByteArray namespaceFullPath = fullKey.mid(0, nsPos); namespaceItem = QSharedPointer(new NamespaceItem(namespaceFullPath, m_operations, currentParent, parent->model(), settings)); From 5848c90c6fc648deb0f4aa525f4b7f8814c62ad4 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 23 Dec 2016 12:00:48 +0200 Subject: [PATCH 13/32] Improve connections dialog --- src/qml/ConnectionSettignsDialog.qml | 29 +++++++++++++++++++++++++++- src/qml/app.qml | 8 +++++--- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/qml/ConnectionSettignsDialog.qml b/src/qml/ConnectionSettignsDialog.qml index 7e04e1975..1d6956224 100644 --- a/src/qml/ConnectionSettignsDialog.qml +++ b/src/qml/ConnectionSettignsDialog.qml @@ -74,6 +74,14 @@ Dialog { return errors_count == 0 } + function hideLoader() { + uiBlocker.visible = false + } + + function showLoader() { + uiBlocker.visible = true + } + onVisibleChanged: { if (visible) settingsTabs.currentIndex = 0 @@ -420,7 +428,10 @@ Dialog { Button { iconSource: "qrc:/images/offline.svg" text: qsTr("Test Connection") - onClicked: root.testConnection(root.settings) + onClicked: { + showLoader() + root.testConnection(root.settings) + } } ToolButton { @@ -450,5 +461,21 @@ Dialog { } } } + + Rectangle { + id: uiBlocker + visible: false + anchors.fill: parent + color: Qt.rgba(0, 0, 0, 0.1) + + Item { + anchors.fill: parent + BusyIndicator { anchors.centerIn: parent; running: true } + } + + MouseArea { + anchors.fill: parent + } + } } } diff --git a/src/qml/app.qml b/src/qml/app.qml index 86a9c58f2..07c101fac 100644 --- a/src/qml/app.qml +++ b/src/qml/app.qml @@ -66,12 +66,14 @@ ApplicationWindow { ConnectionSettignsDialog { id: connectionSettingsDialog - onTestConnection: { + onTestConnection: { if (connectionsManager.testConnectionSettings(settings)) { + hideLoader() notification.showMsg(qsTr("Successful connection to redis-server")) } else { + hideLoader() notification.showError(qsTr("Can't connect to redis-server")) - } + } } onSaveConnection: connectionsManager.updateConnection(settings) @@ -119,7 +121,7 @@ ApplicationWindow { connectionSettingsDialog.open() } - onError: { + onError: { notification.showError(err) } From 762e062fc84716c8af881618a217e2378e3de51f Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 23 Dec 2016 12:01:19 +0200 Subject: [PATCH 14/32] Fix issue #3724: Load NS seperator from connection settings in NS deletion --- src/app/models/treeoperations.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/models/treeoperations.cpp b/src/app/models/treeoperations.cpp index c46e0d9ee..678e0dc04 100644 --- a/src/app/models/treeoperations.cpp +++ b/src/app/models/treeoperations.cpp @@ -137,7 +137,9 @@ void TreeOperations::deleteDbKey(ConnectionsTree::KeyItem& key, std::function(m_connection->getConfig()).namespaceSeparator()); QRegExp filter(pattern, Qt::CaseSensitive, QRegExp::Wildcard); int dbIndex = ns.getDbIndex(); From 9aacc328c8acb4102aac6c1c8064fc4a8616150f Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 23 Dec 2016 12:53:27 +0200 Subject: [PATCH 15/32] Fix #3691: RDM doesn't fade deleted keys & namespaces --- src/app/models/treeoperations.cpp | 4 ++-- src/modules/connections-tree/model.cpp | 15 +++++++++------ src/modules/connections-tree/model.h | 7 +++---- src/qml/connections-tree/BetterTreeView.qml | 19 +++++++++++++++---- 4 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/app/models/treeoperations.cpp b/src/app/models/treeoperations.cpp index 678e0dc04..8947ee51d 100644 --- a/src/app/models/treeoperations.cpp +++ b/src/app/models/treeoperations.cpp @@ -123,9 +123,9 @@ void TreeOperations::deleteDbKey(ConnectionsTree::KeyItem& key, std::functiongetName(); case itemIsInitiallyExpanded: return item->isExpanded(); case itemDepth: return item->itemDepth(); + case itemState: return item->isEnabled(); } return QVariant(); @@ -46,6 +47,8 @@ QHash Model::roleNames() const roles[itemType] = "type"; roles[itemIsInitiallyExpanded] = "expanded"; roles[Qt::DecorationRole] = "icon"; + roles[itemState] = "state"; + roles[itemDepth] = "depth"; return roles; } @@ -227,14 +230,14 @@ void Model::onExpandItem(QWeakPointer item) } -QVariant Model::getItemDepth(const QModelIndex &index) +QVariant Model::getItemData(const QModelIndex &index, const QString& dataKey) { - return data(index, itemDepth); -} + QList result = roleNames().keys(dataKey.toLatin1()); -QVariant Model::getItemType(const QModelIndex &index) -{ - return data(index, itemType); + if (result.size() == 0) + return QVariant(); + + return data(index, result[0]); } QVariant Model::getMetadata(const QModelIndex &index, const QString &metaKey) diff --git a/src/modules/connections-tree/model.h b/src/modules/connections-tree/model.h index 45950f8d5..2fc4e5b90 100644 --- a/src/modules/connections-tree/model.h +++ b/src/modules/connections-tree/model.h @@ -22,7 +22,8 @@ namespace ConnectionsTree { itemType, itemFullPath, itemIsInitiallyExpanded, - itemDepth + itemDepth, + itemState }; public: @@ -97,9 +98,7 @@ namespace ConnectionsTree { void onExpandItem(QWeakPointer item); public slots: - QVariant getItemDepth(const QModelIndex &index); - - QVariant getItemType(const QModelIndex &index); + QVariant getItemData(const QModelIndex &index, const QString& dataKey); QVariant getMetadata(const QModelIndex &index, const QString& metaKey); diff --git a/src/qml/connections-tree/BetterTreeView.qml b/src/qml/connections-tree/BetterTreeView.qml index 36a097796..6fc113962 100644 --- a/src/qml/connections-tree/BetterTreeView.qml +++ b/src/qml/connections-tree/BetterTreeView.qml @@ -45,24 +45,35 @@ TreeView { anchors.verticalCenter: parent.verticalCenter anchors.rightMargin: 10 + property bool itemEnabled: connectionsManager.getItemData(styleData.index, "state") Text { anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter //elide: styleData.elideMode - text: styleData.value + text: wrapper.itemEnabled ? styleData.value : styleData.value + qsTr(" (Removed)") + color: wrapper.itemEnabled ? "black": "#ccc" anchors.leftMargin: { - var itemDepth = connectionsManager.getItemDepth(styleData.index) + var itemDepth = connectionsManager.getItemData(styleData.index, "depth") return itemDepth * 10 + 15 } } + Timer { + id: selectionTimer + interval: 1000; + running: styleData.index && styleData.selected && wrapper.itemEnabled + repeat: true + triggeredOnStart: true + onTriggered: wrapper.itemEnabled = connectionsManager.getItemData(styleData.index, "state") + } + Loader { id: menuLoader anchors {right: wrapper.right; top: wrapper.top; bottom: wrapper.bottom; } anchors.rightMargin: 20 height: parent.height - visible: styleData.selected + visible: styleData.selected && wrapper.itemEnabled asynchronous: true source: { @@ -71,7 +82,7 @@ TreeView { || !styleData.index) return "" - var type = connectionsManager.getItemType(styleData.index) + var type = connectionsManager.getItemData(styleData.index, "type") if (type != undefined) { return "./menu/" + type + ".qml" From 44c62a08812d2e61731effccfbf5ff6a7f679b39 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 31 Mar 2017 17:40:28 +0300 Subject: [PATCH 16/32] Remove qgamp and clean code --- .gitmodules | 3 --- 3rdparty/3rdparty.pri | 4 ---- 3rdparty/qgamp | 1 - src/app/app.cpp | 21 ++--------------- src/app/app.h | 4 +--- src/modules/value-editor/viewmodel.cpp | 5 +---- src/qml/WelcomeTab.qml | 11 --------- src/qml/app.qml | 2 +- src/qml/connections-tree/BetterTreeView.qml | 2 +- src/qml/console/Consoles.qml | 1 - src/qml/server-info/ServerInfoTabs.qml | 1 - src/qml/value-editor/ValueTabs.qml | 25 +++++---------------- 12 files changed, 12 insertions(+), 68 deletions(-) delete mode 160000 3rdparty/qgamp diff --git a/.gitmodules b/.gitmodules index 7ae056c1d..8b3ddd4fa 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,6 +10,3 @@ [submodule "3rdparty/gbreakpad"] path = 3rdparty/gbreakpad url = https://chromium.googlesource.com/breakpad/breakpad -[submodule "3rdparty/qgamp"] - path = 3rdparty/qgamp - url = https://github.com/uglide/qgamp.git diff --git a/3rdparty/3rdparty.pri b/3rdparty/3rdparty.pri index 8cd846656..cd2068d30 100644 --- a/3rdparty/3rdparty.pri +++ b/3rdparty/3rdparty.pri @@ -9,10 +9,6 @@ OTHER_FILES += $$PWD/../src/resources/qml/3rdparty/php-unserialize-js/phpUnseria # qredisclient include($$PWD/qredisclient/qredisclient.pri) -#qgamp -include($$PWD/qgamp/qgamp.pri) -DEFINES += GMP_ID=\\\"UA-68484170-1\\\" - # Easylogging INCLUDEPATH += $$PWD/easyloggingpp/src HEADERS += $$PWD/easyloggingpp/src/easylogging++.h diff --git a/3rdparty/qgamp b/3rdparty/qgamp deleted file mode 160000 index f4da8a860..000000000 --- a/3rdparty/qgamp +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f4da8a86048cc6c74083190a2d7a424e73c01cd7 diff --git a/src/app/app.cpp b/src/app/app.cpp index 94d33bfdf..c34ac2be3 100644 --- a/src/app/app.cpp +++ b/src/app/app.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include "logger.h" @@ -30,14 +29,6 @@ INITIALIZE_EASYLOGGINGPP -static QObject *analytics_singletontype_provider(QQmlEngine *engine, QJSEngine *scriptEngine) -{ - Q_UNUSED(engine) - Q_UNUSED(scriptEngine) - - GoogleMP *gmp = GoogleMP::instance(); - return gmp; -} Application::Application(int &argc, char **argv) : QApplication(argc, argv), @@ -47,8 +38,7 @@ Application::Application(int &argc, char **argv) // Init components required for models and qml initLog(); initAppInfo(); - initAppFonts(); - initAppAnalytics(); + initAppFonts(); initRedisClient(); initUpdater(); installTranslator(); @@ -108,17 +98,10 @@ void Application::initAppFonts() QApplication::setFont(defaultFont); } -void Application::initAppAnalytics() -{ - GoogleMP::startSession(QDateTime::currentMSecsSinceEpoch()); - GoogleMP::instance()->reportEvent("rdm:cpp", "app start", ""); -} - void Application::registerQmlTypes() { qmlRegisterType("rdm.models", 1, 0, "ValueViewModel"); - qmlRegisterType("rdm.models", 1, 0, "SortFilterProxyModel"); - qmlRegisterSingletonType("MeasurementProtocol", 1, 0, "Analytics", analytics_singletontype_provider); + qmlRegisterType("rdm.models", 1, 0, "SortFilterProxyModel"); qRegisterMetaType(); } diff --git a/src/app/app.h b/src/app/app.h index 31cd17753..e6e5a4245 100644 --- a/src/app/app.h +++ b/src/app/app.h @@ -4,7 +4,6 @@ #include #include #include -#include #ifndef RDM_VERSION #include "../version.h" @@ -31,8 +30,7 @@ class Application : public QApplication private: void initAppInfo(); - void initAppFonts(); - void initAppAnalytics(); + void initAppFonts(); void registerQmlTypes(); void registerQmlRootObjects(); void initLog(); diff --git a/src/modules/value-editor/viewmodel.cpp b/src/modules/value-editor/viewmodel.cpp index 1047c240c..6e8e0a42e 100644 --- a/src/modules/value-editor/viewmodel.cpp +++ b/src/modules/value-editor/viewmodel.cpp @@ -2,7 +2,6 @@ #include #include #include -#include #include "connections-tree/items/keyitem.h" #include "value-editor/valueviewmodel.h" @@ -30,9 +29,7 @@ void ValueEditor::ViewModel::openTab(QSharedPointer con { removeModel(keyModel); key.setRemoved(); //Disable key in connections tree - }); - - GoogleMP::instance()->showScreen(QString("ValueEditor-%1").arg(keyModel->getType())); + }); }); // TODO: add empty key model for loading } catch (...) { diff --git a/src/qml/WelcomeTab.qml b/src/qml/WelcomeTab.qml index e50ca70cc..fba689485 100644 --- a/src/qml/WelcomeTab.qml +++ b/src/qml/WelcomeTab.qml @@ -70,17 +70,6 @@ BetterTab { Rectangle { color: "#cccccc"; Layout.preferredHeight: 1; Layout.fillWidth: true } - RichTextWithLinks { - Layout.fillWidth: true - html: '' - + ' Redis Desktop Manager uses Google Analytics to track which features you are using. ' - + '
 This data helps me to develop features that you actually need :)' - + '
 RDM doesn\'t send any sensitive information or data from your databases.' - + ' More >' - + '
'} - - Rectangle { color: "#cccccc"; Layout.preferredHeight: 1; Layout.fillWidth: true } - Item { Layout.fillWidth: true Layout.preferredHeight: 10 diff --git a/src/qml/app.qml b/src/qml/app.qml index 07c101fac..68a055815 100644 --- a/src/qml/app.qml +++ b/src/qml/app.qml @@ -168,7 +168,7 @@ ApplicationWindow { realIndex -= 1 } - viewModel.setCurrentTab(index); + viewModel.setCurrentTab(realIndex); } else if (tabs.getTab(currentIndex).tabType == "server_info") { var realIndex = currentIndex; diff --git a/src/qml/connections-tree/BetterTreeView.qml b/src/qml/connections-tree/BetterTreeView.qml index 6fc113962..211e61997 100644 --- a/src/qml/connections-tree/BetterTreeView.qml +++ b/src/qml/connections-tree/BetterTreeView.qml @@ -45,7 +45,7 @@ TreeView { anchors.verticalCenter: parent.verticalCenter anchors.rightMargin: 10 - property bool itemEnabled: connectionsManager.getItemData(styleData.index, "state") + property bool itemEnabled: connectionsManager? connectionsManager.getItemData(styleData.index, "state") : true Text { anchors.left: parent.left diff --git a/src/qml/console/Consoles.qml b/src/qml/console/Consoles.qml index d8968cb43..49939eba3 100644 --- a/src/qml/console/Consoles.qml +++ b/src/qml/console/Consoles.qml @@ -4,7 +4,6 @@ import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.1 import QtQuick.Dialogs 1.2 import QtQuick.Window 2.2 -import MeasurementProtocol 1.0 import "./../common" diff --git a/src/qml/server-info/ServerInfoTabs.qml b/src/qml/server-info/ServerInfoTabs.qml index 272e0935d..86c78c06d 100644 --- a/src/qml/server-info/ServerInfoTabs.qml +++ b/src/qml/server-info/ServerInfoTabs.qml @@ -5,7 +5,6 @@ import QtQuick.Controls.Styles 1.1 import QtQuick.Dialogs 1.2 import QtQuick.Window 2.2 import QtCharts 2.0 -import MeasurementProtocol 1.0 import "./../common" diff --git a/src/qml/value-editor/ValueTabs.qml b/src/qml/value-editor/ValueTabs.qml index 5209ae62f..075a4155e 100644 --- a/src/qml/value-editor/ValueTabs.qml +++ b/src/qml/value-editor/ValueTabs.qml @@ -4,7 +4,6 @@ import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.1 import QtQuick.Dialogs 1.2 import QtQuick.Window 2.2 -import MeasurementProtocol 1.0 import "./editors/editor.js" as Editor import "./../common" import rdm.models 1.0 @@ -166,9 +165,7 @@ Repeater { onClicked: { newKeyName.text = keyNameField.text - renameConfirmation.open() - - Analytics.reportEvent("value-editor", "rename-key") + renameConfirmation.open() } } @@ -191,9 +188,7 @@ Repeater { } onClicked: { - deleteConfirmation.open() - - Analytics.reportEvent("value-editor", "delete-key") + deleteConfirmation.open() } } @@ -238,9 +233,7 @@ Repeater { onClicked: { newTTL.text = ""+keyTtl - setTTLConfirmation.open() - - Analytics.reportEvent("value-editor", "set-key-ttl") + setTTLConfirmation.open() } } } @@ -423,9 +416,7 @@ Repeater { text: qsTr("Add Row"); iconSource: "qrc:/images/add.svg" onClicked: { - addRowDialog.open() - - Analytics.reportEvent("value-editor", "add-row") + addRowDialog.open() } Dialog { @@ -489,9 +480,7 @@ Repeater { console.log("Original row index in model:", rowIndex) deleteRowConfirmation.rowToDelete = rowIndex - deleteRowConfirmation.open() - - Analytics.reportEvent("value-editor", "delete-row") + deleteRowConfirmation.open() } MessageDialog { @@ -523,9 +512,7 @@ Repeater { onTriggered: { console.log("Reload value in tab") keyTab.keyModel.reload() - valueEditor.clear() - - Analytics.reportEvent("value-editor", "reload-key") + valueEditor.clear() } } } From 449c0c8aba75f61061655b1bf3370edea0b99120 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 31 Mar 2017 17:50:50 +0300 Subject: [PATCH 17/32] Migrate to Qt 5.8 --- .travis.yml | 2 +- appveyor.yml | 2 +- src/configure | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index d2509ccb0..4cff060d0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ install: fi - cd ./src && ./configure && cd ./../ - if [ "$TRAVIS_OS_NAME" == "linux" ]; then - source /opt/qt57/bin/qt57-env.sh + source /opt/qt58/bin/qt58-env.sh ; fi - qmake -v diff --git a/appveyor.yml b/appveyor.yml index 8c5ce9acb..8a90e6c56 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -3,7 +3,7 @@ version: 0.9.0.{build} clone_depth: 5 install: - git submodule update --init --recursive -- set QTDIR=C:\Qt\5.7\msvc2015 +- set QTDIR=C:\Qt\5.8\msvc2015 - set PATH=%QTDIR%\bin;%PATH% - call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" - nuget install -Version 1.6.0.2 -OutputDirectory ./3rdparty/qredisclient/3rdparty/windows rmt_libssh2 diff --git a/src/configure b/src/configure index 30e77ddeb..6c490e1ca 100755 --- a/src/configure +++ b/src/configure @@ -12,7 +12,7 @@ DIR=$(dirname "$(readlink -f "$0")") && RDM_DIR=$DIR/../ GetOSVersion -RDM_QT_VERSION=${RDM_QT_VERSION:-57} +RDM_QT_VERSION=${RDM_QT_VERSION:-58} if [ "$1" == "breakpad" ]; then print_title "Build only Breakpad" From 2378df27eb6a2a9b54bd16ac92086aca9ab70d3b Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 31 Mar 2017 18:08:56 +0300 Subject: [PATCH 18/32] Change breakpad origin to github --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 8b3ddd4fa..6cc0c5cf7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,4 +9,4 @@ url = https://github.com/uglide/qredisclient.git [submodule "3rdparty/gbreakpad"] path = 3rdparty/gbreakpad - url = https://chromium.googlesource.com/breakpad/breakpad + url = https://github.com/google/breakpad.git From ab4458333b27486c051c373030fbb0977ff9cbe2 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 31 Mar 2017 18:16:31 +0300 Subject: [PATCH 19/32] Update breakpad --- 3rdparty/gbreakpad | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdparty/gbreakpad b/3rdparty/gbreakpad index daed3a438..aa7115cfd 160000 --- a/3rdparty/gbreakpad +++ b/3rdparty/gbreakpad @@ -1 +1 @@ -Subproject commit daed3a43836e0fe95003141338af9355736764a0 +Subproject commit aa7115cfdef1c4641830cee5621d2c919dc417b8 From 8af79a4837affe530b0d81429a21fff290047af7 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 14 Apr 2017 19:15:38 +0300 Subject: [PATCH 20/32] Update qredisclient --- 3rdparty/qredisclient | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdparty/qredisclient b/3rdparty/qredisclient index 9081d1905..a2258a4a3 160000 --- a/3rdparty/qredisclient +++ b/3rdparty/qredisclient @@ -1 +1 @@ -Subproject commit 9081d1905a34199da4883ef6f681ca19440f0389 +Subproject commit a2258a4a37e9896ff5f86b19b821cdd548d3efbb From f600bd0acd018358542a67b6ea6788dc41402000 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 21 Apr 2017 11:12:15 +0300 Subject: [PATCH 21/32] Fix Qt installation on Ubuntu --- src/configure | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/configure b/src/configure index 2e3362ce2..bf033595e 100755 --- a/src/configure +++ b/src/configure @@ -48,7 +48,7 @@ if [ "$os_VENDOR" == "Ubuntu" ]; then sudo apt-get update -y sudo apt-get install qt${RDM_QT_VERSION:0:2}base qt${RDM_QT_VERSION:0:2}imageformats qt${RDM_QT_VERSION:0:2}tools \ - qt${RDM_QT_VERSION:0:2}declarative qt${RDM_QT_VERSION:0:2}quickcontrols qt${RDM_QT_VERSION:0:2}charts-no-lgpl qt${RDM_QT_VERSION:0:2}datavis-no-lgpl qt${RDM_QT_VERSION:0:2}svg -y + qt${RDM_QT_VERSION:0:2}declarative qt${RDM_QT_VERSION:0:2}quickcontrols qt${RDM_QT_VERSION:0:2}charts-no-lgpl qt${RDM_QT_VERSION:0:2}webengine qt${RDM_QT_VERSION:0:2}svg -y fi print_title "Check deps" From 701693a10ce00b0769a58f9b423267f3287d18d0 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 21 Apr 2017 12:02:34 +0300 Subject: [PATCH 22/32] Update rdm.desktop --- src/resources/rdm.desktop | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/resources/rdm.desktop b/src/resources/rdm.desktop index e2731df16..7f8056aad 100644 --- a/src/resources/rdm.desktop +++ b/src/resources/rdm.desktop @@ -4,7 +4,7 @@ Name=Redis Desktop Manager Comment=Redis Desktop Manager Type=Application Categories=Development; -Exec=/usr/share/redis-desktop-manager/bin/rdm.sh +Exec=rdm Terminal=false StartupNotify=true -Icon=/usr/share/redis-desktop-manager/bin/rdm.png +Icon=rdm From 8f42d8e3084dfae8e0c7131354dccb9c3e17aa68 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 21 Apr 2017 12:14:19 +0300 Subject: [PATCH 23/32] Do not build libssh2 if static lib exists --- build/common_functions | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/build/common_functions b/build/common_functions index 651cd2ed6..435ea0310 100644 --- a/build/common_functions +++ b/build/common_functions @@ -15,14 +15,16 @@ function build_breakpad { } function build_libssh2 { - cd $DEPS_DIR - git clone https://github.com/libssh2/libssh2.git libssh2 || true - cd libssh2 - mkdir bin || rm -fR ./bin && mkdir bin - cd bin - cmake -DCRYPTO_BACKEND=OpenSSL -DENABLE_ZLIB_COMPRESSION=ON .. - cmake --build . - sudo make install + if [ ! -f /usr/local/lib/libssh2.a ]; then + cd $DEPS_DIR + git clone https://github.com/libssh2/libssh2.git libssh2 || true + cd libssh2 + mkdir bin || rm -fR ./bin && mkdir bin + cd bin + cmake -DCRYPTO_BACKEND=OpenSSL -DENABLE_ZLIB_COMPRESSION=ON .. + cmake --build . + sudo make install + fi } function print_line { From 9b5db95ea8b93bf5acb6599138d9d81e973ecd42 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 21 Apr 2017 12:19:34 +0300 Subject: [PATCH 24/32] Update common_functions --- build/common_functions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/common_functions b/build/common_functions index 435ea0310..2c0633ea2 100644 --- a/build/common_functions +++ b/build/common_functions @@ -11,7 +11,7 @@ function build_breakpad { git clone https://chromium.googlesource.com/linux-syscall-support src/third_party/lss || true touch README ./configure - make -j 2 + make -s -j 2 } function build_libssh2 { From 715fef87acde2c8f7506753a43eee9f474f87d17 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 21 Apr 2017 13:17:47 +0300 Subject: [PATCH 25/32] Remove webengine usage --- .../value-editor/editors/MultilineEditor.qml | 63 ++++++++++--------- src/rdm.pro | 2 +- 2 files changed, 33 insertions(+), 32 deletions(-) diff --git a/src/qml/value-editor/editors/MultilineEditor.qml b/src/qml/value-editor/editors/MultilineEditor.qml index df3a7dc81..9c486583f 100644 --- a/src/qml/value-editor/editors/MultilineEditor.qml +++ b/src/qml/value-editor/editors/MultilineEditor.qml @@ -41,17 +41,18 @@ ColumnLayout formatter.instance.getFormatted(root.value, function (formatted, isReadOnly, format) { - if (format == "json") { - // 1 is JSON - return formatterSelector.model[1].instance.getFormatted(formatted, function (formattedJson, r, f) { - webView.setText(formattedJson, false, isReadOnly) - uiBlocker.visible = false - }) - } else { - webView.setText(formatted, format === "html", isReadOnly) - } - - uiBlocker.visible = false +// TODO: fixme +// if (format == "json") { +// // 1 is JSON +// return formatterSelector.model[1].instance.getFormatted(formatted, function (formattedJson, r, f) { +// webView.setText(formattedJson, false, isReadOnly) +// uiBlocker.visible = false +// }) +// } else { +// webView.setText(formatted, format === "html", isReadOnly) +// } + +// uiBlocker.visible = false }) } @@ -79,32 +80,32 @@ ColumnLayout } } - WebEngineView { - id: webView - Layout.fillWidth: true - Layout.fillHeight: true - Layout.preferredHeight: 100 +// WebEngineView { +// id: webView +// Layout.fillWidth: true +// Layout.fillHeight: true +// Layout.preferredHeight: 100 - onJavaScriptConsoleMessage: { - console.log("Web:", message) - } +// onJavaScriptConsoleMessage: { +// console.log("Web:", message) +// } - function setText(text, html, readOnly) { +// function setText(text, html, readOnly) { - if (html) { - webView.loadHtml("" + text +"") - } else { - var attr = ""; +// if (html) { +// webView.loadHtml("" + text +"") +// } else { +// var attr = ""; - if (readOnly) { - attr = "readonly"; - } +// if (readOnly) { +// attr = "readonly"; +// } - webView.loadHtml("") - } +// webView.loadHtml("") +// } - } - } +// } +// } Rectangle { diff --git a/src/rdm.pro b/src/rdm.pro index 23ca842ff..e8ac7a375 100644 --- a/src/rdm.pro +++ b/src/rdm.pro @@ -4,7 +4,7 @@ # #------------------------------------------------- -QT += core gui network concurrent widgets quick quickwidgets charts webengine +QT += core gui network concurrent widgets quick quickwidgets charts TARGET = rdm TEMPLATE = app From d2c95c7a667aea05796939e136ac5f15d12eebf4 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 21 Apr 2017 13:18:48 +0300 Subject: [PATCH 26/32] Remove import --- src/qml/value-editor/editors/MultilineEditor.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/src/qml/value-editor/editors/MultilineEditor.qml b/src/qml/value-editor/editors/MultilineEditor.qml index 9c486583f..01662e510 100644 --- a/src/qml/value-editor/editors/MultilineEditor.qml +++ b/src/qml/value-editor/editors/MultilineEditor.qml @@ -2,7 +2,6 @@ import QtQuick 2.0 import QtQuick.Controls 1.2 import QtQuick.Controls.Styles 1.2 import QtQuick.Layouts 1.1 -import QtWebEngine 1.0 import "./formatters/formatters.js" as Formatters From f0c82b53a6bef46091e2ba1394dfa032a0ce091e Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 21 Apr 2017 13:28:35 +0300 Subject: [PATCH 27/32] Remove import --- src/app/app.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/app/app.cpp b/src/app/app.cpp index c34ac2be3..37f38c46d 100644 --- a/src/app/app.cpp +++ b/src/app/app.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include @@ -120,8 +119,7 @@ void Application::registerQmlRootObjects() } void Application::initQml() -{ - QtWebEngine::initialize(); +{ registerQmlTypes(); registerQmlRootObjects(); m_engine.load(QUrl(QStringLiteral("qrc:///app.qml"))); From 73b364aa3abba2576287009c34752b407bd0b1fd Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 21 Apr 2017 14:20:17 +0300 Subject: [PATCH 28/32] Revert "Update rdm.desktop" This reverts commit 701693a10ce00b0769a58f9b423267f3287d18d0. --- src/resources/rdm.desktop | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/resources/rdm.desktop b/src/resources/rdm.desktop index 7f8056aad..e2731df16 100644 --- a/src/resources/rdm.desktop +++ b/src/resources/rdm.desktop @@ -4,7 +4,7 @@ Name=Redis Desktop Manager Comment=Redis Desktop Manager Type=Application Categories=Development; -Exec=rdm +Exec=/usr/share/redis-desktop-manager/bin/rdm.sh Terminal=false StartupNotify=true -Icon=rdm +Icon=/usr/share/redis-desktop-manager/bin/rdm.png From 25f520f8a9b3641cf623c86189e69a0265d3714c Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 21 Apr 2017 14:21:45 +0300 Subject: [PATCH 29/32] Update configure script --- src/configure | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/configure b/src/configure index bf033595e..8387c0a20 100755 --- a/src/configure +++ b/src/configure @@ -48,7 +48,7 @@ if [ "$os_VENDOR" == "Ubuntu" ]; then sudo apt-get update -y sudo apt-get install qt${RDM_QT_VERSION:0:2}base qt${RDM_QT_VERSION:0:2}imageformats qt${RDM_QT_VERSION:0:2}tools \ - qt${RDM_QT_VERSION:0:2}declarative qt${RDM_QT_VERSION:0:2}quickcontrols qt${RDM_QT_VERSION:0:2}charts-no-lgpl qt${RDM_QT_VERSION:0:2}webengine qt${RDM_QT_VERSION:0:2}svg -y + qt${RDM_QT_VERSION:0:2}declarative qt${RDM_QT_VERSION:0:2}quickcontrols qt${RDM_QT_VERSION:0:2}charts-no-lgpl qt${RDM_QT_VERSION:0:2}svg -y fi print_title "Check deps" @@ -80,7 +80,7 @@ elif [ "$os_VENDOR" == "LinuxMint" ]; then sudo apt-get update -y sudo apt-get install qt${RDM_QT_VERSION:0:2}base qt${RDM_QT_VERSION:0:2}imageformats qt${RDM_QT_VERSION:0:2}tools \ - qt${RDM_QT_VERSION:0:2}declarative qt${RDM_QT_VERSION:0:2}quickcontrols qt${RDM_QT_VERSION:0:2}charts-no-lgpl qt${RDM_QT_VERSION:0:2}datavis-no-lgpl qt${RDM_QT_VERSION:0:2}svg -y + qt${RDM_QT_VERSION:0:2}declarative qt${RDM_QT_VERSION:0:2}quickcontrols qt${RDM_QT_VERSION:0:2}charts-no-lgpl qt${RDM_QT_VERSION:0:2}svg -y fi print_title "Check deps" From 7add88f072df77b6231249695b3eb9e1813b118c Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 21 Apr 2017 19:05:39 +0300 Subject: [PATCH 30/32] Update value editor --- src/qml/connections-tree/BetterTreeView.qml | 8 +- src/qml/value-editor/AddKeyDialog.qml | 66 +++++---- src/qml/value-editor/ValueTabs.qml | 55 ++++--- .../value-editor/editors/AbstractEditor.qml | 18 +-- .../value-editor/editors/HashItemEditor.qml | 65 +++------ .../value-editor/editors/MultilineEditor.qml | 136 ++++++++++++------ .../value-editor/editors/SingleItemEditor.qml | 40 ++---- .../editors/SortedSetItemEditor.qml | 68 +++++---- .../editors/formatters/formatters.js | 58 +++----- 9 files changed, 268 insertions(+), 246 deletions(-) diff --git a/src/qml/connections-tree/BetterTreeView.qml b/src/qml/connections-tree/BetterTreeView.qml index 211e61997..9294aa079 100644 --- a/src/qml/connections-tree/BetterTreeView.qml +++ b/src/qml/connections-tree/BetterTreeView.qml @@ -54,8 +54,12 @@ TreeView { text: wrapper.itemEnabled ? styleData.value : styleData.value + qsTr(" (Removed)") color: wrapper.itemEnabled ? "black": "#ccc" anchors.leftMargin: { - var itemDepth = connectionsManager.getItemData(styleData.index, "depth") - return itemDepth * 10 + 15 + if (connectionsManager) { + var itemDepth = connectionsManager.getItemData(styleData.index, "depth") + return itemDepth * 10 + 15 + } else { + return 35 + } } } diff --git a/src/qml/value-editor/AddKeyDialog.qml b/src/qml/value-editor/AddKeyDialog.qml index d49338a84..f61ebd258 100644 --- a/src/qml/value-editor/AddKeyDialog.qml +++ b/src/qml/value-editor/AddKeyDialog.qml @@ -23,26 +23,33 @@ Dialog { anchors.fill: parent anchors.margins: 5 - Text { text: qsTr("Key:") } + Text { + text: qsTr("Key:") + } TextField { id: newKeyName Layout.fillWidth: true } - Text { text: qsTr("Type:") } + Text { + text: qsTr("Type:") + } ComboBox { id: typeSelector model: Editor.getSupportedKeyTypes() Layout.fillWidth: true } - Text { text: qsTr("Value:") } + Text { + text: qsTr("Value:") + } Loader { id: valueAddEditor Layout.fillWidth: true Layout.preferredHeight: 300 - source: Editor.getEditorByTypeString(typeSelector.model[typeSelector.currentIndex]) + source: Editor.getEditorByTypeString( + typeSelector.model[typeSelector.currentIndex]) onLoaded: { item.state = "new" @@ -52,7 +59,9 @@ Dialog { RowLayout { Layout.fillWidth: true Layout.minimumHeight: 40 - Item { Layout.fillWidth: true} + Item { + Layout.fillWidth: true + } Button { text: qsTr("Save") @@ -60,28 +69,28 @@ Dialog { if (!valueAddEditor.item) return - if (!valueAddEditor.item.isValueValid() - || newKeyName.text.length == 0) { - valueAddEditor.item.markInvalidFields() - return - } + valueAddEditor.item.validateValue(function (result) { + if (!result) + return; + + var row = valueAddEditor.item.getValue() + viewModel.addKey( + newKeyName.text, + typeSelector.model[typeSelector.currentIndex], + row, afterSave + ); + }) + } - var row = valueAddEditor.item.getValue() - viewModel.addKey( - newKeyName.text, - typeSelector.model[typeSelector.currentIndex], - row, - function callback(err) { - if (!err) { - newKeyName.text = '' - valueAddEditor.item.reset() - root.close() - } else { - addError.text = err - addError.open() - } - } - ) + function afterSave(err) { + if (!err) { + newKeyName.text = '' + valueAddEditor.item.reset() + root.close() + } else { + addError.text = err + addError.open() + } } } @@ -90,7 +99,9 @@ Dialog { onClicked: root.close() } } - Item { Layout.fillWidth: true} + Item { + Layout.fillWidth: true + } } } @@ -104,4 +115,3 @@ Dialog { standardButtons: StandardButton.Ok } } - diff --git a/src/qml/value-editor/ValueTabs.qml b/src/qml/value-editor/ValueTabs.qml index 075a4155e..79f0ad052 100644 --- a/src/qml/value-editor/ValueTabs.qml +++ b/src/qml/value-editor/ValueTabs.qml @@ -51,7 +51,7 @@ Repeater { table.searchField.text = "" if (valueEditor.item) - valueEditor.item.resetAndDisableEditor() + valueEditor.item.reset() table.loadValue() } @@ -441,21 +441,32 @@ Repeater { } } + Timer { + id: reOpenTimer + onTriggered: { + addRowDialog.open() + } + repeat: false + interval: 50 + } + onAccepted: { if (!valueAddEditor.item) return false - if (!valueAddEditor.item.isValueValid()) { - valueAddEditor.item.markInvalidFields() - return open() - } + valueAddEditor.item.validateValue(function (result){ + if (!result) { + reOpenTimer.start(); + return; + } - var row = valueAddEditor.item.getValue() + var row = valueAddEditor.item.getValue() + var model = viewModel.getValue(tabIndex) - var model = viewModel.getValue(tabIndex) - model.addRow(row) - keyTab.keyModel.reload() - valueAddEditor.item.reset() + model.addRow(row) + keyTab.keyModel.reload() + valueAddEditor.item.reset() + }); } visible: false @@ -578,11 +589,6 @@ Repeater { source: Editor.getEditorByTypeString(keyType) - onLoaded: { - if (valueEditor.item) - valueEditor.item.resetAndDisableEditor() - } - function loadRowValue(row) { if (valueEditor.item) { var rowValue = keyTab.keyModel.getRow(row, true) @@ -616,21 +622,26 @@ Repeater { text: qsTr("Save") onClicked: { - if (!valueEditor.item || !valueEditor.item.isValueChanged()) { + if (!valueEditor.item || !valueEditor.item.isEdited()) { savingConfirmation.text = qsTr("Nothing to save") savingConfirmation.open() return } - var value = valueEditor.item.getValue() + valueEditor.item.validateValue(function (result){ - console.log(value, value["value"]) - keyTab.keyModel.updateRow(valueEditor.currentRow, value) + if (!result) + return; - savingConfirmation.text = qsTr("Value was updated!") - savingConfirmation.open() - } + var value = valueEditor.item.getValue() + console.log(value, value["value"]) + keyTab.keyModel.updateRow(valueEditor.currentRow, value) + + savingConfirmation.text = qsTr("Value was updated!") + savingConfirmation.open() + }) + } } MessageDialog { diff --git a/src/qml/value-editor/editors/AbstractEditor.qml b/src/qml/value-editor/editors/AbstractEditor.qml index 51a01e598..a3e5ce770 100644 --- a/src/qml/value-editor/editors/AbstractEditor.qml +++ b/src/qml/value-editor/editors/AbstractEditor.qml @@ -9,16 +9,16 @@ ColumnLayout { state: "edit" states: [ - State { name: "new"}, - State { name: "add"}, - State { name: "edit"} + State { name: "new"}, // Creating new key + State { name: "add"}, // Adding new value to existing key + State { name: "edit"} // Editing existing key ] - function isValueChanged() { + function validateValue(callback) { console.exception("Not implemented") } - function resetAndDisableEditor() { + function isEdited() { console.exception("Not implemented") } @@ -30,14 +30,6 @@ ColumnLayout { console.exception("Not implemented") } - function isValueValid() { - console.exception("Not implemented") - } - - function markInvalidFields() { - console.exception("Not implemented") - } - function reset() { console.exception("Not implemented") } diff --git a/src/qml/value-editor/editors/HashItemEditor.qml b/src/qml/value-editor/editors/HashItemEditor.qml index e2698f9fd..9e3f026ac 100644 --- a/src/qml/value-editor/editors/HashItemEditor.qml +++ b/src/qml/value-editor/editors/HashItemEditor.qml @@ -10,6 +10,8 @@ AbstractEditor { id: root anchors.fill: parent + property bool active: false + MultilineEditor { id: keyText fieldLabel: qsTr("Key:") @@ -18,69 +20,46 @@ AbstractEditor { Layout.preferredHeight: 90 value: "" - enabled: originalValue != "" || root.state !== "edit" - property var originalValue: "" - showFormatters: root.state != "new" - - backgroundColor: (!keyText.value && keyText.enabled - && keyText.readOnly == false) ? "lightyellow" : "white" + enabled: root.active || root.state !== "edit" + showFormatters: root.state != "new" } - MultilineEditor { id: textArea Layout.fillWidth: true Layout.fillHeight: true - enabled: keyText.originalValue != "" || root.state !== "edit" - property var originalValue: "" - showFormatters: root.state != "new" + enabled: root.active || root.state !== "edit" + showFormatters: root.state != "new" + } - backgroundColor: (!textArea.value && textArea.enabled - && textArea.readOnly == false) ? "lightyellow" : "white" + function validateValue(callback) { + return textArea.validate(function (textAreaValid) { + keyText.validate(function (keyTextValid) { + return callback(textAreaValid && keyTextValid); + }); + }); } function setValue(rowValue) { if (!rowValue) return - keyText.originalValue = rowValue['key'] - keyText.setValue(rowValue['key']) - textArea.originalValue = rowValue['value'] - textArea.setValue(rowValue['value']) - } - - function isValueChanged() { - return textArea.originalValue != textArea.getText() - || keyText.originalValue != keyText.text + active = true + keyText.loadFormattedValue(rowValue['key']) + textArea.loadFormattedValue(rowValue['value']) } - function resetAndDisableEditor() { - textArea.value = "" - textArea.originalValue = "" - keyText.originalValue = "" - keyText.value = "" + function isEdited() { + return textArea.isEdited || keyText.isEdited } function getValue() { - return {"value": textArea.getText(), "key": keyText.getText()} - } - - function isValueValid() { - var value = getValue() - - return value && value['key'] && value['key'].length > 0 - } - - function markInvalidFields() { - keyText.textColor = "black" - textArea.textColor = "black" - // Fixme + return {"value": textArea.value, "key": keyText.value} } function reset() { - textArea.originalValue = "" - textArea.value = "" - keyText.originalValue = "" - keyText.value = "" + textArea.reset() + keyText.reset() + active = false } } diff --git a/src/qml/value-editor/editors/MultilineEditor.qml b/src/qml/value-editor/editors/MultilineEditor.qml index 01662e510..b162bc3c7 100644 --- a/src/qml/value-editor/editors/MultilineEditor.qml +++ b/src/qml/value-editor/editors/MultilineEditor.qml @@ -13,19 +13,46 @@ ColumnLayout property string textColor property string backgroundColor property bool showFormatters: true - property string fieldLabel: qsTr("Value:") - property var value + property string fieldLabel: qsTr("Value:") + property bool isEdited: false + property var value + + function validate(callback) { + loadRawValue(function (error, raw) { + if (error) { + // TODO: Show formatter error + console.log("Formatter error") + return callback(false); + } + + var valid = raw.length > 0 + + if (!valid) { + showValidationError("Enter value") + } else { + hideValidationError() + } - function getText() { - // FIXME + return callback(valid) + }); } - function setValue(val) { - value = val - loadFormattedValue() + function loadRawValue(callback) { + var formatter = formatterSelector.model[formatterSelector.currentIndex] + + formatter.instance.getRaw(textArea.text, function (error, raw) { + root.value = raw + return callback(error, raw) + }) } - function loadFormattedValue() { + function loadFormattedValue(val) { + + value = val + + if (!value) + return + var isBin = binaryUtils.isBinaryString(root.value) binaryFlag.visible = false @@ -36,25 +63,53 @@ ColumnLayout var formatter = formatterSelector.model[formatterSelector.currentIndex] - uiBlocker.visible = true + uiBlocker.visible = true + + formatter.instance.getFormatted(root.value, function (error, formatted, isReadOnly, format) { + + // TODO: process error + + if (format == "json") { + // 1 is JSON + return formatterSelector.model[1].instance.getFormatted(formatted, function (formattedJson, r, f) { - formatter.instance.getFormatted(root.value, function (formatted, isReadOnly, format) { + textArea.text = formattedJson + textArea.readOnly = isReadOnly + textArea.textFormat = TextEdit.PlainText + root.isEdited = false + uiBlocker.visible = false + }) + } else { + textArea.text = formatted + textArea.readOnly = isReadOnly + root.isEdited = false -// TODO: fixme -// if (format == "json") { -// // 1 is JSON -// return formatterSelector.model[1].instance.getFormatted(formatted, function (formattedJson, r, f) { -// webView.setText(formattedJson, false, isReadOnly) -// uiBlocker.visible = false -// }) -// } else { -// webView.setText(formatted, format === "html", isReadOnly) -// } + if (format === "html") + textArea.textFormat = TextEdit.RichText + else + textArea.textFormat = TextEdit.PlainText + } -// uiBlocker.visible = false + uiBlocker.visible = false }) } + function reset() { + textArea.text = "" + root.value = "" + root.isEdited = false + hideValidationError() + } + + function showValidationError(msg) { + validationError.text = msg + validationError.visible = true + } + + function hideValidationError() { + validationError.visible = false + } + RowLayout{ visible: showFormatters Layout.fillWidth: true @@ -79,33 +134,28 @@ ColumnLayout } } -// WebEngineView { -// id: webView -// Layout.fillWidth: true -// Layout.fillHeight: true -// Layout.preferredHeight: 100 - -// onJavaScriptConsoleMessage: { -// console.log("Web:", message) -// } - -// function setText(text, html, readOnly) { + TextArea { + id: textArea + Layout.fillWidth: true + Layout.fillHeight: true + Layout.preferredHeight: 100 -// if (html) { -// webView.loadHtml("" + text +"") -// } else { -// var attr = ""; + style: TextAreaStyle { + renderType: Text.QtRendering + } -// if (readOnly) { -// attr = "readonly"; -// } + font { family: monospacedFont.name; pointSize: 12 } + wrapMode: TextEdit.WrapAnywhere -// webView.loadHtml("") -// } + onTextChanged: root.isEdited = true + } -// } -// } + Text { + id: validationError + color: "red" + visible: false + } Rectangle { id: uiBlocker diff --git a/src/qml/value-editor/editors/SingleItemEditor.qml b/src/qml/value-editor/editors/SingleItemEditor.qml index 391f571b8..3213a8611 100644 --- a/src/qml/value-editor/editors/SingleItemEditor.qml +++ b/src/qml/value-editor/editors/SingleItemEditor.qml @@ -10,49 +10,39 @@ AbstractEditor { id: root anchors.fill: parent - property var originalValue: "" + property bool active: false MultilineEditor { id: textArea Layout.fillWidth: true Layout.fillHeight: true value: "" - enabled: originalValue !== "" || root.state !== "edit" + enabled: root.active || root.state !== "edit" showFormatters: root.state != "new" } - function setValue(rowValue) { - root.originalValue = rowValue['value'] - textArea.setValue(rowValue['value']) - } - - function isValueChanged() { - return originalValue != textArea.getText() + function validateValue(callback) { + return textArea.validate(callback); } - function resetAndDisableEditor() { - root.originalValue = "" - textArea.value = "" - } + function setValue(rowValue) { + if (!rowValue) + return - function getValue() { - return {"value": textArea.getText()} + active = true + textArea.loadFormattedValue(rowValue['value']) } - function isValueValid() { - var value = getValue() - - return value && value['value'] - && value['value'].length > 0 + function isEdited() { + return textArea.isEdited } - function markInvalidFields() { - textArea.textColor = "black" - // Fixme + function getValue() { + return {"value": textArea.value} } function reset() { - textArea.value = '' - console.log("reset") + textArea.reset() + active = false } } diff --git a/src/qml/value-editor/editors/SortedSetItemEditor.qml b/src/qml/value-editor/editors/SortedSetItemEditor.qml index f9a4c8a4f..8bb6dcf82 100644 --- a/src/qml/value-editor/editors/SortedSetItemEditor.qml +++ b/src/qml/value-editor/editors/SortedSetItemEditor.qml @@ -10,6 +10,8 @@ AbstractEditor { id: root anchors.fill: parent + property bool active: false + Text { Layout.fillWidth: true text: qsTr("Score:") @@ -22,10 +24,23 @@ AbstractEditor { Layout.minimumHeight: 28 text: "" - enabled: originalValue != "" || root.state !== "edit" - property var originalValue: "" + enabled: root.active || root.state !== "edit" + property string valueHash: "" placeholderText: qsTr("Score") validator: DoubleValidator { locale: "C"; notation: DoubleValidator.StandardNotation } // force point as decimal separator + + function setValue(v) { + valueHash = Qt.md5(v) + text = parseFloat(Number(v).toFixed(20)) + } + + function isEdited() { + return Qt.md5(text) != valueHash + } + + function reset() { + text = "" + } } MultilineEditor { @@ -33,50 +48,33 @@ AbstractEditor { Layout.fillWidth: true Layout.fillHeight: true value: "" - enabled: originalValue != "" || root.state !== "edit" - showFormatters: root.state != "new" - property var originalValue: "" + enabled: root.active || root.state !== "edit" + showFormatters: root.state != "new" } - function setValue(rowValue) { - scoreText.originalValue = rowValue['score'] - scoreText.text = parseFloat(Number(rowValue['score']).toFixed(20)) - textArea.originalValue = rowValue['value'] - textArea.setValue(rowValue['value']) + function validateValue(callback) { + return textArea.validate(callback); } - function isValueChanged() { - return textArea.originalValue != textArea.getText() - || scoreText.originalValue != scoreText.text + function setValue(rowValue) { + if (!rowValue) + return + + active = true + scoreText.setValue(rowValue['score']) + textArea.loadFormattedValue(rowValue['value']) } - function resetAndDisableEditor() { - textArea.originalValue = "" - textArea.value = "" - scoreText.originalValue = "" - scoreText.text = "" + function isEdited() { + return textArea.isEdited || scoreText.isEdited() } function getValue() { - return {"value": textArea.getText(), "score": scoreText.text} + return {"value": textArea.value, "score": scoreText.text} } - function isValueValid() { - var value = getValue() - - return value && value['score'] && value['value'] - && value['score'].length > 0 - && value['value'].length > 0 - } - - function markInvalidFields() { - scoreText.textColor = "black" - textArea.textColor = "black" - // Fixme - } - function reset() { - textArea.value = "" - scoreText.text = "" + scoreText.reset() + textArea.reset() } } diff --git a/src/qml/value-editor/editors/formatters/formatters.js b/src/qml/value-editor/editors/formatters/formatters.js index 4ed91a050..a900af357 100644 --- a/src/qml/value-editor/editors/formatters/formatters.js +++ b/src/qml/value-editor/editors/formatters/formatters.js @@ -13,15 +13,11 @@ var plain = { title: "Plain Text", getFormatted: function (raw, callback) { - return callback(raw, false, FORMAT_PLAIN_TEXT) - }, - - isValid: function (raw, callback) { - return callback(true, "") - }, + return callback("", raw, false, FORMAT_PLAIN_TEXT) + }, getRaw: function (formatted, callback) { - return callback(formatted) + return callback("", formatted) } } @@ -29,15 +25,18 @@ var hex = { title: "HEX", getFormatted: function (raw, callback) { - return callback(binaryUtils.printable(raw), false, FORMAT_PLAIN_TEXT) - }, - isValid: function (raw, callback) { - return callback(binaryUtils.isBinaryString(raw), "") - }, + var isValid = binaryUtils.isBinaryString(raw) + + if (isValid) { + return callback("", binaryUtils.printable(raw), false, FORMAT_PLAIN_TEXT) + } else { + return callback("Value is not binary string") + } + }, getRaw: function (formatted, callback) { - return callback(binaryUtils.printableToValue(formatted)) + return callback("", binaryUtils.printableToValue(formatted)) } } @@ -45,12 +44,15 @@ var hexTable = { title: "HEX TABLE", getFormatted: function (raw, callback) { - return callback(Hexy.hexy(binaryUtils.valueToBinary(raw), {'html': true}), true, FORMAT_HTML) - }, - isValid: function (raw, callback) { - return callback(binaryUtils.isBinaryString(raw), "") - }, + var isValid = binaryUtils.isBinaryString(raw) + + if (isValid) { + return callback("", Hexy.hexy(binaryUtils.valueToBinary(raw), {'html': true}), true, FORMAT_HTML) + } else { + return callback("Value is not binary string") + } + }, } /** @@ -62,26 +64,16 @@ var json = { getFormatted: function (raw, callback) { try { return callback(JSONFormatter.prettyPrint(raw), false, FORMAT_PLAIN_TEXT) - } catch (e) { return callback("Error: Invalid JSON") } - }, - - isValid: function (raw, callback) { - try { - JSON.parse(raw) - return callback(true) - } catch (e) { - return callback(false) - } - }, + }, getRaw: function (formatted, callback) { try { return callback(JSONFormatter.minify(formatted)) } catch (e) { - return callback(formatted) + return callback("Error: " + e) } } } @@ -107,10 +99,6 @@ function buildFormattersModel() return formattersManager.decode(formatterName, raw, callback) }, - isValid: function (raw, callback) { - return formattersManager.isValid(formatterName, raw, callback) - }, - getRaw: function (formatted, callback) { return formattersManager.encode(formatterName, formatted, callback) } @@ -129,6 +117,6 @@ function buildFormattersModel() } function guessFormatter(isBinary) -{ +{ return isBinary? 2 : 0 } From adef64053995b87458fb6121d97d2976f5f8580b Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 28 Apr 2017 17:01:24 +0300 Subject: [PATCH 31/32] Fix issue #3791: Model doesn't hide removed row on page > 1 --- src/modules/value-editor/valueviewmodel.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/value-editor/valueviewmodel.cpp b/src/modules/value-editor/valueviewmodel.cpp index 156ed3321..6b6dd1bbf 100644 --- a/src/modules/value-editor/valueviewmodel.cpp +++ b/src/modules/value-editor/valueviewmodel.cpp @@ -143,11 +143,11 @@ void ValueEditor::ValueViewModel::deleteRow(int i) try { m_model->removeRow(targetRow); - emit beginRemoveRows(QModelIndex(), targetRow, targetRow); + emit beginRemoveRows(QModelIndex(), i, i); emit endRemoveRows(); if (targetRow < m_model->rowsCount()) - emit dataChanged(index(targetRow, 0), index(m_model->rowsCount() - 1, 0)); + emit dataChanged(index(i, 0), index(m_model->rowsCount() - 1, 0)); } catch(const Model::Exception& e) { emit error(QString(e.what())); From 5e67a7d0c2433783b9ef55691a1eb2ed5bcdbe92 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 28 Apr 2017 17:20:08 +0300 Subject: [PATCH 32/32] Fix unit tests & regression --- src/app/models/treeoperations.cpp | 4 ++-- tests/qml_tests/tst_formatters.qml | 7 ++----- tests/unit_tests/testcases/value-editor/test_viewmodel.cpp | 7 ++----- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/app/models/treeoperations.cpp b/src/app/models/treeoperations.cpp index 8947ee51d..ec2239f4b 100644 --- a/src/app/models/treeoperations.cpp +++ b/src/app/models/treeoperations.cpp @@ -20,9 +20,9 @@ TreeOperations::TreeOperations(QSharedPointer connectio void TreeOperations::getDatabases(std::function callback) { - bool connected = false; + bool connected = m_connection->isConnected(); - if (!m_connection->isConnected()) { + if (!connected) { try { connected = m_connection->connect(true); } catch (const RedisClient::Connection::Exception& e) { diff --git a/tests/qml_tests/tst_formatters.qml b/tests/qml_tests/tst_formatters.qml index 660438938..c0f5783ca 100644 --- a/tests/qml_tests/tst_formatters.qml +++ b/tests/qml_tests/tst_formatters.qml @@ -14,14 +14,11 @@ TestCase { // checks verify(plain.title.length !== 0, "title") - plain.isValid(testValue, function (valid, err) { - compare(valid, true) - }) - plain.getFormatted(testValue, function (formatted, readOnly, format){ + plain.getFormatted(testValue, function (error, formatted, readOnly, format){ compare(formatted, testValue) }) - plain.getRaw(testValue, function (plain){ + plain.getRaw(testValue, function (error, plain){ compare(plain, testValue) }) } diff --git a/tests/unit_tests/testcases/value-editor/test_viewmodel.cpp b/tests/unit_tests/testcases/value-editor/test_viewmodel.cpp index decb442bf..357c6f157 100644 --- a/tests/unit_tests/testcases/value-editor/test_viewmodel.cpp +++ b/tests/unit_tests/testcases/value-editor/test_viewmodel.cpp @@ -64,13 +64,11 @@ void TestViewModel::testOpenTab() // When - Case 1 open first key, Welcome tab sould be closed - QSignalSpy spy(&model, SIGNAL(closeWelcomeTab())); model.openTab(connection.dynamicCast(), *key.data(), false); // Then - QCOMPARE(keyFactory->loadKeyCalled, 1u); - QCOMPARE(spy.count(), 1); + QCOMPARE(keyFactory->loadKeyCalled, 1u); QCOMPARE(model.rowCount(), 1); QVERIFY(model.getValue(0) != nullptr); @@ -80,8 +78,7 @@ void TestViewModel::testOpenTab() *key.data(), false); // Then - QCOMPARE(keyFactory->loadKeyCalled, 2u); - QCOMPARE(spy.count(), 1); + QCOMPARE(keyFactory->loadKeyCalled, 2u); QCOMPARE(spy2.count(), 1); QCOMPARE(model.rowCount(), 1); QVERIFY(model.getValue(0) != nullptr);