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

1"""Row selection state for the used-color panel. 

2 

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 

13 

14log = get_logger(__name__) 

15 

16 

17class ColorSelectionMixin(UsedColorPanelMembers): 

18 def selected_rgb_set(self): 

19 return set(self.selected_rgbs) 

20 

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 

32 

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 

45 

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() 

57 

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 ] 

63 

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) 

70 

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 

84 

85 if ctrl: 

86 # 個別に追加/解除。 

87 self._selection_anchor_rgb = rgb 

88 self._toggle_used_color_selection(rgb) 

89 return 

90 

91 # 修飾なしクリックは、その色だけを単独選択する。 

92 self._selection_anchor_rgb = rgb 

93 self._select_single_used_color(rgb) 

94 

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}) 

104 

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 

123 

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 

132 

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 

140 

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) 

147 

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) 

160 

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 

169 

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)) 

186 

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())