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
69 changes: 69 additions & 0 deletions src/script_chainer/config/script_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -451,15 +451,84 @@ def delete_one(self, index: int) -> None:
self.init_idx()
self.save()

def duplicate_one(self, index: int) -> ScriptConfig | None:
"""复制一个配置,插入到原配置(及其挂靠脚本)之后。

内部管理的 Python 脚本会同时复制一份 .py 文件,外部脚本共用同一文件。
副本不继承挂靠关系。

Args:
index: 要复制的配置下标。

Returns:
新创建的 ScriptConfig,下标非法时返回 None。
"""
if index < 0 or index >= len(self.script_list):
return None
new_config = self.script_list[index].copy()
new_config.attach_direction = AttachDirection.NONE

if (
new_config.script_type == ScriptType.PYTHON
and new_config.script_path
and self._is_managed_script(new_config.script_path)
):
code = self.get_python_script_content(index)
num = self._next_python_script_number()
p = self._get_python_scripts_dir() / f'{self.module_name}_{num}.py'
p.write_text(code, encoding='utf-8')
new_config.script_path = str(p)

# 跳过挂靠在原配置上的脚本,避免副本插进挂靠组中间
pos = index + 1
while self.is_attached_to_prev(pos):
pos += 1
self.script_list.insert(pos, new_config)
self.init_idx()
self.save()
return new_config

def move_one(self, index: int, new_index: int) -> None:
"""移动一个配置到新位置(用于上移、下移、置顶)。

Args:
index: 当前下标。
new_index: 目标下标。
"""
n = len(self.script_list)
if index < 0 or index >= n or new_index < 0 or new_index >= n or index == new_index:
return
new_order = list(self.script_list)
new_order.insert(new_index, new_order.pop(index))
self.reorder(new_order)

def reorder(self, new_order: list[ScriptConfig]) -> None:
"""按新顺序重排脚本列表(用于拖拽排序)。

挂靠目标因重排而改变的 Python 脚本会取消挂靠,防止"漂移"到新邻居。

Args:
new_order: 新顺序的脚本列表。
"""
if len(new_order) != len(self.script_list):
return
old_target_of = {
id(config): target
for config, target in zip(self.script_list, self.compute_attach_targets(), strict=True)
}
self.script_list = list(new_order)
# 取消挂靠后该脚本会成为新的挂靠目标,可能导致其他脚本的目标连带变化,需反复检查直至稳定
changed = True
while changed:
changed = False
for config, target in zip(self.script_list, self.compute_attach_targets(), strict=True):
if (
config.script_type == ScriptType.PYTHON
and config.attach_direction != AttachDirection.NONE
and old_target_of.get(id(config)) is not target
):
config.attach_direction = AttachDirection.NONE
changed = True
self.init_idx()
self.save()

Expand Down
36 changes: 34 additions & 2 deletions src/script_chainer/gui/page/script_setting_cards.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,17 @@
from PySide6.QtCore import Signal
from PySide6.QtGui import QIcon
from PySide6.QtWidgets import QHBoxLayout
from qfluentwidgets import Dialog, FluentIcon, SwitchButton, TransparentToolButton
from qfluentwidgets import (
Action,
Dialog,
FluentIcon,
RoundMenu,
SwitchButton,
TransparentToolButton,
)

from one_dragon.utils.os_utils import reveal_in_file_manager
from one_dragon_qt.widgets.draggable_list import DraggableListItem
from one_dragon_qt.widgets.draggable_list import DraggableList, DraggableListItem
from one_dragon_qt.widgets.setting_card.code_editor_setting_card import (
PythonCodeEditorDialog,
)
Expand Down Expand Up @@ -35,6 +42,9 @@ class ScriptCardMixin:
config: ScriptConfig
value_changed: Signal
deleted: Signal
copy_requested: Signal
move_requested: Signal
index: int

def _setup_common_widgets(self) -> None:
self.enable_switch = SwitchButton()
Expand Down Expand Up @@ -75,6 +85,24 @@ def leaveEvent(self, event) -> None:
super().leaveEvent(event)
self._rename_btn.setIcon(QIcon())

def contextMenuEvent(self, event) -> None:
"""右键菜单:复制、置顶、上移、下移。"""
parent = self.parentWidget()
count = parent.get_item_count() if isinstance(parent, DraggableList) else 0
idx = self.index

menu = RoundMenu(parent=self)
menu.addAction(Action(FluentIcon.COPY, '复制', triggered=lambda: self.copy_requested.emit(idx)))
menu.addSeparator()
top_action = Action(FluentIcon.PIN, '置顶', triggered=lambda: self.move_requested.emit(idx, 0))
up_action = Action(FluentIcon.UP, '上移', triggered=lambda: self.move_requested.emit(idx, idx - 1))
down_action = Action(FluentIcon.DOWN, '下移', triggered=lambda: self.move_requested.emit(idx, idx + 1))
top_action.setEnabled(idx > 0)
up_action.setEnabled(idx > 0)
down_action.setEnabled(idx < count - 1)
menu.addActions([top_action, up_action, down_action])
menu.exec(event.globalPos())

def on_enable_changed(self, checked: bool) -> None:
self.config.enabled = checked
self.value_changed.emit(self.config)
Expand Down Expand Up @@ -108,6 +136,8 @@ class ScriptSettingCard(ScriptCardMixin, DraggableListItem):

value_changed = Signal(ScriptConfig)
deleted = Signal(int)
copy_requested = Signal(int)
move_requested = Signal(int, int)
edit_requested = Signal(object)

def __init__(self, config: ScriptConfig, chain_name: str, index: int = 0, parent=None,
Expand Down Expand Up @@ -194,6 +224,8 @@ class PythonScriptSettingCard(ScriptCardMixin, DraggableListItem):

value_changed = Signal(ScriptConfig)
deleted = Signal(int)
copy_requested = Signal(int)
move_requested = Signal(int, int)
attach_changed = Signal()

def __init__(self, config: ScriptConfig, chain_config: ScriptChainConfig,
Expand Down
44 changes: 18 additions & 26 deletions src/script_chainer/gui/page/script_setting_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,8 @@ def update_chain_display(self) -> None:

card.value_changed.connect(self.script_config_changed)
card.deleted.connect(self.script_config_deleted)
card.copy_requested.connect(self.script_config_copied)
card.move_requested.connect(self.script_config_moved)

self._update_attach_margins()

Expand Down Expand Up @@ -306,15 +308,6 @@ def on_order_changed(self, new_data_list: list) -> None:
if self.chosen_config is None:
return

old_target_of = {
id(config): target
for config, target in zip(
self.chosen_config.script_list,
self.chosen_config.compute_attach_targets(),
strict=False,
)
}

# 更新卡片列表的顺序和索引
new_card_list: list[DraggableListItem] = []
for data in new_data_list:
Expand All @@ -325,25 +318,8 @@ def on_order_changed(self, new_data_list: list) -> None:
self.script_card_list = new_card_list

self.chosen_config.reorder(new_data_list)
new_target_of = {
id(config): target
for config, target in zip(
self.chosen_config.script_list,
self.chosen_config.compute_attach_targets(),
strict=False,
)
}

for idx, card in enumerate(self.script_card_list):
config = card.data
if isinstance(config, ScriptConfig) and config.script_type == ScriptType.PYTHON:
old_target = old_target_of.get(id(config))
new_target = new_target_of.get(id(config))
if old_target is not new_target:
config.attach_direction = AttachDirection.NONE
card.data.idx = idx
card.update_item(card.data, idx)
self.chosen_config.save()
self._update_attach_margins()

def _update_attach_margins(self) -> None:
Expand Down Expand Up @@ -391,6 +367,22 @@ def script_config_deleted(self, idx: int) -> None:
self.chosen_config.delete_one(idx)
self.update_chain_display()

def script_config_copied(self, idx: int) -> None:
"""复制脚本配置"""
if self.chosen_config is None:
return

self.chosen_config.duplicate_one(idx)
self.update_chain_display()

def script_config_moved(self, idx: int, new_idx: int) -> None:
"""移动脚本配置(上移、下移、置顶)"""
if self.chosen_config is None:
return

self.chosen_config.move_one(idx, new_idx)
self.update_chain_display()


class ScriptSettingInterface(BaseInterface):

Expand Down