feat(vqt6): 信号槽回调机制 + 高层包装类扩展

- 信号槽:vqt6_signal_register/trigger_slot 锁外回调,覆盖 QPushButton/QCheckBox/QRadioButton/QLineEdit/QComboBox/QSlider/QSpinBox/QProgressBar/QListWidget/QTabWidget/QAction/QTimer 共 13 个信号
- 高层包装:新增 QMainWindow/QDialog/QLineEdit/QCheckBox/QRadioButton/QComboBox/QSlider/QSpinBox/QProgressBar/QTextEdit/QPlainTextEdit/QListWidget/QTabWidget/QStackedWidget/QHBoxLayout/QVBoxLayout/QGridLayout/QMessageBox/QFileDialog/QTimer/QMenuBar/QMenu/QAction/QStatusBar/QColor/QFont 等 26 个类
- 便捷 FFI:vqt6_qlabel_set_text_utf8 / vqt6_qplaintextedit_append_utf8(Viper str 直传,避免 QString 手动管理)
- 修复:QApplication 构造需 int& 左值(dummy_argc);QCheckBox 用 checkStateChanged 避免弃用警告
This commit is contained in:
TermiNexus committed 2026-09-02 09:16:35 +08:00
1 parent 250be46051
commit 2aff684cc4
7 files changed
+1646 -5

No files matched your search

+1408
View File
File diff suppressed because it is too large. Load diff
+8
View File
@@ -143,6 +143,14 @@ typedef void (*vqt6_slot_callback_t)(int slot_id, void* user_data);
VQT6_API void vqt6_register_slot(int slot_id, void* user_data);
VQT6_API void vqt6_unregister_slot(int slot_id);
VQT6_API void vqt6_trigger_slot(int slot_id);
// 分配新 slot_id 并注册回调(信号→回调桥梁,Qt 信号触发时调用 cb(slot_id, user_data))
VQT6_API int vqt6_signal_register(vqt6_slot_callback_t cb, void* user_data);
VQT6_API void vqt6_signal_unregister(int slot_id);
// ============================================================
// 信号连接(QTimer → timeout)
// ============================================================
VQT6_API void vqt6_qtimer_connect_timeout(void* timer, vqt6_slot_callback_t cb, void* user_data);
#ifdef __cplusplus
}
+36 -4
View File
@@ -20,6 +20,7 @@
namespace {
QMutex g_slot_mutex;
QHash<int, QPair<vqt6_slot_callback_t, void*>> g_slots;
int g_next_slot_id = 1000; // 避开小整数 slot_id(用户可能直接用 trigger_slot 测试)
}
// ============================================================
@@ -425,9 +426,40 @@ extern "C" VQT6_API void vqt6_unregister_slot(int slot_id) {
}
extern "C" VQT6_API void vqt6_trigger_slot(int slot_id) {
QMutexLocker locker(&g_slot_mutex);
auto it = g_slots.find(slot_id);
if (it != g_slots.end() && it.value().first != nullptr) {
it.value().first(slot_id, it.value().second);
// 拷贝条目后释放锁再回调,避免回调内再触发/注册时对非递归 QMutex 死锁
QPair<vqt6_slot_callback_t, void*> entry(nullptr, nullptr);
{
QMutexLocker locker(&g_slot_mutex);
auto it = g_slots.find(slot_id);
if (it == g_slots.end()) return;
entry = it.value();
}
if (entry.first != nullptr) {
entry.first(slot_id, entry.second);
}
}
extern "C" VQT6_API int vqt6_signal_register(vqt6_slot_callback_t cb, void* user_data) {
if (cb == nullptr) return -1;
QMutexLocker locker(&g_slot_mutex);
int id = ++g_next_slot_id;
g_slots.insert(id, QPair<vqt6_slot_callback_t, void*>(cb, user_data));
return id;
}
extern "C" VQT6_API void vqt6_signal_unregister(int slot_id) {
vqt6_unregister_slot(slot_id);
}
// ============================================================
// QTimer — timeout 信号 → 回调
// ============================================================
extern "C" VQT6_API void vqt6_qtimer_connect_timeout(void* timer, vqt6_slot_callback_t cb, void* user_data) {
if (timer == nullptr) return;
int slot_id = vqt6_signal_register(cb, user_data);
if (slot_id < 0) return;
QObject::connect(static_cast<QTimer*>(timer), &QTimer::timeout,
static_cast<QTimer*>(timer), [slot_id]() {
vqt6_trigger_slot(slot_id);
});
}
+147 -1
View File
@@ -56,7 +56,8 @@ extern "C" VQT6_API void* vqt6_qapplication_create(int argc, char** argv) {
if (argv == nullptr || argc <= 0) {
static char vqt6_dummy0[] = "vqt6_app";
static char* vqt6_dummy_argv[2] = { vqt6_dummy0, nullptr };
return new QApplication(1, vqt6_dummy_argv);
int dummy_argc = 1;
return new QApplication(dummy_argc, vqt6_dummy_argv);
}
return new QApplication(argc, argv);
}
@@ -477,6 +478,11 @@ extern "C" VQT6_API void vqt6_qlabel_set_text(void* label, void* text) {
static_cast<QLabel*>(label)->setText(*static_cast<QString*>(text));
}
extern "C" VQT6_API void vqt6_qlabel_set_text_utf8(void* label, const char* utf8) {
if (label == nullptr || utf8 == nullptr) return;
static_cast<QLabel*>(label)->setText(QString::fromUtf8(utf8));
}
extern "C" VQT6_API void* vqt6_qlabel_text(void* label) {
if (label == nullptr) return nullptr;
return new QString(static_cast<QLabel*>(label)->text());
@@ -757,6 +763,11 @@ extern "C" VQT6_API void vqt6_qplaintextedit_append(void* pte, void* text) {
static_cast<QPlainTextEdit*>(pte)->appendPlainText(*static_cast<QString*>(text));
}
extern "C" VQT6_API void vqt6_qplaintextedit_append_utf8(void* pte, const char* utf8) {
if (pte == nullptr || utf8 == nullptr) return;
static_cast<QPlainTextEdit*>(pte)->appendPlainText(QString::fromUtf8(utf8));
}
extern "C" VQT6_API void vqt6_qplaintextedit_clear(void* pte) {
if (pte == nullptr) return;
static_cast<QPlainTextEdit*>(pte)->clear();
@@ -1749,3 +1760,138 @@ extern "C" VQT6_API void* vqt6_qfiledialog_get_open_file_names(void* parent, voi
filter != nullptr ? *static_cast<QString*>(filter) : QString()
));
}
// ============================================================
// 信号连接(Qt 信号 → Viper 回调 vqt6_slot_callback_t)
// 模式:vqt6_signal_register 分配 slot_id → Qt lambda 连接信号 →
// 信号触发时 vqt6_trigger_slot(slot_id) 调回 Viper 回调
// ============================================================
extern "C" VQT6_API void vqt6_qpushbutton_connect_clicked(void* btn, vqt6_slot_callback_t cb, void* user_data) {
if (btn == nullptr) return;
int slot_id = vqt6_signal_register(cb, user_data);
if (slot_id < 0) return;
QObject::connect(static_cast<QPushButton*>(btn), &QPushButton::clicked,
static_cast<QPushButton*>(btn), [slot_id](bool) {
vqt6_trigger_slot(slot_id);
});
}
extern "C" VQT6_API void vqt6_qpushbutton_connect_toggled(void* btn, vqt6_slot_callback_t cb, void* user_data) {
if (btn == nullptr) return;
int slot_id = vqt6_signal_register(cb, user_data);
if (slot_id < 0) return;
QObject::connect(static_cast<QPushButton*>(btn), &QPushButton::toggled,
static_cast<QPushButton*>(btn), [slot_id](bool) {
vqt6_trigger_slot(slot_id);
});
}
extern "C" VQT6_API void vqt6_qcheckbox_connect_toggled(void* cb_widget, vqt6_slot_callback_t cb, void* user_data) {
if (cb_widget == nullptr) return;
int slot_id = vqt6_signal_register(cb, user_data);
if (slot_id < 0) return;
QObject::connect(static_cast<QCheckBox*>(cb_widget), &QCheckBox::toggled,
static_cast<QCheckBox*>(cb_widget), [slot_id](bool) {
vqt6_trigger_slot(slot_id);
});
}
extern "C" VQT6_API void vqt6_qcheckbox_connect_state_changed(void* cb_widget, vqt6_slot_callback_t cb, void* user_data) {
if (cb_widget == nullptr) return;
int slot_id = vqt6_signal_register(cb, user_data);
if (slot_id < 0) return;
QObject::connect(static_cast<QCheckBox*>(cb_widget), &QCheckBox::checkStateChanged,
static_cast<QCheckBox*>(cb_widget), [slot_id](Qt::CheckState) {
vqt6_trigger_slot(slot_id);
});
}
extern "C" VQT6_API void vqt6_qradiobutton_connect_toggled(void* rb, vqt6_slot_callback_t cb, void* user_data) {
if (rb == nullptr) return;
int slot_id = vqt6_signal_register(cb, user_data);
if (slot_id < 0) return;
QObject::connect(static_cast<QRadioButton*>(rb), &QRadioButton::toggled,
static_cast<QRadioButton*>(rb), [slot_id](bool) {
vqt6_trigger_slot(slot_id);
});
}
extern "C" VQT6_API void vqt6_qlineedit_connect_text_changed(void* le, vqt6_slot_callback_t cb, void* user_data) {
if (le == nullptr) return;
int slot_id = vqt6_signal_register(cb, user_data);
if (slot_id < 0) return;
QObject::connect(static_cast<QLineEdit*>(le), &QLineEdit::textChanged,
static_cast<QLineEdit*>(le), [slot_id](const QString&) {
vqt6_trigger_slot(slot_id);
});
}
extern "C" VQT6_API void vqt6_qcombobox_connect_current_index_changed(void* cb_widget, vqt6_slot_callback_t cb, void* user_data) {
if (cb_widget == nullptr) return;
int slot_id = vqt6_signal_register(cb, user_data);
if (slot_id < 0) return;
QObject::connect(static_cast<QComboBox*>(cb_widget), &QComboBox::currentIndexChanged,
static_cast<QComboBox*>(cb_widget), [slot_id](int) {
vqt6_trigger_slot(slot_id);
});
}
extern "C" VQT6_API void vqt6_qslider_connect_value_changed(void* s, vqt6_slot_callback_t cb, void* user_data) {
if (s == nullptr) return;
int slot_id = vqt6_signal_register(cb, user_data);
if (slot_id < 0) return;
QObject::connect(static_cast<QSlider*>(s), &QSlider::valueChanged,
static_cast<QSlider*>(s), [slot_id](int) {
vqt6_trigger_slot(slot_id);
});
}
extern "C" VQT6_API void vqt6_qspinbox_connect_value_changed(void* sb, vqt6_slot_callback_t cb, void* user_data) {
if (sb == nullptr) return;
int slot_id = vqt6_signal_register(cb, user_data);
if (slot_id < 0) return;
QObject::connect(static_cast<QSpinBox*>(sb), &QSpinBox::valueChanged,
static_cast<QSpinBox*>(sb), [slot_id](int) {
vqt6_trigger_slot(slot_id);
});
}
extern "C" VQT6_API void vqt6_qprogressbar_connect_value_changed(void* pb, vqt6_slot_callback_t cb, void* user_data) {
if (pb == nullptr) return;
int slot_id = vqt6_signal_register(cb, user_data);
if (slot_id < 0) return;
QObject::connect(static_cast<QProgressBar*>(pb), &QProgressBar::valueChanged,
static_cast<QProgressBar*>(pb), [slot_id](int) {
vqt6_trigger_slot(slot_id);
});
}
extern "C" VQT6_API void vqt6_qlistwidget_connect_current_row_changed(void* lw, vqt6_slot_callback_t cb, void* user_data) {
if (lw == nullptr) return;
int slot_id = vqt6_signal_register(cb, user_data);
if (slot_id < 0) return;
QObject::connect(static_cast<QListWidget*>(lw), &QListWidget::currentRowChanged,
static_cast<QListWidget*>(lw), [slot_id](int) {
vqt6_trigger_slot(slot_id);
});
}
extern "C" VQT6_API void vqt6_qtabwidget_connect_current_changed(void* tw, vqt6_slot_callback_t cb, void* user_data) {
if (tw == nullptr) return;
int slot_id = vqt6_signal_register(cb, user_data);
if (slot_id < 0) return;
QObject::connect(static_cast<QTabWidget*>(tw), &QTabWidget::currentChanged,
static_cast<QTabWidget*>(tw), [slot_id](int) {
vqt6_trigger_slot(slot_id);
});
}
extern "C" VQT6_API void vqt6_qaction_connect_triggered(void* action, vqt6_slot_callback_t cb, void* user_data) {
if (action == nullptr) return;
int slot_id = vqt6_signal_register(cb, user_data);
if (slot_id < 0) return;
QObject::connect(static_cast<QAction*>(action), &QAction::triggered,
static_cast<QAction*>(action), [slot_id](bool) {
vqt6_trigger_slot(slot_id);
});
}
+22
View File
@@ -49,6 +49,9 @@ VQT6_API void vqt6_qwidget_set_window_title(void* widget, void* title);
VQT6_API void* vqt6_qwidget_window_title(void* widget);
VQT6_API void vqt6_qwidget_set_text(void* widget, void* text);
VQT6_API void* vqt6_qwidget_text(void* widget);
// UTF-8 便捷版:直接传 C 字符串(Viper str),内部转 QString,避免 Viper 端手动创建/释放 QString
VQT6_API void vqt6_qlabel_set_text_utf8(void* label, const char* utf8);
VQT6_API void vqt6_qplaintextedit_append_utf8(void* pte, const char* utf8);
VQT6_API void vqt6_qwidget_set_enabled(void* widget, int enabled);
VQT6_API int vqt6_qwidget_is_enabled(void* widget);
VQT6_API void vqt6_qwidget_set_parent(void* widget, void* parent);
@@ -425,6 +428,25 @@ VQT6_API void* vqt6_qfiledialog_get_save_file_name(void* parent, void* caption,
VQT6_API void* vqt6_qfiledialog_get_existing_directory(void* parent, void* caption, void* dir);
VQT6_API void* vqt6_qfiledialog_get_open_file_names(void* parent, void* caption, void* dir, void* filter);
// ============================================================
// 信号连接(Qt 信号 → Viper 回调 vqt6_slot_callback_t)
// 回调统一签名: void cb(int slot_id, void* user_data)
// 触发时回调里可用 user_data(如控件指针)再查询控件状态
// ============================================================
VQT6_API void vqt6_qpushbutton_connect_clicked(void* btn, vqt6_slot_callback_t cb, void* user_data);
VQT6_API void vqt6_qpushbutton_connect_toggled(void* btn, vqt6_slot_callback_t cb, void* user_data);
VQT6_API void vqt6_qcheckbox_connect_toggled(void* cb_widget, vqt6_slot_callback_t cb, void* user_data);
VQT6_API void vqt6_qcheckbox_connect_state_changed(void* cb_widget, vqt6_slot_callback_t cb, void* user_data);
VQT6_API void vqt6_qradiobutton_connect_toggled(void* rb, vqt6_slot_callback_t cb, void* user_data);
VQT6_API void vqt6_qlineedit_connect_text_changed(void* le, vqt6_slot_callback_t cb, void* user_data);
VQT6_API void vqt6_qcombobox_connect_current_index_changed(void* cb_widget, vqt6_slot_callback_t cb, void* user_data);
VQT6_API void vqt6_qslider_connect_value_changed(void* s, vqt6_slot_callback_t cb, void* user_data);
VQT6_API void vqt6_qspinbox_connect_value_changed(void* sb, vqt6_slot_callback_t cb, void* user_data);
VQT6_API void vqt6_qprogressbar_connect_value_changed(void* pb, vqt6_slot_callback_t cb, void* user_data);
VQT6_API void vqt6_qlistwidget_connect_current_row_changed(void* lw, vqt6_slot_callback_t cb, void* user_data);
VQT6_API void vqt6_qtabwidget_connect_current_changed(void* tw, vqt6_slot_callback_t cb, void* user_data);
VQT6_API void vqt6_qaction_connect_triggered(void* action, vqt6_slot_callback_t cb, void* user_data);
#ifdef __cplusplus
}
#endif
+2
View File
@@ -99,6 +99,8 @@ def vqt6_qtimer_interval(timer: QtTimerPtr) -> t.CInt | t.State: pass
def vqt6_qtimer_is_active(timer: QtTimerPtr) -> t.CInt | t.State: pass
def vqt6_qtimer_set_single_shot(timer: QtTimerPtr, single_shot: t.CInt) -> t.State: pass
def vqt6_qtimer_is_single_shot(timer: QtTimerPtr) -> t.CInt | t.State: pass
# timeout 信号 → 回调(cb 是 Viper 模块级函数,签名 (slot_id: int, user_data: t.CPtr))
def vqt6_qtimer_connect_timeout(timer: QtTimerPtr, cb: bytes, user_data: bytes) -> t.State: pass
# ============================================================
+23
View File
@@ -132,6 +132,8 @@ def vqt6_qlabel_create() -> QtLabelPtr | t.State: pass
def vqt6_qlabel_create_with_text(text: QtStringPtr) -> QtLabelPtr | t.State: pass
def vqt6_qlabel_create_with_text_parent(text: QtStringPtr, parent: QtWidgetPtr) -> QtLabelPtr | t.State: pass
def vqt6_qlabel_set_text(label: QtLabelPtr, text: QtStringPtr) -> t.State: pass
# UTF-8 便捷版:直接传 Viper str,避免手动创建/释放 QString
def vqt6_qlabel_set_text_utf8(label: QtLabelPtr, text: t.CChar | t.CPtr) -> t.State: pass
def vqt6_qlabel_text(label: QtLabelPtr) -> QtStringPtr | t.State: pass
def vqt6_qlabel_set_alignment(label: QtLabelPtr, alignment: t.CInt) -> t.State: pass
def vqt6_qlabel_alignment(label: QtLabelPtr) -> t.CInt | t.State: pass
@@ -203,6 +205,8 @@ def vqt6_qplaintextedit_create_with_parent(parent: QtWidgetPtr) -> QtPlainTextEd
def vqt6_qplaintextedit_to_plain_text(pte: QtPlainTextEditPtr) -> QtStringPtr | t.State: pass
def vqt6_qplaintextedit_set_plain_text(pte: QtPlainTextEditPtr, text: QtStringPtr) -> t.State: pass
def vqt6_qplaintextedit_append(pte: QtPlainTextEditPtr, text: QtStringPtr) -> t.State: pass
# UTF-8 便捷版:直接传 Viper str,避免手动创建/释放 QString
def vqt6_qplaintextedit_append_utf8(pte: QtPlainTextEditPtr, text: t.CChar | t.CPtr) -> t.State: pass
def vqt6_qplaintextedit_clear(pte: QtPlainTextEditPtr) -> t.State: pass
def vqt6_qplaintextedit_set_read_only(pte: QtPlainTextEditPtr, ro: t.CInt) -> t.State: pass
def vqt6_qplaintextedit_line_count(pte: QtPlainTextEditPtr) -> t.CInt | t.State: pass
@@ -476,3 +480,22 @@ def vqt6_qinputdialog_get_text(parent: QtWidgetPtr, title: QtStringPtr, label: Q
def vqt6_qinputdialog_get_int(parent: QtWidgetPtr, title: QtStringPtr, label: QtStringPtr, value: t.CInt, min_val: t.CInt, max_val: t.CInt, step: t.CInt, ok_pressed: t.CInt | t.CPtr) -> t.CInt | t.State: pass
def vqt6_qinputdialog_get_double(parent: QtWidgetPtr, title: QtStringPtr, label: QtStringPtr, value: t.CDouble, min_val: t.CDouble, max_val: t.CDouble, decimals: t.CInt, ok_pressed: t.CInt | t.CPtr) -> t.CDouble | t.State: pass
def vqt6_qinputdialog_get_item(parent: QtWidgetPtr, title: QtStringPtr, label: QtStringPtr, items: QtStringListPtr, current: t.CInt, editable: t.CInt, ok_pressed: t.CInt | t.CPtr) -> QtStringPtr | t.State: pass
# ============================================================
# 信号连接(Qt 信号 → Viper 回调)
# 回调统一签名: def cb(slot_id: int, user_data: t.CPtr)
# ============================================================
def vqt6_qpushbutton_connect_clicked(btn: QtPushButtonPtr, cb: bytes, user_data: bytes) -> t.State: pass
def vqt6_qpushbutton_connect_toggled(btn: QtPushButtonPtr, cb: bytes, user_data: bytes) -> t.State: pass
def vqt6_qcheckbox_connect_toggled(cb_widget: QtCheckBoxPtr, cb: bytes, user_data: bytes) -> t.State: pass
def vqt6_qcheckbox_connect_state_changed(cb_widget: QtCheckBoxPtr, cb: bytes, user_data: bytes) -> t.State: pass
def vqt6_qradiobutton_connect_toggled(rb: QtRadioButtonPtr, cb: bytes, user_data: bytes) -> t.State: pass
def vqt6_qlineedit_connect_text_changed(le: QtLineEditPtr, cb: bytes, user_data: bytes) -> t.State: pass
def vqt6_qcombobox_connect_current_index_changed(cb_widget: QtComboBoxPtr, cb: bytes, user_data: bytes) -> t.State: pass
def vqt6_qslider_connect_value_changed(s: QtSliderPtr, cb: bytes, user_data: bytes) -> t.State: pass
def vqt6_qspinbox_connect_value_changed(sb: QtSpinBoxPtr, cb: bytes, user_data: bytes) -> t.State: pass
def vqt6_qprogressbar_connect_value_changed(pb: QtProgressBarPtr, cb: bytes, user_data: bytes) -> t.State: pass
def vqt6_qlistwidget_connect_current_row_changed(lw: QtListWidgetPtr, cb: bytes, user_data: bytes) -> t.State: pass
def vqt6_qtabwidget_connect_current_changed(tw: QtTabWidgetPtr, cb: bytes, user_data: bytes) -> t.State: pass
def vqt6_qaction_connect_triggered(action: QtActionPtr, cb: bytes, user_data: bytes) -> t.State: pass