diff --git a/CMakeLists.txt b/CMakeLists.txt index 4756069..026b74f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,6 +92,7 @@ set(PROJECT_HEADERS src/view_models/nodelist.h src/view_models/nodemodel.h src/view_models/nodeformmodel.h + src/view_models/notificationmodel.h src/view_models/buildinfo.h.in src/view_models/systemtray.h src/view_models/imageprovider.h @@ -122,6 +123,7 @@ set(PROJECT_SOURCES src/view_models/nodelist.cpp src/view_models/nodemodel.cpp src/view_models/nodeformmodel.cpp + src/view_models/notificationmodel.cpp src/view_models/systemtray.cpp src/view_models/imageprovider.cpp src/view_models/jsonhighlighter.cpp diff --git a/i18n/across_zh_CN.ts b/i18n/across_zh_CN.ts index bb2e5a6..72237ab 100644 --- a/i18n/across_zh_CN.ts +++ b/i18n/across_zh_CN.ts @@ -614,7 +614,7 @@ 编辑 - + Copy URL 复制链接 @@ -623,32 +623,32 @@ ICMP Ping - + Copy Nodes 复制节点 - + TCP Ping TCP Ping - + Update 更新 - + [%1] Updating... [%1] 更新中... - + Updated: %1 更新于: %1 - + Delete 删除 @@ -1225,10 +1225,21 @@ across::GroupList - - - - + + [%1] TCP Pinging... + [%1] TCP Pinging... + + + + + Testing: %1/%2 + 測試中:%1/%2 + + + + + + Copy [%1] URL to clipboard 复制 [%1] 链接到剪贴板 @@ -1236,7 +1247,7 @@ across::NodeFormModel - + custom configuration encoding to url is not supported 未支持自定义配置编码到链接格式 @@ -1295,22 +1306,22 @@ across::setting::ConfigTools - + Click the application or tray icon to show 点击应用或托盘图标以显示 - + Start From Minimize 最小化启动 - + Failed to parse version 无法解析版本 - + New Version: v%1 新版本:v%1 @@ -1319,7 +1330,7 @@ 新版本:%1 - + Already the latest version 已经是最新版本 diff --git a/src/app.cpp b/src/app.cpp index 4157370..8565f75 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -39,6 +39,7 @@ bool Application::initialize() { p_nodes = QSharedPointer::create(); p_groups = QSharedPointer::create(); p_tray = QSharedPointer::create(); + p_notifications = QSharedPointer::create(); p_image_provider = new ImageProvider; // free by qml engine p_config->init(p_curl); @@ -93,6 +94,8 @@ void Application::setRootContext() { p_groups.get()); m_engine.rootContext()->setContextProperty(QStringLiteral("acrossTray"), p_tray.get()); + m_engine.rootContext()->setContextProperty(QStringLiteral("acrossNotifications"), + p_notifications.get()); m_engine.rootContext()->setContextProperty( QStringLiteral("fixedFont"), QFontDatabase::systemFont(QFontDatabase::FixedFont)); @@ -109,10 +112,10 @@ void Application::setRootContext() { p_tray->init(p_config, p_core, p_nodes); #if !defined(Q_CC_MINGW) && !defined(Q_OS_MACOS) p_nodes->init(p_config, p_core, p_db); - p_groups->init(p_config, p_db, p_nodes, p_curl); + p_groups->init(p_config, p_db, p_nodes, p_curl, p_notifications); #else p_nodes->init(p_config, p_core, p_db, p_tray->getTrayIcon()); - p_groups->init(p_config, p_db, p_nodes, p_curl, p_tray->getTrayIcon()); + p_groups->init(p_config, p_db, p_nodes, p_curl, p_notifications, p_tray->getTrayIcon()); #endif } @@ -143,6 +146,8 @@ void Application::registerModels() { "NodeModel"); qmlRegisterType(qml_model_name.c_str(), 1, 0, "NodeFormModel"); + qmlRegisterType(qml_model_name.c_str(), 1, 0, + "Notification"); } void Application::onMessageReceived(quint32 clientId, const QByteArray &msg) { diff --git a/src/app.h b/src/app.h index a271cb3..4461a3c 100644 --- a/src/app.h +++ b/src/app.h @@ -22,6 +22,7 @@ #include "view_models/configtools.h" #include "view_models/grouplist.h" #include "view_models/groupmodel.h" +#include "view_models/notificationmodel.h" #include "view_models/imageprovider.h" #include "view_models/logview.h" #include "view_models/nodeformmodel.h" @@ -73,6 +74,7 @@ class Application : public SingleApplication { QSharedPointer p_nodes; QSharedPointer p_groups; QSharedPointer p_tray; + QSharedPointer p_notifications; across::ImageProvider *p_image_provider; ACrossExitReason exitReason = EXIT_NORMAL; diff --git a/src/view_models/configtools.cpp b/src/view_models/configtools.cpp index ec7d5ba..29f0851 100644 --- a/src/view_models/configtools.cpp +++ b/src/view_models/configtools.cpp @@ -117,7 +117,8 @@ void ConfigTools::setInboundObject(v2ray::config::V2RayConfig &config) { http->set_timeout(http_setting.timeout()); http->set_userlevel(http_setting.user_level()); - if (const auto& auth_setting = http_setting.auth(); auth_setting.enable()) { + if (const auto &auth_setting = http_setting.auth(); + auth_setting.enable()) { auto auth = http->add_accounts(); auth->set_user(auth_setting.username()); auth->set_pass(auth_setting.password()); @@ -134,7 +135,8 @@ void ConfigTools::setInboundObject(v2ray::config::V2RayConfig &config) { auto socks = inbound->mutable_settings()->mutable_socks(); socks->set_userlevel(socks5_setting.user_level()); - if (const auto& auth_setting = socks5_setting.auth(); auth_setting.enable()) { + if (const auto &auth_setting = socks5_setting.auth(); + auth_setting.enable()) { auto auth = socks->add_accounts(); auth->set_user(auth_setting.username()); auth->set_pass(auth_setting.password()); @@ -306,7 +308,7 @@ void ConfigTools::freshInbound() { emit httpPasswordChanged(); } -void ConfigTools::saveConfig(const QString& config_path) { +void ConfigTools::saveConfig(const QString &config_path) { auto json_str = SerializeTools::MessageToJson(m_config); if (!config_path.isEmpty()) { @@ -822,7 +824,7 @@ void ConfigTools::setHttpPassword(const QString &val) { emit configChanged(); } -bool ConfigTools::isFileExist(const QString& file_path) { +bool ConfigTools::isFileExist(const QString &file_path) { return QFile(file_path).exists(); } diff --git a/src/view_models/grouplist.cpp b/src/view_models/grouplist.cpp index 2faa8e9..92a784e 100644 --- a/src/view_models/grouplist.cpp +++ b/src/view_models/grouplist.cpp @@ -14,7 +14,8 @@ void GroupList::init(QSharedPointer config, QSharedPointer db, QSharedPointer nodes, QSharedPointer curl, - const QSharedPointer& tray) { + QSharedPointer notifications, + const QSharedPointer &tray ) { if (auto app_logger = spdlog::get("app"); app_logger != nullptr) { p_logger = app_logger->clone("groups"); } else { @@ -26,6 +27,7 @@ void GroupList::init(QSharedPointer config, p_db = std::move(db); p_curl = std::move(curl); p_nodes = std::move(nodes); + p_notifications = std::move(notifications); if (tray != nullptr) { p_tray = tray; @@ -37,6 +39,9 @@ void GroupList::init(QSharedPointer config, connect(p_curl.get(), &across::network::CURLTools::downloadFinished, this, &GroupList::handleDownloaded); + connect(this, &GroupList::nodeLatencyChanged, this, + &GroupList::handleNodeLatencyChanged); + reloadItems(); checkAllUpdate(); } @@ -98,18 +103,57 @@ void GroupList::checkUpdate(int index, bool force) { } while (false); } -Q_INVOKABLE void GroupList::testTcpPing(int index) { +Q_INVOKABLE int GroupList::testTcpPing(int index) { if (index >= m_groups.size()) - return; + return 1; if (auto &group = m_groups[index]; group.items != 0) { + if (m_is_tcpPinging.contains(group.id)) { + return 1; + } + m_is_tcpPinging[group.id] = true; + auto nodes = p_db->listAllNodesFromGroupID(group.id); + m_tcpPinging_count[group.id] = 0; + m_group_size[group.id] = nodes.size(); + m_tcpPinging_notifications[group.id] = p_notifications->append( + tr("[%1] TCP Pinging...").arg(group.name), + tr("Testing: %1/%2").arg("0").arg(QString::number(m_group_size[group.id])), + 0.0, + m_group_size[group.id], + 0.0 + ); + for (int i = 0; i < nodes.size(); ++i) { auto &node = nodes[i]; - p_nodes->testLatency(node, i); + p_nodes->testLatency(node, i, [this, node, i] { + emit nodeLatencyChanged(node.group_id, i, node); + }); } } + return 0; +} +Q_INVOKABLE int GroupList::testTcpPingLeft(int index) { + auto &group = m_groups[index]; + if (m_tcpPinging_count.contains(group.id)) { + return -m_tcpPinging_count[group.id]; + } + return 0; +} +void GroupList::handleNodeLatencyChanged(qint64 group_id, int index, + const across::NodeInfo &node) { + m_tcpPinging_count[group_id]++; + m_tcpPinging_notifications[group_id]->setMessage(tr("Testing: %1/%2") + .arg(QString::number(m_tcpPinging_count[group_id])) + .arg(QString::number(m_group_size[group_id]))); + m_tcpPinging_notifications[group_id]->setValue(m_tcpPinging_count[group_id]); + if (m_tcpPinging_count[group_id] >= m_group_size[group_id]) { + p_notifications->remove(m_tcpPinging_notifications[group_id]->getIndex()); + m_tcpPinging_count.remove(group_id); + m_tcpPinging_notifications.remove(group_id); + m_is_tcpPinging.remove(group_id); + } } Q_INVOKABLE int GroupList::getIndexByID(int id) { @@ -227,7 +271,7 @@ bool GroupList::insertSIP008(const GroupInfo &group_info, auto url = SerializeTools::sip002Encode(meta).value(); auto outbound = meta.outbound; auto shadowsocks = outbound.settings().shadowsocks(); - const auto& server = shadowsocks.servers(0); + const auto &server = shadowsocks.servers(0); std::string json_str; @@ -438,8 +482,9 @@ void GroupList::copyNodesToClipboard(int index) { void GroupList::handleDownloaded(const QVariant &content) { auto task = content.value(); - if (task.content.isEmpty()){ - if(task.is_updated) m_is_updating.remove(task.id); + if (task.content.isEmpty()) { + if (task.is_updated) + m_is_updating.remove(task.id); return; } diff --git a/src/view_models/grouplist.h b/src/view_models/grouplist.h index 81c21e9..f8dc88f 100644 --- a/src/view_models/grouplist.h +++ b/src/view_models/grouplist.h @@ -21,6 +21,7 @@ #include "configtools.h" #include "logtools.h" #include "nodelist.h" +#include "notificationmodel.h" namespace across { @@ -33,7 +34,8 @@ class GroupList : public QObject { QSharedPointer db, QSharedPointer nodes, QSharedPointer curl, - const QSharedPointer& tray = nullptr); + QSharedPointer notifications, + const QSharedPointer &tray = nullptr); bool insert(const GroupInfo &group_info, const QString &content); bool insertSIP008(const GroupInfo &group_info, const QString &content); @@ -44,7 +46,8 @@ class GroupList : public QObject { Q_INVOKABLE void checkAllUpdate(bool force = false); Q_INVOKABLE void checkUpdate(int index, bool force = true); - Q_INVOKABLE void testTcpPing(int index); + Q_INVOKABLE int testTcpPing(int index); + Q_INVOKABLE int testTcpPingLeft(int index); Q_INVOKABLE int getIndexByID(int id); Q_INVOKABLE void search(const QString &value); @@ -65,18 +68,23 @@ class GroupList : public QObject { void copyNodesToClipboard(int index); void handleDownloaded(const QVariant &content); void handleItemsChanged(int64_t group_id, int size); + void handleNodeLatencyChanged(qint64 group_id, int index, + const across::NodeInfo &node); signals: void preItemsReset(); void postItemsReset(); void itemInfoChanged(int index); + void nodeLatencyChanged(qint64 group_id, int index, + const across::NodeInfo &node); private: QSharedPointer p_config; QSharedPointer p_db; QSharedPointer p_nodes; QSharedPointer p_curl; + QSharedPointer p_notifications; QSharedPointer p_tray; std::shared_ptr p_logger; @@ -85,6 +93,10 @@ class GroupList : public QObject { QList m_origin_groups; QList m_pre_groups; QMap m_is_updating; + QMap m_is_tcpPinging; + QMap m_tcpPinging_count; + QMap m_group_size; + QMap m_tcpPinging_notifications; }; } // namespace across diff --git a/src/view_models/imageprovider.h b/src/view_models/imageprovider.h index 98ecc84..baaac08 100644 --- a/src/view_models/imageprovider.h +++ b/src/view_models/imageprovider.h @@ -9,7 +9,8 @@ namespace across { class ImageProvider : public QQuickImageProvider { public: - explicit ImageProvider(ImageType type = ImageType::Image, Flags flags = Flags()); + explicit ImageProvider(ImageType type = ImageType::Image, + Flags flags = Flags()); public slots: void setContent(const QString &id, const QString &content); diff --git a/src/view_models/jsonhighlighter.cpp b/src/view_models/jsonhighlighter.cpp index 7ea400d..2c39112 100644 --- a/src/view_models/jsonhighlighter.cpp +++ b/src/view_models/jsonhighlighter.cpp @@ -38,7 +38,7 @@ void JSONHighlighter::init() { } void JSONHighlighter::setTheme(config::Theme *p_theme) { - const auto& colors = p_theme->colors(); + const auto &colors = p_theme->colors(); val_format.setForeground(QColor(colors.text_color().c_str())); string_format.setForeground(QColor(colors.text_color().c_str())); diff --git a/src/view_models/loghighlighter.cpp b/src/view_models/loghighlighter.cpp index 4a406ce..b30eda3 100644 --- a/src/view_models/loghighlighter.cpp +++ b/src/view_models/loghighlighter.cpp @@ -84,7 +84,7 @@ void LogHighlighter::init() { } void LogHighlighter::setTheme(const config::Theme &theme) { - const auto& colors = theme.colors(); + const auto &colors = theme.colors(); info_format->setForeground(QColor(colors.style_color().c_str())); warning_format->setForeground(QColor(colors.warn_color().c_str())); diff --git a/src/view_models/logtools.h b/src/view_models/logtools.h index 471be8d..940a8ae 100644 --- a/src/view_models/logtools.h +++ b/src/view_models/logtools.h @@ -14,8 +14,9 @@ enum LoggerEnum { core, app }; class LogTools : public LogView { public: - explicit LogTools(QSharedPointer log_view, const QString &name = "", - LoggerEnum log_enum = LoggerEnum::app); + explicit LogTools(QSharedPointer log_view, + const QString &name = "", + LoggerEnum log_enum = LoggerEnum::app); template inline void trace(fmt::format_string fmt, Args &&...args) { @@ -50,5 +51,5 @@ class LogTools : public LogView { private: std::shared_ptr p_logger; }; -} // namespace across +} // namespace across::utils #endif // LOGTOOLS_H diff --git a/src/view_models/nodeformmodel.cpp b/src/view_models/nodeformmodel.cpp index 3a5374d..b479d52 100644 --- a/src/view_models/nodeformmodel.cpp +++ b/src/view_models/nodeformmodel.cpp @@ -49,7 +49,6 @@ void NodeFormModel::accept(const QVariantMap &values) { } else { p_list->updateNode(node); } - } QString NodeFormModel::refreshPreview(const QVariantMap &values) { diff --git a/src/view_models/nodelist.cpp b/src/view_models/nodelist.cpp index e04270b..fbe93b2 100644 --- a/src/view_models/nodelist.cpp +++ b/src/view_models/nodelist.cpp @@ -18,7 +18,7 @@ NodeList::~NodeList() { void NodeList::init(QSharedPointer config, QSharedPointer core, QSharedPointer db, - const QSharedPointer& tray) { + const QSharedPointer &tray) { if (auto app_logger = spdlog::get("app"); app_logger != nullptr) { p_logger = app_logger->clone("nodes"); } else { @@ -396,7 +396,8 @@ void NodeList::setCurrentNodeByID(int id) { } } -void NodeList::handleLatencyChanged(qint64 group_id, int index, const NodeInfo& node) { +void NodeList::handleLatencyChanged(qint64 group_id, int index, + const NodeInfo &node) { auto db_future = QtConcurrent::run([&, node] { auto t_node = node; p_db->update(t_node); @@ -468,15 +469,19 @@ Q_INVOKABLE void NodeList::testLatency(int id) { } } -void NodeList::testLatency(const NodeInfo& node, int index) { - m_tasks.enqueue(QtConcurrent::run([this, index, node] { - auto current_node = node; - TCPPing ping; - ping.setAddr(node.address); - ping.setPort(node.port); - current_node.latency = ping.getAvgLatency(); - emit itemLatencyChanged(node.group_id, index, current_node); - })); +void NodeList::testLatency(const NodeInfo &node, int index, + std::function after) { + m_tasks.enqueue( + QtConcurrent::run([this, index, node, after{std::move(after)}] { + auto current_node = node; + TCPPing ping; + ping.setAddr(node.address); + ping.setPort(node.port); + current_node.latency = ping.getAvgLatency(); + + after(); + emit itemLatencyChanged(node.group_id, index, current_node); + })); } bool NodeList::isRunning() { diff --git a/src/view_models/nodelist.h b/src/view_models/nodelist.h index 583fb3f..cfa90f4 100644 --- a/src/view_models/nodelist.h +++ b/src/view_models/nodelist.h @@ -22,6 +22,7 @@ #include #include #include +#include namespace across { using Json = nlohmann::json; @@ -48,7 +49,7 @@ class NodeList : public QObject { void init(QSharedPointer config, QSharedPointer core, QSharedPointer db, - const QSharedPointer& tray = nullptr); + const QSharedPointer &tray = nullptr); bool run(); @@ -66,7 +67,8 @@ class NodeList : public QObject { void setUploadTraffic(double newUploadTraffic); void setDownloadTraffic(double newDownloadTraffic); - void testLatency(const NodeInfo& node, int index); + void testLatency( + const NodeInfo &node, int index, std::function after = [] {}); void setDownloadProxy(across::network::DownloadTask &task); @@ -99,7 +101,8 @@ class NodeList : public QObject { public slots: void setDisplayGroupID(int group_id); - void handleLatencyChanged(qint64 group_id, int index, const across::NodeInfo& node); + void handleLatencyChanged(qint64 group_id, int index, + const across::NodeInfo &node); signals: void itemReset(int index); diff --git a/src/view_models/nodemodel.h b/src/view_models/nodemodel.h index 71ebcf4..7d07579 100644 --- a/src/view_models/nodemodel.h +++ b/src/view_models/nodemodel.h @@ -35,10 +35,11 @@ class NodeModel : public QAbstractListModel { ModifiedAtRole, }; - [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override; + [[nodiscard]] int + rowCount(const QModelIndex &parent = QModelIndex()) const override; [[nodiscard]] QVariant data(const QModelIndex &index, - int role = Qt::DisplayRole) const override; + int role = Qt::DisplayRole) const override; [[nodiscard]] QHash roleNames() const override; diff --git a/src/view_models/notificationmodel.cpp b/src/view_models/notificationmodel.cpp new file mode 100644 index 0000000..d2fbba9 --- /dev/null +++ b/src/view_models/notificationmodel.cpp @@ -0,0 +1,126 @@ +#include "notificationmodel.h" + +using namespace across; + +Notification::Notification(int index, QObject *parent) : QObject(parent) { + this->index = index; + + connect(this, &Notification::titleChanged, + &Notification::propertiesChanged); + connect(this, &Notification::messageChanged, + &Notification::propertiesChanged); + connect(this, &Notification::fromChanged, &Notification::propertiesChanged); + connect(this, &Notification::toChanged, &Notification::propertiesChanged); + connect(this, &Notification::valueChanged, + &Notification::propertiesChanged); + connect(this, &Notification::pinChanged, &Notification::propertiesChanged); + connect(this, &Notification::indexChanged, &Notification::propertiesChanged); +} + +NotificationModel::NotificationModel(QObject *parent) + : QAbstractListModel(parent) {} + +QVariant NotificationModel::headerData(int section, Qt::Orientation orientation, + int role) const { + if (section >= rowCount()) + return {}; + + if (role == NotificationRoles::TitleRole) + return notifications.at(section)->getTitle(); + + return {}; +} + +int NotificationModel::rowCount(const QModelIndex &parent) const { + if (parent.isValid()) + return 0; + else + return notifications.size(); +} + +QVariant NotificationModel::data(const QModelIndex &index, int role) const { + if (!index.isValid() || index.row() >= rowCount()) + return QVariant(); + + const auto item = notifications.at(index.row()); + + switch (role) { + case NotificationIndexRole: + return static_cast(item->getIndex()); + case TitleRole: + return item->getTitle(); + case MessageRole: + return item->getMessage(); + case FromRole: + return item->getFrom(); + case ToRole: + return item->getTo(); + case ValueRole: + return item->getValue(); + default: + return {}; + } +} + +QHash NotificationModel::roleNames() const { + static const QHash roles{ + {NotificationIndexRole, "id"}, + {TitleRole, "title"}, + {MessageRole, "message"}, + {FromRole, "from"}, + {ToRole, "to"}, + {ValueRole, "value"}, + }; + + return roles; +} + +Qt::ItemFlags NotificationModel::flags(const QModelIndex &index) const { + if (!index.isValid()) + return Qt::NoItemFlags; + + return Qt::ItemIsEnabled; +} + +across::Notification * +NotificationModel::append(const QString &title, const QString &message = "", + qreal from = 0.0, qreal to = 1.0, qreal value = 0.0) { + int index = notifications.size(); + Notification *noti = new Notification(index, this); + + noti->setTitle(title); + noti->setMessage(message); + noti->setFrom(from); + noti->setTo(to); + noti->setValue(value); + + auto rcnt = rowCount(); + beginInsertRows(QModelIndex(), rcnt, rcnt); + connect(noti, &Notification::propertiesChanged, this, [this,noti]() { + QModelIndex topLeft = createIndex(noti->getIndex(), 0); + QModelIndex bottomRight = createIndex(noti->getIndex(), 0); + emit NotificationModel::dataChanged(topLeft, bottomRight); + }); + notifications.emplace_back(noti); + endInsertRows(); + return noti; +} + +void NotificationModel::remove(const int index) { + if (index >= notifications.size() || index < 0) + return; + + auto rcnt = index; + beginRemoveRows(QModelIndex(), rcnt, rcnt); + + QModelIndex topLeft = createIndex(rcnt, 0); + QModelIndex bottomRight = createIndex(rcnt, 0); + emit NotificationModel::dataChanged(topLeft, bottomRight); + Notification *noti = notifications[index]; + for(int i=index+1;isetIndex(notifications[i]->getIndex()-1); + } + notifications.remove(index); + noti->deleteLater(); + endRemoveRows(); +} \ No newline at end of file diff --git a/src/view_models/notificationmodel.h b/src/view_models/notificationmodel.h new file mode 100644 index 0000000..b548863 --- /dev/null +++ b/src/view_models/notificationmodel.h @@ -0,0 +1,118 @@ +#ifndef NOTIFICATIONMODEL_H +#define NOTIFICATIONMODEL_H + +#include +#include +#include +#include + +namespace across { + +class Notification : public QObject { + Q_OBJECT + Q_PROPERTY(QString title READ getTitle WRITE setTitle BINDABLE bindableTitle + NOTIFY titleChanged); + Q_PROPERTY(QString message READ getMessage WRITE setMessage BINDABLE + bindableMessage NOTIFY messageChanged); + Q_PROPERTY(qreal from READ getFrom WRITE setFrom BINDABLE bindableFrom + NOTIFY fromChanged); + Q_PROPERTY( + qreal to READ getTo WRITE setTo BINDABLE bindableTo NOTIFY toChanged); + Q_PROPERTY(qreal value READ getValue WRITE setValue BINDABLE bindableValue + NOTIFY valueChanged); + Q_PROPERTY(bool pin READ getPin WRITE setPin NOTIFY pinChanged); + Q_PROPERTY(int index READ getIndex WRITE setIndex NOTIFY indexChanged); + + public: + explicit Notification(int id, QObject *parent = nullptr); + + QString getTitle() const { return title.value(); } + void setTitle(const QString &title) { this->title = title; }; + QBindable bindableTitle() { return &title; } + QString getMessage() const { return message.value(); } + void setMessage(const QString &message) { this->message = message; } + QBindable bindableMessage() { return &message; } + qreal getFrom() const { return from.value(); } + void setFrom(const qreal &from) { this->from = from; } + QBindable bindableFrom() { return &from; } + qreal getTo() const { return to.value(); } + void setTo(const qreal &to) { this->to = to; } + QBindable bindableTo() { return &to; } + qreal getValue() const { return value.value(); } + void setValue(const qreal &value) { this->value = value; } + QBindable bindableValue() { return &value; } + bool getPin() const { return pin; } + void setPin(const bool pin) { this->pin = pin; } + int getIndex() const { return index; } + void setIndex(int index) { this->index = index;} + + signals: + void titleChanged(); + void messageChanged(); + void fromChanged(); + void toChanged(); + void valueChanged(); + void pinChanged(); + void indexChanged(); + void propertiesChanged(); + + private: + Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(Notification, QString, title, "", + &Notification::titleChanged); + Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(Notification, QString, message, "", + &Notification::messageChanged); + Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(Notification, qreal, from, 0.0, + &Notification::fromChanged); + Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(Notification, qreal, to, 1.0, + &Notification::toChanged); + Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(Notification, qreal, value, 0.0, + &Notification::valueChanged); + + bool pin; + int index = 0; +}; + +class NotificationModel : public QAbstractListModel { + Q_OBJECT + + public: + explicit NotificationModel(QObject *parent = nullptr); + + enum NotificationRoles { + NotificationIndexRole = Qt::UserRole, + TitleRole, + MessageRole, + FromRole, + ToRole, + ValueRole, + }; + + Q_ENUM(NotificationRoles); + + // Header: + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const override; + + // Basic functionality: + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + QVariant data(const QModelIndex &index, + int role = Qt::DisplayRole) const override; + + virtual QHash roleNames() const override; + + Qt::ItemFlags flags(const QModelIndex &index) const override; + + Q_INVOKABLE across::Notification *append(const QString &title, + const QString &message, qreal from, + qreal to, qreal value); + + Q_INVOKABLE void remove(const int id); + + private: + QVector notifications; +}; + +} // namespace across + +#endif // NOTIFICATIONMODEL_H diff --git a/src/views/home/CurrentPanel.qml b/src/views/home/CurrentPanel.qml index d37f1f9..3407145 100644 --- a/src/views/home/CurrentPanel.qml +++ b/src/views/home/CurrentPanel.qml @@ -253,7 +253,7 @@ Item { acrossCore.stop(); } else { if (acrossCore.run() < 0) - popNotify.notify(qsTr("Core Error"), qsTr("Failed to start the process")); + popNotify.notify(qsTr("Core Error"), qsTr("Failed to start the process"), 0.0, 1.0, 0.0, 2000); } } diff --git a/src/views/home/GroupItemPopMenu.qml b/src/views/home/GroupItemPopMenu.qml index 02e4763..7c2fbec 100644 --- a/src/views/home/GroupItemPopMenu.qml +++ b/src/views/home/GroupItemPopMenu.qml @@ -17,33 +17,45 @@ Menu { Action { text: qsTr("Edit") onTriggered: { - darkBackground.show(); - var groupInfo = acrossGroups.getGroupInfo(index); - groupEditForm.groupInfo = groupInfo; - groupEditForm.index = index; - groupEditForm.open(); + darkBackground.show() + var groupInfo = acrossGroups.getGroupInfo(index) + + groupEditForm.groupInfo = groupInfo + groupEditForm.index = index + groupEditForm.open() } } Action { enabled: isSubscription + text: qsTr("Copy URL") + onTriggered: { - acrossGroups.copyUrlToClipboard(index); + acrossGroups.copyUrlToClipboard(index) } } Action { text: qsTr("Copy Nodes") + onTriggered: { - acrossGroups.copyNodesToClipboard(index); + acrossGroups.copyNodesToClipboard(index) } } Action { text: qsTr("TCP Ping") onTriggered: { - acrossGroups.testTcpPing(index); + /* + if(acrossGroups.testTcpPing(index) === 0){ + popNotify.notify(qsTr("[%1] TCP Pinging...").arg(name), + qsTr("Testing:"), + index, + items) + } + */ + acrossGroups.testTcpPing(index); } } @@ -54,25 +66,31 @@ Menu { height: 1 color: acrossConfig.deepColor } - } Action { enabled: isSubscription + text: qsTr("Update") onTriggered: { - acrossGroups.checkUpdate(index); - popNotify.notify(qsTr("[%1] Updating...").arg(name), qsTr("Updated: %1").arg(modifiedAt)); + acrossGroups.checkUpdate(index) + popNotify.notify(qsTr("[%1] Updating...").arg(name), + qsTr("Updated: %1").arg(modifiedAt), + 0.0, + 1.0, + 0.0, + 2000) } } Action { text: qsTr("Delete") enabled: 0 === model.index ? false : true + onTriggered: { - darkBackground.show(); - removeConfirmDialog.index = index; - removeConfirmDialog.open(); + darkBackground.show() + removeConfirmDialog.index = index + removeConfirmDialog.open() } } @@ -106,5 +124,4 @@ Menu { } } - } diff --git a/src/views/home/GroupListPanel.qml b/src/views/home/GroupListPanel.qml index 93457ee..683a4a0 100644 --- a/src/views/home/GroupListPanel.qml +++ b/src/views/home/GroupListPanel.qml @@ -84,7 +84,7 @@ Item { updateIcon.visible = false; } if (updateToken && this.contentY === 0) { - popNotify.notify(qsTr("Updating..."), qsTr("The default node will be reset")); + popNotify.notify(qsTr("Updating..."), qsTr("The default node will be reset"), 0.0, 1.0, 0.0, 2000); acrossGroups.checkAllUpdate(true); // force update updateToken = false; } diff --git a/src/views/home/NodeItemCard.qml b/src/views/home/NodeItemCard.qml index b6c07c2..037b47e 100644 --- a/src/views/home/NodeItemCard.qml +++ b/src/views/home/NodeItemCard.qml @@ -136,7 +136,7 @@ Item { if (mouse.button === Qt.LeftButton) { acrossNodes.setCurrentNodeByID(nodeID); if (!acrossCore.isRunning) - popNotify.notify(qsTr("Core Error"), qsTr("Failed to start the process")); + popNotify.notify(qsTr("Core Error"), qsTr("Failed to start the process"), 0.0, 1.0, 0.0, 2000); } } diff --git a/src/views/main/PopMessageBox.qml b/src/views/main/PopMessageBox.qml index 8116534..0b1f205 100644 --- a/src/views/main/PopMessageBox.qml +++ b/src/views/main/PopMessageBox.qml @@ -7,9 +7,11 @@ CardBox { id: control property int fontSize: 12 - property string title: "" - property string message: "" - property int intervalTime: 5000 + property alias title: displayTitle.text + property alias message: displayMessage.text + property alias from: progressBar.from + property alias to: progressBar.to + property alias value: progressBar.value implicitWidth: 320 implicitHeight: 84 @@ -23,7 +25,6 @@ CardBox { id: displayTitle Layout.fillWidth: true - text: control.title color: acrossConfig.textColor font.pointSize: fontSize } @@ -31,8 +32,6 @@ CardBox { Label { id: displayMessage - Layout.fillWidth: true - text: control.message color: acrossConfig.textColor } @@ -42,7 +41,6 @@ CardBox { Layout.fillWidth: true Layout.preferredHeight: 4 padding: 2 - value: 0 background: Rectangle { implicitWidth: 240 @@ -64,27 +62,8 @@ CardBox { } - NumberAnimation on value { - id: progressBarAnimation - - from: 0 - to: 1 - duration: control.intervalTime - } - } } - Timer { - repeat: true - running: true - interval: intervalTime - onTriggered: { - if (popNotifyModel.hasChildren()) - popNotifyModel.remove(0); - - } - } - } diff --git a/src/views/main/PopNotify.qml b/src/views/main/PopNotify.qml index 43fea20..91fcb11 100644 --- a/src/views/main/PopNotify.qml +++ b/src/views/main/PopNotify.qml @@ -6,14 +6,6 @@ import QtQuick.Layouts Item { id: popNotifyControl - function notify(title = "", message = "") { - popNotifyControl.visible = true; - popNotifyModel.append({ - "title": title, - "message": message - }); - } - implicitWidth: 320 implicitHeight: popNotifyListView.count >= 3 ? 84 * 3 : 84 * popNotifyListView.count clip: true @@ -28,17 +20,22 @@ Item { anchors.fill: parent verticalLayoutDirection: ListView.BottomToTop - model: popNotifyModel + model: acrossNotifications move: add + onCountChanged: { if (count === 0) popNotifyControl.visible = false; - + else + popNotifyControl.visible = true; } delegate: PopMessageBox { title: model.title message: model.message + from: model.from + value: model.value + to: model.to } add: Transition { @@ -50,5 +47,49 @@ Item { } } + function numAnimation(parent) { + return Qt.createQmlObject("import QtQuick; NumberAnimation {}", parent); + } + + function notify(title = "", message = "", from = 0.0, to = 1.0, value = 0.0, duration = -1) { + let notification = acrossNotifications.append(title,message,0.0,1.0,0.0); + if(duration != -1) { + let animation = new numAnimation(notification); + animation.from = from; + animation.to = to; + animation.duration = duration; + animation.target = notification; + animation.property = "value"; + animation.start(); + + function removeNotification() { + acrossNotifications.remove(notification.index); + } + + animation.finished.connect(removeNotification); + } + + return notification; +/* + // Debug + var noti1 = acrossNotifications.append(title,message,0.0,1.0,0.0); + + function Timer() { + return Qt.createQmlObject("import QtQuick; Timer {}", popNotifyControl); + } + + var timer = new Timer(); + timer.interval = 3000; + timer.repeat = false; + noti1.title = noti1.id + timer.triggered.connect(function () { + console.log("remove",noti1.id); + noti1.value = 1.0 + noti1.title = "Done" + acrossNotifications.remove(noti1.id) + }) + timer.start(); + */ + } } diff --git a/src/views/setting/ApplicationItem.qml b/src/views/setting/ApplicationItem.qml index 0a759ee..1711485 100644 --- a/src/views/setting/ApplicationItem.qml +++ b/src/views/setting/ApplicationItem.qml @@ -98,7 +98,7 @@ Item { onCheckedChanged: { acrossConfig.enableAutoConnect = checked; if (checked) - popNotify.notify(qsTr("Auto Connect"), qsTr("Set as Default > Last Connected")); + popNotify.notify(qsTr("Auto Connect"), qsTr("Set as Default > Last Connected"), 0.0, 1.0, 0.0, 2000); } } @@ -117,7 +117,7 @@ Item { onCheckedChanged: { acrossConfig.enableAutoExport = checked; if (checked) - popNotify.notify(qsTr("Auto Export"), acrossConfig.dataDir); + popNotify.notify(qsTr("Auto Export"), acrossConfig.dataDir, 0.0, 1.0, 0.0, 2000); } }