Skip to content

Commit ff967fb

Browse files
authored
Merge pull request #97 from ljzloser/master
支持列排序并重构对话框为按需创建,增加历史服务的原始sql查询接口
2 parents 68288fc + 3782c14 commit ff967fb

12 files changed

Lines changed: 623 additions & 134 deletions

File tree

src/plugin_manager/__main__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,13 @@
1212
"""
1313

1414
import argparse
15+
import os
1516
import sys
1617

18+
# 高分屏支持:必须在导入 PyQt 之前设置
19+
os.environ.setdefault("QT_ENABLE_HIGHDPI_SCALING", "1")
20+
os.environ.setdefault("QT_SCALE_FACTOR_ROUNDING_POLICY", "PassThrough")
21+
1722

1823
def main() -> int:
1924
parser = argparse.ArgumentParser(description="Solvable-Minesweeper 插件管理器")

src/plugin_manager/_run.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,13 @@
77
"""
88

99
import argparse
10+
import os
1011
import sys
1112

13+
# 高分屏支持:必须在导入 PyQt 之前设置
14+
os.environ.setdefault("QT_ENABLE_HIGHDPI_SCALING", "1")
15+
os.environ.setdefault("QT_SCALE_FACTOR_ROUNDING_POLICY", "PassThrough")
16+
1217

1318
def main() -> int:
1419
parser = argparse.ArgumentParser(description="Solvable-Minesweeper 插件管理器")

src/plugin_manager/app_paths.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,10 +161,14 @@ def get_env_for_subprocess(env: dict | None = None) -> dict:
161161
为启动插件管理器子进程构建环境变量
162162
163163
确保 PYTHONPATH 包含正确的路径,使子进程中的动态导入正常工作。
164+
移除 QT_FONT_DPI 以让插件管理器使用独立的高分屏设置。
164165
"""
165166
if env is None:
166167
env = dict(os.environ)
167168

169+
# 移除主程序的 DPI 设置,让插件管理器使用自己的高分屏配置
170+
env.pop("QT_FONT_DPI", None)
171+
168172
bundle = str(get_bundle_dir())
169173
exec_dir = str(get_executable_dir())
170174

Lines changed: 155 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,68 @@
11
"""
2-
列显示设置对话框
2+
列显示设置对话框(支持上下移动排序)
33
"""
44

55
from __future__ import annotations
66

7-
from PyQt5.QtCore import QCoreApplication
7+
from PyQt5.QtCore import QCoreApplication, Qt
8+
from PyQt5.QtGui import QKeyEvent
89
from PyQt5.QtWidgets import (
9-
QWidget,
1010
QVBoxLayout,
1111
QHBoxLayout,
1212
QPushButton,
13-
QScrollArea,
14-
QCheckBox,
15-
QDialog,
13+
QListWidget,
14+
QListWidgetItem,
15+
QDialogButtonBox,
16+
QMenu,
17+
QWidget,
1618
)
1719

20+
from shared_types.widgets import ConfirmDialog
21+
1822
_translate = QCoreApplication.translate
1923

2024

21-
class ColumnsDialog(QDialog):
25+
class ColumnListWidget(QListWidget):
26+
"""自定义列表控件,处理移动快捷键"""
27+
28+
def __init__(self, parent=None):
29+
super().__init__(parent)
30+
self._move_up_callback = None
31+
self._move_down_callback = None
32+
33+
def set_move_callbacks(self, move_up, move_down):
34+
"""设置移动回调函数"""
35+
self._move_up_callback = move_up
36+
self._move_down_callback = move_down
37+
38+
def keyPressEvent(self, event: QKeyEvent):
39+
"""键盘事件:Ctrl+Shift+↑↓ 移动选中项"""
40+
if (event.modifiers() & Qt.ControlModifier) and (event.modifiers() & Qt.ShiftModifier): # type: ignore
41+
if event.key() == Qt.Key_Up and self._move_up_callback:
42+
self._move_up_callback()
43+
return
44+
elif event.key() == Qt.Key_Down and self._move_down_callback:
45+
self._move_down_callback()
46+
return
47+
super().keyPressEvent(event)
48+
49+
50+
class ColumnsDialog(ConfirmDialog):
2251
"""列显示设置对话框"""
2352

2453
def __init__(self, headers: list[str], show_fields: list[str], parent=None):
25-
super().__init__(parent)
26-
self.setWindowTitle(_translate("Form", "列设置"))
27-
self.resize(300, 500)
2854
self._headers = headers
55+
self._show_fields = show_fields
56+
super().__init__(
57+
parent,
58+
title=_translate("Form", "列设置(右键/Ctrl+Shift+↑↓ 排序)"),
59+
)
60+
self.resize(300, 500)
2961

30-
layout = QVBoxLayout(self)
62+
def _create_content(self):
63+
widget = QWidget()
64+
layout = QVBoxLayout(widget)
65+
layout.setContentsMargins(0, 0, 0, 0)
3166

3267
# 全选/取消全选
3368
select_layout = QHBoxLayout()
@@ -37,40 +72,123 @@ def __init__(self, headers: list[str], show_fields: list[str], parent=None):
3772
select_layout.addWidget(self.deselect_all_btn)
3873
layout.addLayout(select_layout)
3974

40-
# 勾选列表
41-
scroll = QScrollArea(self)
42-
scroll.setWidgetResizable(True)
43-
scroll_widget = QWidget()
44-
scroll_layout = QVBoxLayout(scroll_widget)
45-
scroll_layout.setContentsMargins(4, 4, 4, 4)
75+
# 列表(支持多选)
76+
self.list_widget = ColumnListWidget()
77+
self.list_widget.setSelectionMode(QListWidget.ExtendedSelection)
78+
self.list_widget.setContextMenuPolicy(Qt.CustomContextMenu)
79+
self.list_widget.customContextMenuRequested.connect(
80+
self._show_context_menu)
81+
self.list_widget.set_move_callbacks(self._move_up, self._move_down)
4682

47-
self.checks: dict[str, QCheckBox] = {}
48-
for field in headers:
49-
cb = QCheckBox(field)
50-
cb.setChecked(field in show_fields)
51-
scroll_layout.addWidget(cb)
52-
self.checks[field] = cb
83+
# 初始化列表
84+
self._init_list()
5385

54-
scroll_layout.addStretch()
55-
scroll.setWidget(scroll_widget)
56-
layout.addWidget(scroll)
57-
58-
# 确定按钮
59-
self.ok_button = QPushButton(_translate("Form", "确定"))
60-
layout.addWidget(self.ok_button)
86+
layout.addWidget(self.list_widget)
6187

6288
self.select_all_btn.clicked.connect(self._select_all)
6389
self.deselect_all_btn.clicked.connect(self._deselect_all)
64-
self.ok_button.clicked.connect(self.accept)
90+
91+
return widget
92+
93+
def _init_list(self):
94+
"""初始化列表内容"""
95+
self.list_widget.clear()
96+
97+
# 按 show_fields 顺序排列
98+
ordered_fields = [f for f in self._show_fields if f in self._headers]
99+
remaining_fields = [
100+
f for f in self._headers if f not in self._show_fields]
101+
102+
for field in ordered_fields + remaining_fields:
103+
item = QListWidgetItem(field)
104+
item.setCheckState(
105+
Qt.Checked if field in self._show_fields else Qt.Unchecked)
106+
self.list_widget.addItem(item)
107+
108+
def set_show_fields(self, show_fields: list[str]):
109+
"""设置当前显示的字段列表(下次打开时使用)"""
110+
self._show_fields = show_fields
111+
112+
def item(self, row: int) -> QListWidgetItem:
113+
return self.list_widget.item(row) # type: ignore
65114

66115
def _select_all(self):
67-
for cb in self.checks.values():
68-
cb.setChecked(True)
116+
for i in range(self.list_widget.count()):
117+
self.item(i).setCheckState(Qt.Checked)
69118

70119
def _deselect_all(self):
71-
for cb in self.checks.values():
72-
cb.setChecked(False)
120+
for i in range(self.list_widget.count()):
121+
self.item(i).setCheckState(Qt.Unchecked)
122+
123+
def _show_context_menu(self, pos):
124+
"""显示右键菜单"""
125+
menu = QMenu(self)
126+
menu.addAction(_translate("Form", "上移 (Ctrl+Shift+↑)"), self._move_up)
127+
menu.addAction(_translate(
128+
"Form", "下移 (Ctrl+Shift+↓)"), self._move_down)
129+
menu.exec_(self.list_widget.mapToGlobal(pos))
130+
131+
def _move_up(self):
132+
"""上移选中的项目"""
133+
selected = self.list_widget.selectedItems()
134+
if not selected:
135+
return
136+
137+
# 按行号升序排列
138+
for item in sorted(selected, key=lambda x: self.list_widget.row(x)):
139+
row = self.list_widget.row(item)
140+
if row > 0:
141+
# 检查上一行是否也在选中列表中
142+
prev_item = self.list_widget.item(row - 1)
143+
if prev_item not in selected:
144+
self.list_widget.takeItem(row)
145+
self.list_widget.insertItem(row - 1, item)
146+
item.setSelected(True)
147+
148+
# 滚动到选中的第一行
149+
self._scroll_to_first_selected()
150+
151+
def _move_down(self):
152+
"""下移选中的项目"""
153+
selected = self.list_widget.selectedItems()
154+
if not selected:
155+
return
156+
157+
count = self.list_widget.count()
158+
# 按行号降序排列(从下往上处理)
159+
for item in sorted(selected, key=lambda x: self.list_widget.row(x), reverse=True):
160+
row = self.list_widget.row(item)
161+
if row < count - 1:
162+
# 检查下一行是否也在选中列表中
163+
next_item = self.list_widget.item(row + 1)
164+
if next_item not in selected:
165+
self.list_widget.takeItem(row)
166+
self.list_widget.insertItem(row + 1, item)
167+
item.setSelected(True)
168+
169+
# 滚动到选中的第一行
170+
self._scroll_to_first_selected()
171+
172+
def _scroll_to_first_selected(self):
173+
"""滚动到选中的第一行"""
174+
selected = self.list_widget.selectedItems()
175+
if selected:
176+
first_row = min(self.list_widget.row(item) for item in selected)
177+
self.list_widget.scrollToItem(self.list_widget.item(first_row))
73178

74179
def get_show_fields(self) -> list[str]:
75-
"""获取当前勾选的字段集合"""
76-
return [field for field, cb in self.checks.items() if cb.isChecked()]
180+
"""获取当前勾选的字段列表(按显示顺序)"""
181+
result = []
182+
for i in range(self.list_widget.count()):
183+
item = self.item(i)
184+
if item.checkState() == Qt.Checked:
185+
result.append(item.text())
186+
return result
187+
188+
def set_field_checked(self, field: str, checked: bool):
189+
"""设置指定字段的勾选状态"""
190+
for i in range(self.list_widget.count()):
191+
item = self.item(i)
192+
if item.text() == field:
193+
item.setCheckState(Qt.Checked if checked else Qt.Unchecked)
194+
break

src/plugins/history/filter_dialog.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,29 @@
1515
QMessageBox,
1616
QSizePolicy,
1717
QHeaderView,
18-
QDialog,
18+
QWidget,
1919
)
2020

21+
from shared_types.widgets import ConfirmDialog
2122
from .delegates import ComboBoxDelegate, EditableComboBoxDelegate, FilterValueDelegate
2223
from .models import HistoryData, LogicSymbol, CompareSymbol
2324
from .table_views import AutoEditTableView, FilterModel
2425

2526
_translate = QCoreApplication.translate
2627

2728

28-
class FilterDialog(QDialog):
29+
class FilterDialog(ConfirmDialog):
2930
"""过滤条件对话框"""
3031

3132
def __init__(self, float_decimals: int = 2, parent=None):
32-
super().__init__(parent)
33-
self.setWindowTitle(_translate("Form", "过滤条件"))
34-
self.resize(700, 300)
3533
self._float_decimals = float_decimals
34+
super().__init__(parent, title=_translate("Form", "过滤条件"))
35+
self.resize(700, 300)
3636

37-
layout = QVBoxLayout(self)
37+
def _create_content(self):
38+
widget = QWidget()
39+
layout = QVBoxLayout(widget)
40+
layout.setContentsMargins(0, 0, 0, 0)
3841

3942
self.table = AutoEditTableView()
4043
self.table.setModel(FilterModel(self))
@@ -55,6 +58,8 @@ def __init__(self, float_decimals: int = 2, parent=None):
5558
self._setup_delegates()
5659
self._connect_field_change_signal()
5760

61+
return widget
62+
5863
def _connect_field_change_signal(self):
5964
"""当字段列改变时,更新值列的默认值"""
6065
self.table.model().dataChanged.connect(self._on_field_changed)

0 commit comments

Comments
 (0)