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
+