diff --git a/assets/icons/ui_dark/design/sort.svg b/assets/icons/ui_dark/design/sort.svg new file mode 100644 index 00000000..689c8367 --- /dev/null +++ b/assets/icons/ui_dark/design/sort.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/ui_dark/sort.png b/assets/icons/ui_dark/sort.png new file mode 100644 index 00000000..046e1c7e Binary files /dev/null and b/assets/icons/ui_dark/sort.png differ diff --git a/assets/icons/ui_light/design/sort.svg b/assets/icons/ui_light/design/sort.svg new file mode 100644 index 00000000..f189441b --- /dev/null +++ b/assets/icons/ui_light/design/sort.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/ui_light/sort.png b/assets/icons/ui_light/sort.png new file mode 100644 index 00000000..89e6121b Binary files /dev/null and b/assets/icons/ui_light/sort.png differ diff --git a/makespec/BUILDVERSION b/makespec/BUILDVERSION index 9a910c68..e6a49d5d 100644 --- a/makespec/BUILDVERSION +++ b/makespec/BUILDVERSION @@ -1 +1 @@ -4349 +4385 diff --git a/resources.qrc b/resources.qrc index 84a3e4e6..d3598f3a 100644 --- a/resources.qrc +++ b/resources.qrc @@ -30,5 +30,7 @@ assets/icons/ui_light/stop.png assets/icons/ui_dark/locate.png assets/icons/ui_light/locate.png + assets/icons/ui_dark/sort.png + assets/icons/ui_light/sort.png diff --git a/src/core/handler/ConnectionHandler.cpp b/src/core/handler/ConnectionHandler.cpp index a3ea4bdb..d23e0ae8 100644 --- a/src/core/handler/ConnectionHandler.cpp +++ b/src/core/handler/ConnectionHandler.cpp @@ -227,14 +227,24 @@ namespace Qv2ray::core::handlers return connections[id].groupId; } - double QvConnectionHandler::GetConnectionLatency(const ConnectionId &id) const + uint64_t QvConnectionHandler::GetConnectionTotalData(const ConnectionId &id) const { if (!connections.contains(id)) { LOG(MODULE_CORE_HANDLER, "Cannot find id: " + id.toString()); } - return connections[id].latency; + return connections[id].upLinkData + connections[id].downLinkData; + } + + int64_t QvConnectionHandler::GetConnectionLatency(const ConnectionId &id) const + { + if (!connections.contains(id)) + { + LOG(MODULE_CORE_HANDLER, "Cannot find id: " + id.toString()); + } + + return max(connections[id].latency, 0L); } const optional QvConnectionHandler::RenameConnection(const ConnectionId &id, const QString &newName) { diff --git a/src/core/handler/ConnectionHandler.hpp b/src/core/handler/ConnectionHandler.hpp index b2aad268..b431fe86 100644 --- a/src/core/handler/ConnectionHandler.hpp +++ b/src/core/handler/ConnectionHandler.hpp @@ -64,7 +64,8 @@ namespace Qv2ray::core::handlers const QString GetConnectionProtocolString(const ConnectionId &id) const; const CONFIGROOT GetConnectionRoot(const ConnectionId &id) const; const CONFIGROOT GetConnectionRoot(const GroupId &group, const ConnectionId &id) const; - double GetConnectionLatency(const ConnectionId &id) const; + int64_t GetConnectionLatency(const ConnectionId &id) const; + uint64_t GetConnectionTotalData(const ConnectionId &id) const; const tuple GetConnectionUsageAmount(const ConnectionId &id) const; // // Misc Connection Operations diff --git a/src/ui/w_MainWindow.cpp b/src/ui/w_MainWindow.cpp index eb9ca4f1..dfa85f96 100644 --- a/src/ui/w_MainWindow.cpp +++ b/src/ui/w_MainWindow.cpp @@ -26,15 +26,14 @@ #include #include -// MainWindow.cpp --> Main MainWindow source file, handles mostly UI-related -// operations. - #define TRAY_TOOLTIP_PREFIX "Qv2ray " QV2RAY_VERSION_STRING #define CheckCurrentWidget \ auto widget = GetItemWidget(connectionListWidget->currentItem()); \ if (widget == nullptr) \ return; + #define GetItemWidget(item) (qobject_cast(connectionListWidget->itemWidget(item, 0))) +#define NumericString(i) (QString("%1").arg(i, 15, 10, QLatin1Char('0'))) MainWindow *MainWindow::mwInstance = nullptr; @@ -53,7 +52,14 @@ void MainWindow::MWAddConnectionItem_p(const ConnectionId &connection, const Gro MWAddGroupItem_p(groupId); } auto groupItem = groupNodes[groupId]; - auto connectionItem = make_shared(QStringList{ "", ConnectionManager->GetDisplayName(connection) }); + auto connectionItem = make_shared(QStringList{ + "", // + ConnectionManager->GetDisplayName(connection), // + NumericString(ConnectionManager->GetConnectionLatency(connection)), // + "IMPORTTIME_NOT_SUPPORTED", // + "LAST_CONNECTED_NOT_SUPPORTED", // + NumericString(ConnectionManager->GetConnectionTotalData(connection)) // + }); connectionNodes[connection] = connectionItem; groupItem->addChild(connectionItem.get()); auto widget = new ConnectionItemWidget(connection, connectionListWidget); @@ -69,6 +75,16 @@ void MainWindow::MWAddGroupItem_p(const GroupId &groupId) connectionListWidget->setItemWidget(groupItem.get(), 0, new ConnectionItemWidget(groupId, connectionListWidget)); } +void MainWindow::SortConnectionList(MW_ITEM_COL byCol, bool asending) +{ + + connectionListWidget->sortByColumn(MW_ITEM_COL_DISPLAYNAME, Qt::AscendingOrder); + for (auto i = 0; i < connectionListWidget->topLevelItemCount(); i++) + { + connectionListWidget->topLevelItem(i)->sortChildren(byCol, asending ? Qt::AscendingOrder : Qt::DescendingOrder); + } +} + MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) //, vinstance(), hTray(this), tcpingHelper(3, this) { setupUi(this); @@ -82,11 +98,11 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) //, vinstance(), h // For charts speedChartWidget = new SpeedWidget(this); speedChart->addWidget(speedChartWidget); + // this->setWindowIcon(QIcon(":/assets/icons/qv2ray.png")); hTray.setIcon(QIcon(GlobalConfig.uiConfig.useDarkTrayIcon ? ":/assets/icons/ui_dark/tray.png" : ":/assets/icons/ui_light/tray.png")); + // importConfigButton->setIcon(QICON_R("import.png")); - // pingTestBtn->setIcon(QICON_R("ping_gauge.png")); - // shareBtn->setIcon(QICON_R("share.png")); updownImageBox->setStyleSheet("image: url(" + QV2RAY_UI_RESOURCES_ROOT + "netspeed_arrow.png)"); updownImageBox_2->setStyleSheet("image: url(" + QV2RAY_UI_RESOURCES_ROOT + "netspeed_arrow.png)"); // @@ -109,7 +125,13 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) //, vinstance(), h // connect(ConnectionManager, &QvConnectionHandler::OnGroupCreated, this, &MainWindow::OnGroupCreated); connect(ConnectionManager, &QvConnectionHandler::OnGroupDeleted, this, &MainWindow::OnGroupDeleted); - + // + connect(ConnectionManager, &QvConnectionHandler::OnConnectionRenamed, [&](const ConnectionId &id, const QString &, const QString &newName) { + connectionNodes[id]->setText(MW_ITEM_COL_DISPLAYNAME, newName); // + }); + connect(ConnectionManager, &QvConnectionHandler::OnLatencyTestFinished, [&](const ConnectionId &id, const uint avg) { + connectionNodes[id]->setText(MW_ITEM_COL_LATENCY, NumericString(avg)); // + }); // connect(infoWidget, &ConnectionInfoWidget::OnEditRequested, this, &MainWindow::OnEditRequested); connect(infoWidget, &ConnectionInfoWidget::OnJsonEditRequested, this, &MainWindow::OnJsonEditRequested); @@ -164,12 +186,14 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) //, vinstance(), h QAction *action_RCM_StartThis = new QAction(tr("Connect to this"), this); QAction *action_RCM_RenameThis = new QAction(tr("Rename"), this); QAction *action_RCM_DeleteThese = new QAction(tr("Delete Connection"), this); + QAction *action_RCM_DuplicateThese = new QAction(QICON_R("duplicate.png"), tr("Duplicate to the Same Group"), this); QAction *action_RCM_ConvToComplex = new QAction(QICON_R("edit.png"), tr("Edit as Complex Config"), this); // connect(action_RCM_StartThis, &QAction::triggered, this, &MainWindow::on_action_StartThis_triggered); connect(action_RCM_ConvToComplex, &QAction::triggered, this, &MainWindow::on_action_RCM_ConvToComplex_triggered); connect(action_RCM_RenameThis, &QAction::triggered, this, &MainWindow::on_action_RCM_RenameThis_triggered); connect(action_RCM_DeleteThese, &QAction::triggered, this, &MainWindow::on_action_RCM_DeleteThese_triggered); + connect(action_RCM_DuplicateThese, &QAction::triggered, this, &MainWindow::on_action_RCM_DuplicateThese_triggered); // // Globally invokable signals. connect(this, &MainWindow::Connect, [&] { ConnectionManager->StartConnection(lastConnectedId); }); @@ -182,11 +206,35 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) //, vinstance(), h connectionListMenu = new QMenu(this); connectionListMenu->addAction(action_RCM_StartThis); connectionListMenu->addAction(action_RCM_RenameThis); + connectionListMenu->addAction(action_RCM_DuplicateThese); connectionListMenu->addAction(action_RCM_DeleteThese); connectionListMenu->addAction(action_RCM_ConvToComplex); // - LOG(MODULE_UI, "Loading data...") - auto groups = ConnectionManager->AllGroups(); + sortMenu = new QMenu(this); + sortAction_SortByName_Asc = new QAction(tr("By connection name, A-Z")); + sortAction_SortByName_Dsc = new QAction(tr("By connection name, Z-A")); + sortAction_SortByData_Asc = new QAction(tr("By data, Less to more")); + sortAction_SortByData_Dsc = new QAction(tr("By data, More to less")); + sortAction_SortByLatency_Asc = new QAction(tr("By latency, Short to long")); + sortAction_SortByLatency_Dsc = new QAction(tr("By latency, Long to short")); + // + connect(sortAction_SortByName_Asc, &QAction::triggered, [&] { SortConnectionList(MW_ITEM_COL_DISPLAYNAME, true); }); + connect(sortAction_SortByName_Dsc, &QAction::triggered, [&] { SortConnectionList(MW_ITEM_COL_DISPLAYNAME, false); }); + connect(sortAction_SortByData_Asc, &QAction::triggered, [&] { SortConnectionList(MW_ITEM_COL_DATAUSAGE, true); }); + connect(sortAction_SortByData_Dsc, &QAction::triggered, [&] { SortConnectionList(MW_ITEM_COL_DATAUSAGE, false); }); + connect(sortAction_SortByLatency_Asc, &QAction::triggered, [&] { SortConnectionList(MW_ITEM_COL_LATENCY, true); }); + connect(sortAction_SortByLatency_Dsc, &QAction::triggered, [&] { SortConnectionList(MW_ITEM_COL_LATENCY, false); }); + // + sortMenu->addAction(sortAction_SortByName_Asc); + sortMenu->addAction(sortAction_SortByName_Dsc); + sortMenu->addAction(sortAction_SortByData_Asc); + sortMenu->addAction(sortAction_SortByData_Dsc); + sortMenu->addAction(sortAction_SortByLatency_Asc); + sortMenu->addAction(sortAction_SortByLatency_Dsc); + // + sortBtn->setMenu(sortMenu); + // + LOG(MODULE_UI, "Loading data...") auto groups = ConnectionManager->AllGroups(); for (auto group : groups) { @@ -405,7 +453,6 @@ void MainWindow::on_connectionListWidget_customContextMenuRequested(const QPoint void MainWindow::on_action_RCM_DeleteThese_triggered() { - QvMessageBoxInfo(this, "NOT SUPPORTED", "WIP"); QList connlist; for (auto item : connectionListWidget->selectedItems()) @@ -678,8 +725,11 @@ void MainWindow::OnStatsAvailable(const ConnectionId &id, const quint64 upS, con netspeedLabel->setText(totalSpeedUp + NEWLINE + totalSpeedDown); dataamountLabel->setText(totalDataUp + NEWLINE + totalDataDown); // - hTray.setToolTip(TRAY_TOOLTIP_PREFIX NEWLINE + tr("Connected: ") + ConnectionManager->GetDisplayName(id) + NEWLINE "Up: " + totalSpeedUp + - " Down: " + totalSpeedDown); + hTray.setToolTip(TRAY_TOOLTIP_PREFIX NEWLINE + tr("Connected: ") + // + ConnectionManager->GetDisplayName(id) + NEWLINE "Up: " + totalSpeedUp + " Down: " + totalSpeedDown); + // + // Set data accordingly + connectionNodes[id]->setText(MW_ITEM_COL_DATAUSAGE, NumericString(ConnectionManager->GetConnectionTotalData(id))); } void MainWindow::OnVCoreLogAvailable(const ConnectionId &id, const QString &log) @@ -801,3 +851,37 @@ void MainWindow::on_action_RCM_RenameThis_triggered() CheckCurrentWidget; widget->BeginRename(); } + +void MainWindow::on_action_RCM_DuplicateThese_triggered() +{ + QList connlist; + + for (auto item : connectionListWidget->selectedItems()) + { + auto widget = GetItemWidget(item); + if (widget->IsConnection()) + { + connlist.append(get<1>(widget->Identifier())); + } + } + + LOG(MODULE_UI, "Selected " + QSTRN(connlist.count()) + " items") + + if (connlist.isEmpty()) + { + return; + } + + if (connlist.count() > 1 && + QvMessageBoxAsk(this, tr("Duplicating Connection(s)"), tr("Are you sure to duplicate these connection(s)?")) != QMessageBox::Yes) + { + return; + } + + for (auto conn : connlist) + { + ConnectionManager->CreateConnection(ConnectionManager->GetDisplayName(conn) + tr(" (Copy)"), // + ConnectionManager->GetConnectionGroupId(conn), // + ConnectionManager->GetConnectionRoot(conn)); + } +} diff --git a/src/ui/w_MainWindow.hpp b/src/ui/w_MainWindow.hpp index d08f0422..185f29b2 100644 --- a/src/ui/w_MainWindow.hpp +++ b/src/ui/w_MainWindow.hpp @@ -17,6 +17,15 @@ #include "ui/widgets/ConnectionInfoWidget.hpp" #include "ui/widgets/ConnectionItemWidget.hpp" +enum MW_ITEM_COL +{ + MW_ITEM_COL_DISPLAYNAME = 1, + MW_ITEM_COL_LATENCY = 2, + MW_ITEM_COL_IMPORTTIME = 3, + MW_ITEM_COL_LASTCONNETED = 4, + MW_ITEM_COL_DATAUSAGE = 5 +}; + class MainWindow : public QMainWindow , Ui::MainWindow @@ -77,11 +86,14 @@ class MainWindow void on_action_RCM_ConvToComplex_triggered(); void on_action_RCM_RenameThis_triggered(); void on_action_RCM_DeleteThese_triggered(); + void on_action_RCM_DuplicateThese_triggered(); // void on_connectionListWidget_itemDoubleClicked(QTreeWidgetItem *item, int column); void on_connectionFilterTxt_textEdited(const QString &arg1); void on_connectionListWidget_itemClicked(QTreeWidgetItem *item, int column); void on_locateBtn_clicked(); + // + void SortConnectionList(MW_ITEM_COL byCol, bool asending); private: QHash> groupNodes; @@ -115,6 +127,14 @@ class MainWindow QAction *action_Tray_SetSystemProxy; QAction *action_Tray_ClearSystemProxy; // + QMenu *sortMenu; + QAction *sortAction_SortByName_Asc; + QAction *sortAction_SortByName_Dsc; + QAction *sortAction_SortByLatency_Asc; + QAction *sortAction_SortByLatency_Dsc; + QAction *sortAction_SortByData_Asc; + QAction *sortAction_SortByData_Dsc; + // // Extra Headers For w_MainWindow_extra.cpp Handling V2ray Connectivities. ConnectionId lastConnectedId; void MWSetSystemProxy(); diff --git a/src/ui/w_MainWindow.ui b/src/ui/w_MainWindow.ui index c369cdaf..2596a71a 100644 --- a/src/ui/w_MainWindow.ui +++ b/src/ui/w_MainWindow.ui @@ -86,16 +86,6 @@ - - - - Search - - - true - - - @@ -113,43 +103,14 @@ - - - - Qt::CustomContextMenu + + + + Search - - QAbstractItemView::NoEditTriggers - - - QAbstractItemView::ExtendedSelection - - - QAbstractItemView::ScrollPerPixel - - - 0 - - + true - - true - - - true - - - true - - - true - - - - 1 - - @@ -204,6 +165,62 @@ + + + + + + + + :/assets/icons/ui_light/sort.png:/assets/icons/ui_light/sort.png + + + QToolButton::InstantPopup + + + + + + + Qt::CustomContextMenu + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::ScrollPerPixel + + + 0 + + + true + + + true + + + true + + + true + + + true + + + false + + + + 1 + + + + diff --git a/src/ui/widgets/ConnectionInfoWidget.cpp b/src/ui/widgets/ConnectionInfoWidget.cpp index 61245060..07ea8cdd 100644 --- a/src/ui/widgets/ConnectionInfoWidget.cpp +++ b/src/ui/widgets/ConnectionInfoWidget.cpp @@ -8,7 +8,6 @@ ConnectionInfoWidget::ConnectionInfoWidget(QWidget *parent) : QWidget(parent) { setupUi(this); - duplicateBtn->setIcon(QICON_R("duplicate.png")); deleteBtn->setIcon(QICON_R("delete.png")); editBtn->setIcon(QICON_R("edit.png")); editJsonBtn->setIcon(QICON_R("json.png")); @@ -32,20 +31,24 @@ void ConnectionInfoWidget::ShowDetails(const tuple &_iden editBtn->setEnabled(isConnection); editJsonBtn->setEnabled(isConnection); connectBtn->setEnabled(isConnection); - duplicateBtn->setEnabled(isConnection); stackedWidget->setCurrentIndex(isConnection ? 0 : 1); if (isConnection) { + bool isComplexConfig = IsComplexConfig(ConnectionManager->GetConnectionRoot(connectionId)); + qrLabel->setVisible(!isComplexConfig); + // + auto shareLink = ConvertConfigToString(connectionId); + // + shareLinkTxt->setText(isComplexConfig ? tr("(Complex Connection Config)") : shareLink); + protocolLabel->setText(isComplexConfig ? tr("N/A") : ConnectionManager->GetConnectionProtocolString(connectionId)); + // groupLabel->setText(ConnectionManager->GetDisplayName(groupId, 175)); - protocolLabel->setText(ConnectionManager->GetConnectionProtocolString(connectionId)); auto [protocol, host, port] = ConnectionManager->GetConnectionData(connectionId); Q_UNUSED(protocol) addressLabel->setText(host); portLabel->setNum(port); // - auto shareLink = ConvertConfigToString(connectionId); - shareLinkTxt->setText(shareLink); shareLinkTxt->setCursorPosition(0); // QZXingEncoderConfig conf; @@ -145,16 +148,6 @@ void ConnectionInfoWidget::OnDisConnected(const ConnectionId &id) } } -void ConnectionInfoWidget::on_duplicateBtn_clicked() -{ - if (connectionId != NullConnectionId) - { - ConnectionManager->CreateConnection(ConnectionManager->GetDisplayName(connectionId) + tr(" (Copy)"), - ConnectionManager->GetConnectionGroupId(connectionId), - ConnectionManager->GetConnectionRoot(connectionId)); - } -} - void ConnectionInfoWidget::on_latencyBtn_clicked() { if (connectionId != NullConnectionId) diff --git a/src/ui/widgets/ConnectionInfoWidget.hpp b/src/ui/widgets/ConnectionInfoWidget.hpp index 2d87e0e8..43cf1958 100644 --- a/src/ui/widgets/ConnectionInfoWidget.hpp +++ b/src/ui/widgets/ConnectionInfoWidget.hpp @@ -20,19 +20,17 @@ class ConnectionInfoWidget void OnEditRequested(const ConnectionId &id); void OnJsonEditRequested(const ConnectionId &id); + protected: + bool eventFilter(QObject *object, QEvent *event) override; + private slots: void on_connectBtn_clicked(); void on_editBtn_clicked(); void on_editJsonBtn_clicked(); void on_deleteBtn_clicked(); - protected: - bool eventFilter(QObject *object, QEvent *event) override; - - private slots: void OnConnected(const ConnectionId &id); void OnDisConnected(const ConnectionId &id); - void on_duplicateBtn_clicked(); void on_latencyBtn_clicked(); private: diff --git a/src/ui/widgets/ConnectionInfoWidget.ui b/src/ui/widgets/ConnectionInfoWidget.ui index 6b5083f9..ec1cdb52 100644 --- a/src/ui/widgets/ConnectionInfoWidget.ui +++ b/src/ui/widgets/ConnectionInfoWidget.ui @@ -58,6 +58,48 @@ + + + + Connect/Disconnect + + + + + + + :/assets/icons/ui_light/connect.png:/assets/icons/ui_light/connect.png + + + + + + + Edit Connection + + + + + + + :/assets/icons/ui_light/edit.png:/assets/icons/ui_light/edit.png + + + + + + + Edit Connection as JSON + + + + + + + :/assets/icons/ui_light/json.png:/assets/icons/ui_light/json.png + + + @@ -86,62 +128,6 @@ - - - - Edit Connection as JSON - - - - - - - :/assets/icons/ui_light/json.png:/assets/icons/ui_light/json.png - - - - - - - Edit Connection - - - - - - - :/assets/icons/ui_light/edit.png:/assets/icons/ui_light/edit.png - - - - - - - Duplicate Connection - - - - - - - :/assets/icons/ui_light/duplicate.png:/assets/icons/ui_light/duplicate.png - - - - - - - Connect/Disconnect - - - - - - - :/assets/icons/ui_light/connect.png:/assets/icons/ui_light/connect.png - - - @@ -264,7 +250,7 @@ - + Link @@ -323,6 +309,9 @@ QR Code + + QFrame::Box +