Coverage for paintmaskanimator/color_panel_mask.py: 17%

119 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-11-02 04:50 +0000

1"""Mask-colour checkboxes for the used-color panel. 

2 

3Split out of ``color_panel.py`` as a mixin. A masked colour is one the transform 

4tools treat as protected; these methods toggle that state for one colour or all 

5of them, run the press-and-sweep drag that toggles a run of checkboxes in one 

6gesture, and provide the mask context menu. 

7""" 

8from PySide6.QtWidgets import QMenu 

9from .i18n import tr 

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 MaskColorsMixin(UsedColorPanelMembers): 

18 def all_masks_enabled(self): 

19 keys = set(self.mask_checks) 

20 return bool(keys) and keys.issubset(self.mask_rgbs) 

21 

22 def _emit_mask_state(self): 

23 self._mask_all_mode = self.all_masks_enabled() 

24 self.maskColorsChanged.emit(set(self.mask_rgbs)) 

25 

26 def _set_mask_color(self, rgb, checked): 

27 if self._mask_sweep_active: 

28 if checked: 

29 self.mask_rgbs.add(rgb) 

30 else: 

31 self.mask_rgbs.discard(rgb) 

32 self._emit_mask_state() 

33 return 

34 with self._history_edit(tr("マスクの切り替え")): 

35 if checked: 

36 self.mask_rgbs.add(rgb) 

37 else: 

38 self.mask_rgbs.discard(rgb) 

39 self._emit_mask_state() 

40 

41 def _set_mask_checkbox_without_signal(self, rgb, checked): 

42 checkbox = self.mask_checks.get(rgb) 

43 if checkbox is not None and checkbox.isChecked() != bool(checked): 43 ↛ 44line 43 didn't jump to line 44 because the condition on line 43 was never true

44 checkbox.blockSignals(True) 

45 checkbox.setChecked(bool(checked)) 

46 checkbox.blockSignals(False) 

47 

48 def _set_all_masks_on(self): 

49 with self._history_edit(tr("マスクを全体ON")): 

50 self.mask_rgbs = set(self.mask_checks) 

51 for rgb in self.mask_checks: 

52 self._set_mask_checkbox_without_signal(rgb, True) 

53 self._emit_mask_state() 

54 

55 def _clear_mask_colors(self): 

56 """Compatibility helper: turn every mask OFF.""" 

57 with self._history_edit(tr("マスクを全体OFF")): 

58 self.mask_rgbs.clear() 

59 for rgb in self.mask_checks: 

60 self._set_mask_checkbox_without_signal(rgb, False) 

61 self._emit_mask_state() 

62 

63 def _isolate_mask_color(self, rgb): 

64 """Alt+click: enable only the clicked mask.""" 

65 with self._history_edit(tr("この色だけマスクON")): 

66 self.mask_rgbs = {rgb} 

67 for key in self.mask_checks: 

68 self._set_mask_checkbox_without_signal(key, key == rgb) 

69 self._emit_mask_state() 

70 

71 def _disable_other_masks(self, rgb): 

72 """右クリック対象だけをONにし、それ以外のマスクをOFFにする。""" 

73 with self._history_edit(tr("対象以外のマスクOFF")): 

74 self.mask_rgbs = {rgb} 

75 for key in self.mask_checks: 

76 self._set_mask_checkbox_without_signal(key, key == rgb) 

77 self._emit_mask_state() 

78 

79 def _set_single_mask_state(self, rgb, enabled): 

80 with self._history_edit(tr("マスクの切り替え")): 

81 self._set_mask_checkbox_without_signal(rgb, enabled) 

82 if enabled: 

83 self.mask_rgbs.add(rgb) 

84 else: 

85 self.mask_rgbs.discard(rgb) 

86 self._emit_mask_state() 

87 

88 def _begin_mask_sweep(self, source_rgb, checked): 

89 self._sweep_history_before = self.capture_history_state() 

90 self._mask_sweep_active = True 

91 self._mask_sweep_state = bool(checked) 

92 self._mask_sweep_touched = {source_rgb} 

93 old = source_rgb in self.mask_rgbs 

94 self._set_mask_checkbox_without_signal(source_rgb, checked) 

95 if checked: 

96 self.mask_rgbs.add(source_rgb) 

97 else: 

98 self.mask_rgbs.discard(source_rgb) 

99 self._mask_sweep_changed = old != bool(checked) 

100 

101 def _mask_checkbox_rgb_at_global(self, global_position): 

102 for rgb, checkbox in self.mask_checks.items(): 

103 area = checkbox.parentWidget() 

104 target = area if isinstance(area, CheckClickArea) else checkbox 

105 if target.rect().contains(target.mapFromGlobal(global_position)): 

106 return rgb 

107 return None 

108 

109 def _move_mask_sweep(self, global_position): 

110 if not self._mask_sweep_active: 

111 return 

112 rgb = self._mask_checkbox_rgb_at_global(global_position) 

113 if rgb is None or rgb in self._mask_sweep_touched: 

114 return 

115 self._mask_sweep_touched.add(rgb) 

116 old = rgb in self.mask_rgbs 

117 self._set_mask_checkbox_without_signal(rgb, self._mask_sweep_state) 

118 if self._mask_sweep_state: 

119 self.mask_rgbs.add(rgb) 

120 else: 

121 self.mask_rgbs.discard(rgb) 

122 self._mask_sweep_changed |= old != self._mask_sweep_state 

123 

124 def _end_mask_sweep(self): 

125 if not self._mask_sweep_active: 

126 return 

127 self._mask_sweep_active = False 

128 if self._mask_sweep_changed: 

129 self._emit_mask_state() 

130 self._record_history(tr("マスクの一括切り替え"), self._sweep_history_before) 

131 self._sweep_history_before = None 

132 self._mask_sweep_touched.clear() 

133 self._mask_sweep_changed = False 

134 

135 def _show_mask_context_menu(self, rgb, global_position): 

136 menu = QMenu(self) 

137 action_on = menu.addAction("ON") 

138 action_off = menu.addAction("OFF") 

139 menu.addSeparator() 

140 action_others = menu.addAction(tr("対象以外をOFF")) 

141 action_all = menu.addAction(tr("全表示")) 

142 chosen = menu.exec(global_position) 

143 if chosen is action_on: 

144 self._set_single_mask_state(rgb, True) 

145 elif chosen is action_off: 

146 self._set_single_mask_state(rgb, False) 

147 elif chosen is action_others: 

148 self._disable_other_masks(rgb) 

149 elif chosen is action_all: 

150 self._set_all_masks_on()