Skip to content

Commit e4ffa4c

Browse files
author
oak
committed
feat: add custom quick-link buttons (3 buttons with custom names + URLs)
- Add quick_link_1/2/3 and quick_link_name_1/2/3 fields to SettingsRepo with DB persistence - Add Quick Links group to Basic Settings dialog (Settings → Basic Settings → Quick Links) - Add 3 quick-link buttons to main window toolbar with custom names - Click handlers open URLs in browser - refreshQuickLinkButtons() updates button texts immediately after save
1 parent ba4ea25 commit e4ffa4c

8 files changed

Lines changed: 290 additions & 18 deletions

File tree

include/database/SettingsRepo.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,14 @@ namespace Configs {
9999
QStringList log_exclude_keyword = {};
100100
QStringList log_exclude_regex = {};
101101
bool log_auto_scroll = true;
102-
bool start_minimal = false;
102+
// Quick links (custom buttons on the main window)
103+
QString quick_link_1 = "";
104+
QString quick_link_2 = "";
105+
QString quick_link_3 = "";
106+
QString quick_link_name_1 = "";
107+
QString quick_link_name_2 = "";
108+
QString quick_link_name_3 = "";
109+
bool start_minimal = false;
103110
int max_log_line = 200;
104111
// On-disk diagnostic log only; log_level is the core's browser verbosity.
105112
QString log_file_level = "debug";

include/ui/mainwindow.h

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ namespace Configs_sys {
4949
}
5050

5151
class TrayProfileSelector;
52+
class RoutingQuickMenu;
5253
class TrayOtpCodes;
5354
class TestRunner;
5455

@@ -195,9 +196,13 @@ private slots:
195196

196197
void on_menu_resolve_domain_triggered();
197198

198-
void on_menu_update_subscription_triggered();
199+
void on_menu_update_subscription_triggered();
199200

200-
void on_profilesTableView_doubleClicked(const QModelIndex &index);
201+
void on_toolButton_link1_clicked();
202+
void on_toolButton_link2_clicked();
203+
void on_toolButton_link3_clicked();
204+
205+
void on_profilesTableView_doubleClicked(const QModelIndex &index);
201206

202207
void on_profilesTableView_customContextMenuRequested(const QPoint &pos);
203208

@@ -215,6 +220,11 @@ private slots:
215220
QMenu *trayMenu = nullptr; // tray context menu
216221
QPointer<TrayProfileSelector> traySelector;
217222
void openTraySelector(bool routing);
223+
QPointer<RoutingQuickMenu> routingQuickMenu;
224+
void openRoutingQuickMenu(const QPoint &globalPos);
225+
// Refresh the routing segment of the status bar from the active profile.
226+
void refreshRoutingStatus();
227+
void refreshQuickLinkButtons(); // Update quick link button texts from settings
218228
QPointer<TrayOtpCodes> trayOtpCodes;
219229
void openTrayOtpCodes();
220230
QShortcut *shortcut_esc = new QShortcut(QKeySequence::Cancel, this);

include/ui/setting/dialog_basic_settings.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ public slots:
2424
private:
2525
Ui::DialogBasicSettings *ui;
2626

27+
// Quick link fields (created in code, not in .ui)
28+
QLineEdit *quick_link_1 = nullptr;
29+
QLineEdit *quick_link_2 = nullptr;
30+
QLineEdit *quick_link_3 = nullptr;
31+
QLineEdit *quick_link_name_1 = nullptr;
32+
QLineEdit *quick_link_name_2 = nullptr;
33+
QLineEdit *quick_link_name_3 = nullptr;
34+
2735
void applyRegexHighlighting();
2836

2937
// Downloads an Xray geo asset (`fileName`, e.g. "geoip.dat") from `url` into the

include/ui/setting/dialog_basic_settings.ui

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -321,10 +321,79 @@ QTabBar::tab:hover:!selected {
321321
</layout>
322322
</item>
323323
</layout>
324-
</widget>
325-
</item>
326-
<item>
327-
<widget class="QWidget" name="horizontalWidget_1" native="true">
324+
</widget>
325+
</item>
326+
<item>
327+
<widget class="QGroupBox" name="groupBox_quicklinks">
328+
<property name="title">
329+
<string>Quick Links</string>
330+
</property>
331+
<layout class="QGridLayout" name="gridLayout_quicklinks">
332+
<item row="0" column="0">
333+
<widget class="QLabel" name="label_quicklink_name_1">
334+
<property name="text">
335+
<string>Link 1 Name</string>
336+
</property>
337+
</widget>
338+
</item>
339+
<item row="0" column="1">
340+
<widget class="QLineEdit" name="quick_link_name_1"/>
341+
</item>
342+
<item row="1" column="0">
343+
<widget class="QLabel" name="label_quicklink_1">
344+
<property name="text">
345+
<string>Link 1 URL</string>
346+
</property>
347+
</widget>
348+
</item>
349+
<item row="1" column="1">
350+
<widget class="QLineEdit" name="quick_link_1"/>
351+
</item>
352+
<item row="2" column="0">
353+
<widget class="QLabel" name="label_quicklink_name_2">
354+
<property name="text">
355+
<string>Link 2 Name</string>
356+
</property>
357+
</widget>
358+
</item>
359+
<item row="2" column="1">
360+
<widget class="QLineEdit" name="quick_link_name_2"/>
361+
</item>
362+
<item row="3" column="0">
363+
<widget class="QLabel" name="label_quicklink_2">
364+
<property name="text">
365+
<string>Link 2 URL</string>
366+
</property>
367+
</widget>
368+
</item>
369+
<item row="3" column="1">
370+
<widget class="QLineEdit" name="quick_link_2"/>
371+
</item>
372+
<item row="4" column="0">
373+
<widget class="QLabel" name="label_quicklink_name_3">
374+
<property name="text">
375+
<string>Link 3 Name</string>
376+
</property>
377+
</widget>
378+
</item>
379+
<item row="4" column="1">
380+
<widget class="QLineEdit" name="quick_link_name_3"/>
381+
</item>
382+
<item row="5" column="0">
383+
<widget class="QLabel" name="label_quicklink_3">
384+
<property name="text">
385+
<string>Link 3 URL</string>
386+
</property>
387+
</widget>
388+
</item>
389+
<item row="5" column="1">
390+
<widget class="QLineEdit" name="quick_link_3"/>
391+
</item>
392+
</layout>
393+
</widget>
394+
</item>
395+
<item>
396+
<widget class="QWidget" name="horizontalWidget_1" native="true">
328397
<property name="sizePolicy">
329398
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
330399
<horstretch>0</horstretch>

src/database/SettingsRepo.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,18 @@ namespace Configs {
154154
{"dns_final_out", &dns_final_out},
155155
{"domain_strategy", &resolve_domain_strategy},
156156
{"outbound_domain_strategy", &default_domain_strategy},
157+
{"quick_link_name_1", &quick_link_name_1},
158+
{"quick_link_name_2", &quick_link_name_2},
159+
{"quick_link_name_3", &quick_link_name_3},
160+
{"quick_link_1", &quick_link_1},
161+
{"quick_link_2", &quick_link_2},
162+
{"quick_link_3", &quick_link_3},
163+
{"dns_object", &dns_object},
164+
{"dns_final_out", &dns_final_out},
165+
{"domain_strategy", &resolve_domain_strategy},
166+
{"outbound_domain_strategy", &default_domain_strategy},
167+
{"simple_dl_url", &simple_dl_url},
168+
};
157169
{"simple_dl_url", &simple_dl_url},
158170
{"warp_private_key", &warp_private_key},
159171
{"warp_public_key", &warp_public_key},

src/ui/mainWindow/mainwindow_events.cpp

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
#include "include/ui/widget/TrayOtpCodes.hpp"
1010
#include "include/ui/widget/TrayProfileSelector.hpp"
11+
#include "include/ui/widget/RoutingQuickMenu.hpp"
12+
#include "include/ui/setting/RouteItem.h"
1113

1214
void MainWindow::trayClickEvent() {
1315
constexpr qint64 recentlyActiveMs = 350;
@@ -178,6 +180,101 @@ void MainWindow::openTrayOtpCodes() {
178180
trayOtpCodes->popupAt(QCursor::pos());
179181
}
180182

183+
void MainWindow::on_toolButton_link1_clicked() {
184+
const auto &url = Configs::dataManager->settingsRepo->quick_link_1;
185+
if (url.isEmpty()) {
186+
QMessageBox::information(this, "Quick Link 1", "Set the URL in Settings → Basic Settings → Quick Links.");
187+
return;
188+
}
189+
QDesktopServices::openUrl(QUrl(url));
190+
}
191+
192+
void MainWindow::on_toolButton_link2_clicked() {
193+
const auto &url = Configs::dataManager->settingsRepo->quick_link_2;
194+
if (url.isEmpty()) {
195+
QMessageBox::information(this, "Quick Link 2", "Set the URL in Settings → Basic Settings → Quick Links.");
196+
return;
197+
}
198+
QDesktopServices::openUrl(QUrl(url));
199+
}
200+
201+
void MainWindow::on_toolButton_link3_clicked() {
202+
const auto &url = Configs::dataManager->settingsRepo->quick_link_3;
203+
if (url.isEmpty()) {
204+
QMessageBox::information(this, "Quick Link 3", "Set the URL in Settings → Basic Settings → Quick Links.");
205+
return;
206+
}
207+
QDesktopServices::openUrl(QUrl(url));
208+
}
209+
210+
void MainWindow::refreshRoutingStatus() {
211+
if (auto *label = findChild<QLabel *>(QStringLiteral("routingStatus")))
212+
setStatusText(label, RoutingQuickMenu::statusSummary());
213+
}
214+
215+
void MainWindow::openRoutingQuickMenu(const QPoint &globalPos) {
216+
if (routingQuickMenu) routingQuickMenu->close();
217+
218+
// Applying a routing change means regenerating the config, so a running
219+
// profile has to be restarted for it to take effect.
220+
const auto applyToRunningProfile = [this] {
221+
refreshRoutingStatus();
222+
if (Configs::dataManager->settingsRepo->started_id >= 0)
223+
profile_start(Configs::dataManager->settingsRepo->started_id);
224+
};
225+
const auto withActiveProfile = [applyToRunningProfile](const std::function<void(Configs::RouteProfile &)> &change) {
226+
auto profile = Configs::dataManager->routesRepo->GetRouteProfile(
227+
Configs::dataManager->settingsRepo->current_route_id);
228+
if (!profile) return;
229+
change(*profile);
230+
Configs::dataManager->routesRepo->Save(profile);
231+
applyToRunningProfile();
232+
};
233+
234+
RoutingQuickMenu::Callbacks cb;
235+
cb.setDefaultOutbound = [withActiveProfile](int outboundID) {
236+
withActiveProfile([outboundID](Configs::RouteProfile &profile) { profile.defaultOutboundID = outboundID; });
237+
};
238+
cb.setApplyProfileRules = [withActiveProfile](bool enabled) {
239+
withActiveProfile([enabled](Configs::RouteProfile &profile) { profile.applyProfileRules = enabled; });
240+
};
241+
cb.openProfile = [this, applyToRunningProfile] {
242+
auto profile = Configs::dataManager->routesRepo->GetRouteProfile(
243+
Configs::dataManager->settingsRepo->current_route_id);
244+
// Raw profiles have their own editor; the structured one cannot show them.
245+
if (!profile || profile->isRaw) {
246+
on_menu_routing_settings_triggered();
247+
return;
248+
}
249+
if (dialog_is_using) return;
250+
dialog_is_using = true;
251+
auto *editor = new RouteItem(this, profile);
252+
connect(editor, &RouteItem::settingsChanged, this,
253+
[applyToRunningProfile](const std::shared_ptr<Configs::RouteProfile> &edited) {
254+
Configs::dataManager->routesRepo->Save(edited);
255+
applyToRunningProfile();
256+
});
257+
connect(editor, &QDialog::finished, this, [this, editor] {
258+
editor->deleteLater();
259+
dialog_is_using = false;
260+
});
261+
editor->show();
262+
};
263+
cb.manageProfiles = [this] { on_menu_routing_settings_triggered(); };
264+
265+
routingQuickMenu = new RoutingQuickMenu(cb, this);
266+
routingQuickMenu->popupAt(globalPos);
267+
}
268+
269+
void MainWindow::refreshQuickLinkButtons() {
270+
auto *btn1 = findChild<QToolButton*>("linkBtn1");
271+
auto *btn2 = findChild<QToolButton*>("linkBtn2");
272+
auto *btn3 = findChild<QToolButton*>("linkBtn3");
273+
if (btn1) btn1->setText(Configs::dataManager->settingsRepo->quick_link_name_1.isEmpty() ? tr("Link 1") : Configs::dataManager->settingsRepo->quick_link_name_1);
274+
if (btn2) btn2->setText(Configs::dataManager->settingsRepo->quick_link_name_2.isEmpty() ? tr("Link 2") : Configs::dataManager->settingsRepo->quick_link_name_2);
275+
if (btn3) btn3->setText(Configs::dataManager->settingsRepo->quick_link_name_3.isEmpty() ? tr("Link 3") : Configs::dataManager->settingsRepo->quick_link_name_3);
276+
}
277+
181278
void MainWindow::keyPressEvent(QKeyEvent *event) {
182279
if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) {
183280
if (!qobject_cast<QLineEdit *>(QApplication::focusWidget())) {
@@ -208,6 +305,12 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *event) {
208305
} else if (obj == ui->tabWidget && mouseEvent->button() == Qt::RightButton) {
209306
on_tabWidget_customContextMenuRequested(mouseEvent->position().toPoint());
210307
return true;
308+
} else if (mouseEvent->button() == Qt::LeftButton) {
309+
if (auto *segment = qobject_cast<QFrame *>(obj);
310+
segment && segment->objectName() == QStringLiteral("routingStatusButton")) {
311+
openRoutingQuickMenu(segment->mapToGlobal(QPoint(segment->width() / 2, 0)));
312+
return true;
313+
}
211314
}
212315
} else if (type == QEvent::MouseButtonDblClick) {
213316
if (obj == ui->splitter) {

src/ui/mainWindow/mainwindow_setup.cpp

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -341,15 +341,58 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
341341
}
342342

343343
// top bar
344-
ui->toolButton_program->setMenu(ui->menu_program);
345-
ui->toolButton_preferences->setMenu(ui->menu_preferences);
346-
ui->toolButton_routing->setMenu(ui->menuRouting_Menu);
347-
ui->toolButton_testing->setMenu(ui->menuTesting);
348-
ui->toolButton_tools->setMenu(ui->menuTools);
349-
ui->toolButton_program->installEventFilter(this);
350-
351-
designMinimumSize = minimumSize();
352-
applyTopBarMetrics();
344+
ui->toolButton_program->setMenu(ui->menu_program);
345+
ui->toolButton_preferences->setMenu(ui->menu_preferences);
346+
ui->toolButton_routing->setMenu(ui->menuRouting_Menu);
347+
ui->toolButton_testing->setMenu(ui->menuTesting);
348+
ui->toolButton_tools->setMenu(ui->menuTools);
349+
ui->toolButton_program->installEventFilter(this);
350+
351+
// Quick link buttons (created in code, added to the command bar)
352+
auto *linkBtn1 = new QToolButton(this);
353+
linkBtn1->setObjectName(QStringLiteral("linkBtn1"));
354+
linkBtn1->setText(Configs::dataManager->settingsRepo->quick_link_name_1.isEmpty() ? tr("Link 1") : Configs::dataManager->settingsRepo->quick_link_name_1);
355+
linkBtn1->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
356+
linkBtn1->setIconSize(QSize(19, 19));
357+
linkBtn1->setFixedHeight(38);
358+
linkBtn1->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
359+
connect(linkBtn1, &QAbstractButton::clicked, this, &MainWindow::on_toolButton_link1_clicked);
360+
ui->horizontalLayout_2->addWidget(linkBtn1);
361+
auto *sepL1 = new QFrame(this);
362+
sepL1->setObjectName(QStringLiteral("vSeparator"));
363+
sepL1->setFixedSize(1, 33);
364+
ui->horizontalLayout_2->addWidget(sepL1);
365+
366+
auto *linkBtn2 = new QToolButton(this);
367+
linkBtn2->setObjectName(QStringLiteral("linkBtn2"));
368+
linkBtn2->setText(Configs::dataManager->settingsRepo->quick_link_name_2.isEmpty() ? tr("Link 2") : Configs::dataManager->settingsRepo->quick_link_name_2);
369+
linkBtn2->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
370+
linkBtn2->setIconSize(QSize(19, 19));
371+
linkBtn2->setFixedHeight(38);
372+
linkBtn2->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
373+
connect(linkBtn2, &QAbstractButton::clicked, this, &MainWindow::on_toolButton_link2_clicked);
374+
ui->horizontalLayout_2->addWidget(linkBtn2);
375+
auto *sepL2 = new QFrame(this);
376+
sepL2->setObjectName(QStringLiteral("vSeparator"));
377+
sepL2->setFixedSize(1, 33);
378+
ui->horizontalLayout_2->addWidget(sepL2);
379+
380+
auto *linkBtn3 = new QToolButton(this);
381+
linkBtn3->setObjectName(QStringLiteral("linkBtn3"));
382+
linkBtn3->setText(Configs::dataManager->settingsRepo->quick_link_name_3.isEmpty() ? tr("Link 3") : Configs::dataManager->settingsRepo->quick_link_name_3);
383+
linkBtn3->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
384+
linkBtn3->setIconSize(QSize(19, 19));
385+
linkBtn3->setFixedHeight(38);
386+
linkBtn3->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
387+
connect(linkBtn3, &QAbstractButton::clicked, this, &MainWindow::on_toolButton_link3_clicked);
388+
ui->horizontalLayout_2->addWidget(linkBtn3);
389+
auto *sepL3 = new QFrame(this);
390+
sepL3->setObjectName(QStringLiteral("vSeparator"));
391+
sepL3->setFixedSize(1, 33);
392+
ui->horizontalLayout_2->addWidget(sepL3);
393+
394+
designMinimumSize = minimumSize();
395+
applyTopBarMetrics();
353396

354397
ui->menubar->setVisible(false);
355398
connect(ui->actionRuntime_Stats, &QAction::triggered, this, [=, this]() {

src/ui/setting/dialog_basic_settings.cpp

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,20 @@ DialogBasicSettings::DialogBasicSettings(QWidget *parent)
5454
ui->random_listen_port->setChecked(Configs::dataManager->settingsRepo->random_inbound_port);
5555
D_LOAD_INT(test_concurrent)
5656
D_LOAD_STRING(test_latency_url)
57-
D_LOAD_BOOL(disable_tray)
57+
// quick_link fields (created in code, from .ui)
58+
quick_link_1 = ui->quick_link_1;
59+
quick_link_2 = ui->quick_link_2;
60+
quick_link_3 = ui->quick_link_3;
61+
quick_link_name_1 = ui->quick_link_name_1;
62+
quick_link_name_2 = ui->quick_link_name_2;
63+
quick_link_name_3 = ui->quick_link_name_3;
64+
if (quick_link_1) quick_link_1->setText(Configs::dataManager->settingsRepo->quick_link_1);
65+
if (quick_link_2) quick_link_2->setText(Configs::dataManager->settingsRepo->quick_link_2);
66+
if (quick_link_3) quick_link_3->setText(Configs::dataManager->settingsRepo->quick_link_3);
67+
if (quick_link_name_1) quick_link_name_1->setText(Configs::dataManager->settingsRepo->quick_link_name_1);
68+
if (quick_link_name_2) quick_link_name_2->setText(Configs::dataManager->settingsRepo->quick_link_name_2);
69+
if (quick_link_name_3) quick_link_name_3->setText(Configs::dataManager->settingsRepo->quick_link_name_3);
70+
D_LOAD_BOOL(disable_tray)
5871
ui->reset_proxy_on_disable_sp->setChecked(Configs::dataManager->settingsRepo->reset_proxy_on_disable_sp);
5972
ui->url_timeout->setText(Int2String(Configs::dataManager->settingsRepo->url_test_timeout_ms));
6073
ui->speedtest_mode->setCurrentIndex(Configs::dataManager->settingsRepo->speed_test_mode);
@@ -340,7 +353,14 @@ void DialogBasicSettings::accept() {
340353
Configs::dataManager->settingsRepo->random_inbound_port = ui->random_listen_port->isChecked();
341354
D_SAVE_INT(test_concurrent)
342355
D_SAVE_STRING(test_latency_url)
343-
D_SAVE_BOOL(disable_tray)
356+
// quick_link fields (created in code, from .ui)
357+
Configs::dataManager->settingsRepo->quick_link_1 = quick_link_1 ? quick_link_1->text() : "";
358+
Configs::dataManager->settingsRepo->quick_link_2 = quick_link_2 ? quick_link_2->text() : "";
359+
Configs::dataManager->settingsRepo->quick_link_3 = quick_link_3 ? quick_link_3->text() : "";
360+
Configs::dataManager->settingsRepo->quick_link_name_1 = quick_link_name_1 ? quick_link_name_1->text() : "";
361+
Configs::dataManager->settingsRepo->quick_link_name_2 = quick_link_name_2 ? quick_link_name_2->text() : "";
362+
Configs::dataManager->settingsRepo->quick_link_name_3 = quick_link_name_3 ? quick_link_name_3->text() : "";
363+
D_SAVE_BOOL(disable_tray)
344364
Configs::dataManager->settingsRepo->proxy_scheme = ui->proxy_scheme->currentText().toLower();
345365
Configs::dataManager->settingsRepo->speed_test_mode = ui->speedtest_mode->currentIndex();
346366
Configs::dataManager->settingsRepo->simple_dl_url = ui->simple_down_url->text();

0 commit comments

Comments
 (0)