Coverage for paintmaskanimator/color_panel_selection.py: 42%
142 statements
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-02 04:50 +0000
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-02 04:50 +0000
1"""Row selection state for the used-color panel.
3Split out of ``color_panel.py`` as a mixin. These methods own which colours are
4selected -- click, ctrl/shift-click and checkbox toggling, the sweep drag over
5the selection column, and the queries (``selected_rgb_set``,
6``_ordered_non_background_rgbs``) the rest of the panel and the main window use.
7"""
8from PySide6.QtCore import Qt
9from PySide6.QtGui import QColor
10from ._color_panel_members import UsedColorPanelMembers
11from .color_panel_widgets import CheckClickArea
12from .logging_setup import get_logger
14log = get_logger(__name__)
17class ColorSelectionMixin(UsedColorPanelMembers):
18 def selected_rgb_set(self):
19 return set(self.selected_rgbs)
21 def select_matching_color(self, color):
22 """スポイト色と完全一致する使用色を、単独の親色として選択する。"""
23 qc = QColor(color)
24 rgb = self._rgb_key(qc)
25 if ( 25 ↛ 33line 25 didn't jump to line 33 because the condition on line 25 was always true
26 not qc.isValid()
27 or rgb == self.background_rgb
28 or rgb not in self.source_buttons
29 ):
30 self._clear_used_color_selection()
31 return False
33 old = set(self.selected_rgbs)
34 changed = self.selected_rgbs != [rgb] or self.parent_rgb != rgb
35 self.selected_rgbs = [rgb]
36 self.parent_rgb = rgb
37 self._selection_anchor_rgb = rgb
38 self._refresh_used_color_styles(old | {rgb})
39 row_widget = self.row_widgets.get(rgb)
40 if row_widget is not None:
41 self.scroll.ensureWidgetVisible(row_widget)
42 if changed:
43 self.selectedColorsChanged.emit({rgb})
44 return True
46 def _sync_selection_check(self, rgb):
47 checkbox = self.selection_checks.get(rgb)
48 if checkbox is None: 48 ↛ 49line 48 didn't jump to line 49 because the condition on line 48 was never true
49 return
50 selected = rgb in self.selected_rgbs
51 if checkbox.isChecked() != selected:
52 checkbox.blockSignals(True)
53 checkbox.setChecked(selected)
54 checkbox.blockSignals(False)
55 else:
56 checkbox.update()
58 def _ordered_non_background_rgbs(self):
59 return [
60 self._rgb_key(color) for color in self.colors
61 if self._rgb_key(color) != self.background_rgb
62 ]
64 def _select_used_color(self, rgb, modifiers=Qt.KeyboardModifier.NoModifier):
65 """クリック=単独選択/Shift=範囲選択/Ctrl=個別トグル。"""
66 if rgb == self.background_rgb: 66 ↛ 67line 66 didn't jump to line 67 because the condition on line 66 was never true
67 return
68 shift = bool(modifiers & Qt.KeyboardModifier.ShiftModifier)
69 ctrl = bool(modifiers & Qt.KeyboardModifier.ControlModifier)
71 if shift and self._selection_anchor_rgb: 71 ↛ 72line 71 didn't jump to line 72 because the condition on line 71 was never true
72 ordered = self._ordered_non_background_rgbs()
73 if rgb in ordered and self._selection_anchor_rgb in ordered:
74 start = ordered.index(self._selection_anchor_rgb)
75 end = ordered.index(rgb)
76 lo, hi = sorted((start, end))
77 old = set(self.selected_rgbs)
78 # 直前選択からクリック色までを範囲としてまとめ選択する。
79 self.selected_rgbs = list(ordered[lo:hi + 1])
80 self.parent_rgb = rgb
81 self._refresh_used_color_styles(old | set(self.selected_rgbs))
82 self.selectedColorsChanged.emit(set(self.selected_rgbs))
83 return
85 if ctrl:
86 # 個別に追加/解除。
87 self._selection_anchor_rgb = rgb
88 self._toggle_used_color_selection(rgb)
89 return
91 # 修飾なしクリックは、その色だけを単独選択する。
92 self._selection_anchor_rgb = rgb
93 self._select_single_used_color(rgb)
95 def _select_single_used_color(self, rgb):
96 if rgb == self.background_rgb: 96 ↛ 97line 96 didn't jump to line 97 because the condition on line 96 was never true
97 return
98 old = set(self.selected_rgbs)
99 self.selected_rgbs = [rgb]
100 self.parent_rgb = rgb
101 self._selection_anchor_rgb = rgb
102 self._refresh_used_color_styles(old | {rgb})
103 self.selectedColorsChanged.emit({rgb})
105 # ------------------------------------------------------------------
106 # 「選択」チェックボックス(クリック連動・上下スイープ一括選択)
107 # ------------------------------------------------------------------
108 def _apply_selection_state(self, rgb, selected):
109 """1色分の選択状態を更新する(emitは呼び出し側で行う)。"""
110 if rgb == self.background_rgb:
111 return
112 was = rgb in self.selected_rgbs
113 if bool(selected) == was:
114 return
115 if selected:
116 self.selected_rgbs.append(rgb)
117 else:
118 self.selected_rgbs.remove(rgb)
119 self.parent_rgb = self.selected_rgbs[-1] if self.selected_rgbs else None
120 self._selection_anchor_rgb = rgb
121 self._set_source_button_style(rgb)
122 self._selection_sweep_changed = True
124 def _on_selection_check_toggled(self, rgb, checked):
125 if self._selection_sweep_active:
126 return
127 self._selection_sweep_changed = False
128 self._apply_selection_state(rgb, checked)
129 if self._selection_sweep_changed:
130 self.selectedColorsChanged.emit(set(self.selected_rgbs))
131 self._selection_sweep_changed = False
133 def _selection_check_rgb_at_global(self, global_position):
134 for rgb, checkbox in self.selection_checks.items():
135 area = checkbox.parentWidget()
136 target = area if isinstance(area, CheckClickArea) else checkbox
137 if target.rect().contains(target.mapFromGlobal(global_position)):
138 return rgb
139 return None
141 def _begin_selection_sweep(self, source_rgb, checked):
142 self._selection_sweep_active = True
143 self._selection_sweep_state = bool(checked)
144 self._selection_sweep_touched = {source_rgb}
145 self._selection_sweep_changed = False
146 self._apply_selection_state(source_rgb, checked)
148 def _move_selection_sweep(self, global_position):
149 if not self._selection_sweep_active:
150 return
151 rgb = self._selection_check_rgb_at_global(global_position)
152 if (
153 rgb is None
154 or rgb in self._selection_sweep_touched
155 or rgb == self.background_rgb
156 ):
157 return
158 self._selection_sweep_touched.add(rgb)
159 self._apply_selection_state(rgb, self._selection_sweep_state)
161 def _end_selection_sweep(self):
162 if not self._selection_sweep_active:
163 return
164 self._selection_sweep_active = False
165 if self._selection_sweep_changed:
166 self.selectedColorsChanged.emit(set(self.selected_rgbs))
167 self._selection_sweep_touched.clear()
168 self._selection_sweep_changed = False
170 # ------------------------------------------------------------------
171 # ドラッグ&ドロップによる並べ替え・親子付け(非破壊プレビュー)
172 # ------------------------------------------------------------------
173 def _toggle_used_color_selection(self, rgb):
174 if rgb == self.background_rgb: 174 ↛ 175line 174 didn't jump to line 175 because the condition on line 174 was never true
175 return
176 affected = set(self.selected_rgbs)
177 if rgb in self.selected_rgbs: 177 ↛ 178line 177 didn't jump to line 178 because the condition on line 177 was never true
178 self.selected_rgbs.remove(rgb)
179 else:
180 self.selected_rgbs.append(rgb)
181 self.parent_rgb = self.selected_rgbs[-1] if self.selected_rgbs else None
182 affected.update(self.selected_rgbs)
183 for key in affected:
184 self._set_source_button_style(key)
185 self.selectedColorsChanged.emit(set(self.selected_rgbs))
187 def _clear_used_color_selection(self):
188 old = set(self.selected_rgbs)
189 changed = bool(old) or self.parent_rgb is not None
190 self.selected_rgbs = []
191 self.parent_rgb = None
192 self._selection_anchor_rgb = None
193 self._refresh_used_color_styles(old)
194 if changed: 194 ↛ 195line 194 didn't jump to line 195 because the condition on line 194 was never true
195 self.selectedColorsChanged.emit(set())