Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/plugin_manager/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,13 @@
"""

import argparse
import os
import sys

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


def main() -> int:
parser = argparse.ArgumentParser(description="Solvable-Minesweeper 插件管理器")
Expand Down
5 changes: 5 additions & 0 deletions src/plugin_manager/_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@
"""

import argparse
import os
import sys

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


def main() -> int:
parser = argparse.ArgumentParser(description="Solvable-Minesweeper 插件管理器")
Expand Down
4 changes: 4 additions & 0 deletions src/plugin_manager/app_paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,14 @@ def get_env_for_subprocess(env: dict | None = None) -> dict:
为启动插件管理器子进程构建环境变量

确保 PYTHONPATH 包含正确的路径,使子进程中的动态导入正常工作。
移除 QT_FONT_DPI 以让插件管理器使用独立的高分屏设置。
"""
if env is None:
env = dict(os.environ)

# 移除主程序的 DPI 设置,让插件管理器使用自己的高分屏配置
env.pop("QT_FONT_DPI", None)

bundle = str(get_bundle_dir())
exec_dir = str(get_executable_dir())

Expand Down
192 changes: 155 additions & 37 deletions src/plugins/history/columns_dialog.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,68 @@
"""
列显示设置对话框
列显示设置对话框(支持上下移动排序)
"""

from __future__ import annotations

from PyQt5.QtCore import QCoreApplication
from PyQt5.QtCore import QCoreApplication, Qt
from PyQt5.QtGui import QKeyEvent
from PyQt5.QtWidgets import (
QWidget,
QVBoxLayout,
QHBoxLayout,
QPushButton,
QScrollArea,
QCheckBox,
QDialog,
QListWidget,
QListWidgetItem,
QDialogButtonBox,
QMenu,
QWidget,
)

from shared_types.widgets import ConfirmDialog

_translate = QCoreApplication.translate


class ColumnsDialog(QDialog):
class ColumnListWidget(QListWidget):
"""自定义列表控件,处理移动快捷键"""

def __init__(self, parent=None):
super().__init__(parent)
self._move_up_callback = None
self._move_down_callback = None

def set_move_callbacks(self, move_up, move_down):
"""设置移动回调函数"""
self._move_up_callback = move_up
self._move_down_callback = move_down

def keyPressEvent(self, event: QKeyEvent):
"""键盘事件:Ctrl+Shift+↑↓ 移动选中项"""
if (event.modifiers() & Qt.ControlModifier) and (event.modifiers() & Qt.ShiftModifier): # type: ignore
if event.key() == Qt.Key_Up and self._move_up_callback:
self._move_up_callback()
return
elif event.key() == Qt.Key_Down and self._move_down_callback:
self._move_down_callback()
return
super().keyPressEvent(event)


class ColumnsDialog(ConfirmDialog):
"""列显示设置对话框"""

def __init__(self, headers: list[str], show_fields: list[str], parent=None):
super().__init__(parent)
self.setWindowTitle(_translate("Form", "列设置"))
self.resize(300, 500)
self._headers = headers
self._show_fields = show_fields
super().__init__(
parent,
title=_translate("Form", "列设置(右键/Ctrl+Shift+↑↓ 排序)"),
)
self.resize(300, 500)

layout = QVBoxLayout(self)
def _create_content(self):
widget = QWidget()
layout = QVBoxLayout(widget)
layout.setContentsMargins(0, 0, 0, 0)

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

# 勾选列表
scroll = QScrollArea(self)
scroll.setWidgetResizable(True)
scroll_widget = QWidget()
scroll_layout = QVBoxLayout(scroll_widget)
scroll_layout.setContentsMargins(4, 4, 4, 4)
# 列表(支持多选)
self.list_widget = ColumnListWidget()
self.list_widget.setSelectionMode(QListWidget.ExtendedSelection)
self.list_widget.setContextMenuPolicy(Qt.CustomContextMenu)
self.list_widget.customContextMenuRequested.connect(
self._show_context_menu)
self.list_widget.set_move_callbacks(self._move_up, self._move_down)

self.checks: dict[str, QCheckBox] = {}
for field in headers:
cb = QCheckBox(field)
cb.setChecked(field in show_fields)
scroll_layout.addWidget(cb)
self.checks[field] = cb
# 初始化列表
self._init_list()

scroll_layout.addStretch()
scroll.setWidget(scroll_widget)
layout.addWidget(scroll)

# 确定按钮
self.ok_button = QPushButton(_translate("Form", "确定"))
layout.addWidget(self.ok_button)
layout.addWidget(self.list_widget)

self.select_all_btn.clicked.connect(self._select_all)
self.deselect_all_btn.clicked.connect(self._deselect_all)
self.ok_button.clicked.connect(self.accept)

return widget

def _init_list(self):
"""初始化列表内容"""
self.list_widget.clear()

# 按 show_fields 顺序排列
ordered_fields = [f for f in self._show_fields if f in self._headers]
remaining_fields = [
f for f in self._headers if f not in self._show_fields]

for field in ordered_fields + remaining_fields:
item = QListWidgetItem(field)
item.setCheckState(
Qt.Checked if field in self._show_fields else Qt.Unchecked)
self.list_widget.addItem(item)

def set_show_fields(self, show_fields: list[str]):
"""设置当前显示的字段列表(下次打开时使用)"""
self._show_fields = show_fields

def item(self, row: int) -> QListWidgetItem:
return self.list_widget.item(row) # type: ignore

def _select_all(self):
for cb in self.checks.values():
cb.setChecked(True)
for i in range(self.list_widget.count()):
self.item(i).setCheckState(Qt.Checked)

def _deselect_all(self):
for cb in self.checks.values():
cb.setChecked(False)
for i in range(self.list_widget.count()):
self.item(i).setCheckState(Qt.Unchecked)

def _show_context_menu(self, pos):
"""显示右键菜单"""
menu = QMenu(self)
menu.addAction(_translate("Form", "上移 (Ctrl+Shift+↑)"), self._move_up)
menu.addAction(_translate(
"Form", "下移 (Ctrl+Shift+↓)"), self._move_down)
menu.exec_(self.list_widget.mapToGlobal(pos))

def _move_up(self):
"""上移选中的项目"""
selected = self.list_widget.selectedItems()
if not selected:
return

# 按行号升序排列
for item in sorted(selected, key=lambda x: self.list_widget.row(x)):
row = self.list_widget.row(item)
if row > 0:
# 检查上一行是否也在选中列表中
prev_item = self.list_widget.item(row - 1)
if prev_item not in selected:
self.list_widget.takeItem(row)
self.list_widget.insertItem(row - 1, item)
item.setSelected(True)

# 滚动到选中的第一行
self._scroll_to_first_selected()

def _move_down(self):
"""下移选中的项目"""
selected = self.list_widget.selectedItems()
if not selected:
return

count = self.list_widget.count()
# 按行号降序排列(从下往上处理)
for item in sorted(selected, key=lambda x: self.list_widget.row(x), reverse=True):
row = self.list_widget.row(item)
if row < count - 1:
# 检查下一行是否也在选中列表中
next_item = self.list_widget.item(row + 1)
if next_item not in selected:
self.list_widget.takeItem(row)
self.list_widget.insertItem(row + 1, item)
item.setSelected(True)

# 滚动到选中的第一行
self._scroll_to_first_selected()

def _scroll_to_first_selected(self):
"""滚动到选中的第一行"""
selected = self.list_widget.selectedItems()
if selected:
first_row = min(self.list_widget.row(item) for item in selected)
self.list_widget.scrollToItem(self.list_widget.item(first_row))

def get_show_fields(self) -> list[str]:
"""获取当前勾选的字段集合"""
return [field for field, cb in self.checks.items() if cb.isChecked()]
"""获取当前勾选的字段列表(按显示顺序)"""
result = []
for i in range(self.list_widget.count()):
item = self.item(i)
if item.checkState() == Qt.Checked:
result.append(item.text())
return result

def set_field_checked(self, field: str, checked: bool):
"""设置指定字段的勾选状态"""
for i in range(self.list_widget.count()):
item = self.item(i)
if item.text() == field:
item.setCheckState(Qt.Checked if checked else Qt.Unchecked)
break
17 changes: 11 additions & 6 deletions src/plugins/history/filter_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,29 @@
QMessageBox,
QSizePolicy,
QHeaderView,
QDialog,
QWidget,
)

from shared_types.widgets import ConfirmDialog
from .delegates import ComboBoxDelegate, EditableComboBoxDelegate, FilterValueDelegate
from .models import HistoryData, LogicSymbol, CompareSymbol
from .table_views import AutoEditTableView, FilterModel

_translate = QCoreApplication.translate


class FilterDialog(QDialog):
class FilterDialog(ConfirmDialog):
"""过滤条件对话框"""

def __init__(self, float_decimals: int = 2, parent=None):
super().__init__(parent)
self.setWindowTitle(_translate("Form", "过滤条件"))
self.resize(700, 300)
self._float_decimals = float_decimals
super().__init__(parent, title=_translate("Form", "过滤条件"))
self.resize(700, 300)

layout = QVBoxLayout(self)
def _create_content(self):
widget = QWidget()
layout = QVBoxLayout(widget)
layout.setContentsMargins(0, 0, 0, 0)

self.table = AutoEditTableView()
self.table.setModel(FilterModel(self))
Expand All @@ -55,6 +58,8 @@ def __init__(self, float_decimals: int = 2, parent=None):
self._setup_delegates()
self._connect_field_change_signal()

return widget

def _connect_field_change_signal(self):
"""当字段列改变时,更新值列的默认值"""
self.table.model().dataChanged.connect(self._on_field_changed)
Expand Down
Loading
Loading