11"""
2- 列显示设置对话框
2+ 列显示设置对话框(支持上下移动排序)
33"""
44
55from __future__ import annotations
66
7- from PyQt5 .QtCore import QCoreApplication
7+ from PyQt5 .QtCore import QCoreApplication , Qt
8+ from PyQt5 .QtGui import QKeyEvent
89from 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
0 commit comments