Coverage for paintmaskanimator/preferences.py: 93%

147 statements  

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

1"""User preferences shown in the Preferences window (Qt-independent). 

2 

3Each setting is read from the per-user :mod:`config` store and clamped to a 

4safe range, so a hand-edited or stale ``config.json`` can never push the app 

5into an unusable state (a zero-second autosave, a 0-step undo). Theme, accent 

6and language keep living in :mod:`theme` / :mod:`i18n`; this module holds the 

7settings that used to be hard-coded constants. 

8""" 

9from . import config, constants, display_shrink, view_preview 

10 

11AUTOSAVE_ENABLED_KEY = "autosave_enabled" 

12AUTOSAVE_INTERVAL_KEY = "autosave_interval_minutes" 

13UNDO_STEPS_KEY = "undo_max_steps" 

14UNDO_MEMORY_KEY = "undo_max_memory_mb" 

15NEW_CANVAS_SIZE_KEY = "new_canvas_size" 

16KEEP_INITIAL_STATE_KEY = "keep_initial_state_on_cut_folder" 

17MAP_PALETTE_KEY = "map_color_palette" 

18TOOL_HOLD_ENABLED_KEY = "tool_hold_switch_enabled" 

19TOOL_HOLD_MS_KEY = "tool_hold_switch_ms" 

20AUTO_UPDATE_CHECK_KEY = "check_updates_automatically" 

21SKIPPED_UPDATE_KEY = "skipped_update_version" 

22BACKGROUND_CHECKER_ENABLED_KEY = "background_checker_enabled" 

23BACKGROUND_CHECKER_COLOR_KEY = "background_checker_color" 

24TIME_DISPLAY_KEY = "time_display_mode" 

25TIME_DISPLAY_FRAMES = "frames" 

26TIME_DISPLAY_SECONDS_FRAMES = "seconds_frames" 

27SNAPSHOT_FOLDER_KEY = "snapshot_folder" 

28DISPLAY_SHRINK_MODE_KEY = "display_shrink_mode" 

29 

30 

31def display_shrink_mode(): 

32 """縮小表示のしかた。``nearest`` か ``keep_lines``(Issue #46)。""" 

33 value = config.get_value( 

34 DISPLAY_SHRINK_MODE_KEY, display_shrink.MODE_NEAREST 

35 ) 

36 return value if value in display_shrink.MODES else display_shrink.MODE_NEAREST 

37 

38 

39def set_display_shrink_mode(mode): 

40 if mode not in display_shrink.MODES: 

41 raise ValueError(mode) 

42 config.set_value(DISPLAY_SHRINK_MODE_KEY, mode) 

43 

44 

45def background_color_tool(): 

46 value = config.get_value("background_color_tool", "brush") 

47 return value if value in ("brush", "bucket") else "brush" 

48 

49 

50def set_background_color_tool(tool): 

51 if tool not in ("brush", "bucket"): 51 ↛ 52line 51 didn't jump to line 52 because the condition on line 51 was never true

52 raise ValueError(tool) 

53 config.set_value("background_color_tool", tool) 

54 

55 

56def background_checker_enabled(): 

57 return bool(config.get_value(BACKGROUND_CHECKER_ENABLED_KEY, False)) 

58 

59 

60def set_background_checker_enabled(enabled): 

61 config.set_value(BACKGROUND_CHECKER_ENABLED_KEY, bool(enabled)) 

62 

63 

64def background_checker_color(): 

65 try: 

66 return _rgb_from_hex(config.get_value(BACKGROUND_CHECKER_COLOR_KEY, "#ffffff")) 

67 except (TypeError, ValueError): 

68 return (255, 255, 255) 

69 

70 

71def set_background_checker_color(color): 

72 rgb = _rgb_from_hex(color) 

73 config.set_value(BACKGROUND_CHECKER_COLOR_KEY, "#{:02x}{:02x}{:02x}".format(*rgb)) 

74 

75 

76def time_display_mode(): 

77 value = config.get_value(TIME_DISPLAY_KEY, TIME_DISPLAY_FRAMES) 

78 if value not in (TIME_DISPLAY_FRAMES, TIME_DISPLAY_SECONDS_FRAMES): 78 ↛ 79line 78 didn't jump to line 79 because the condition on line 78 was never true

79 return TIME_DISPLAY_FRAMES 

80 return value 

81 

82 

83def set_time_display_mode(mode): 

84 if mode not in (TIME_DISPLAY_FRAMES, TIME_DISPLAY_SECONDS_FRAMES): 84 ↛ 85line 84 didn't jump to line 85 because the condition on line 84 was never true

85 raise ValueError(mode) 

86 config.set_value(TIME_DISPLAY_KEY, mode) 

87 

88DEFAULT_AUTOSAVE_INTERVAL = 3 

89AUTOSAVE_INTERVAL_RANGE = (1, 60) 

90DEFAULT_UNDO_STEPS = constants.MAX_UNDO 

91UNDO_STEPS_RANGE = (5, 500) 

92DEFAULT_UNDO_MEMORY_MB = constants.MAX_UNDO_BYTES // (1024 * 1024) 

93UNDO_MEMORY_RANGE = (128, 16384) 

94DEFAULT_CANVAS_SIZE = (constants.CANVAS_WIDTH, constants.CANVAS_HEIGHT) 

95CANVAS_SIZE_RANGE = (64, constants.MAX_IMAGE_DIMENSION) 

96DEFAULT_TOOL_HOLD_MS = 0 

97TOOL_HOLD_MS_RANGE = (0, 3000) 

98 

99 

100def _clamped_int(key, default, bounds): 

101 raw = config.get_value(key, default) 

102 try: 

103 value = int(raw) if raw is not None else default 

104 except (TypeError, ValueError): 

105 value = default 

106 return max(bounds[0], min(bounds[1], value)) 

107 

108 

109def autosave_enabled(): 

110 return bool(config.get_value(AUTOSAVE_ENABLED_KEY, True)) 

111 

112 

113def set_autosave_enabled(enabled): 

114 config.set_value(AUTOSAVE_ENABLED_KEY, bool(enabled)) 

115 

116 

117def snapshot_folder(): 

118 """キャンバスの書き出し(カメラボタン)の保存先。未設定なら空文字。""" 

119 value = config.get_value(SNAPSHOT_FOLDER_KEY, "") 

120 return str(value) if value else "" 

121 

122 

123def set_snapshot_folder(path): 

124 config.set_value(SNAPSHOT_FOLDER_KEY, str(path or "")) 

125 

126 

127def keep_initial_state_on_cut_folder(): 

128 """カットフォルダーを読み込んだら、尋ねずに色塗り前の初期状態として保持するか。""" 

129 return bool(config.get_value(KEEP_INITIAL_STATE_KEY, False)) 

130 

131 

132def set_keep_initial_state_on_cut_folder(enabled): 

133 config.set_value(KEEP_INITIAL_STATE_KEY, bool(enabled)) 

134 

135 

136def autosave_interval_minutes(): 

137 return _clamped_int( 

138 AUTOSAVE_INTERVAL_KEY, DEFAULT_AUTOSAVE_INTERVAL, AUTOSAVE_INTERVAL_RANGE 

139 ) 

140 

141 

142def set_autosave_interval_minutes(minutes): 

143 config.set_value(AUTOSAVE_INTERVAL_KEY, int(minutes)) 

144 

145 

146def undo_max_steps(): 

147 return _clamped_int(UNDO_STEPS_KEY, DEFAULT_UNDO_STEPS, UNDO_STEPS_RANGE) 

148 

149 

150def set_undo_max_steps(steps): 

151 config.set_value(UNDO_STEPS_KEY, int(steps)) 

152 

153 

154def undo_max_memory_mb(): 

155 return _clamped_int(UNDO_MEMORY_KEY, DEFAULT_UNDO_MEMORY_MB, UNDO_MEMORY_RANGE) 

156 

157 

158def set_undo_max_memory_mb(megabytes): 

159 config.set_value(UNDO_MEMORY_KEY, int(megabytes)) 

160 

161 

162def new_canvas_size(): 

163 """Return the ``(width, height)`` used for a new document.""" 

164 stored = config.get_value(NEW_CANVAS_SIZE_KEY) 

165 if stored is None: 

166 return DEFAULT_CANVAS_SIZE 

167 try: 

168 width, height = (int(v) for v in stored) 

169 except (TypeError, ValueError): 

170 return DEFAULT_CANVAS_SIZE 

171 low, high = CANVAS_SIZE_RANGE 

172 return (max(low, min(high, width)), max(low, min(high, height))) 

173 

174 

175def set_new_canvas_size(width, height): 

176 config.set_value(NEW_CANVAS_SIZE_KEY, [int(width), int(height)]) 

177 

178 

179def _rgb_from_hex(value): 

180 text = str(value).strip().lstrip("#") 

181 if len(text) != 6: 

182 raise ValueError(value) 

183 return tuple(int(text[index:index + 2], 16) for index in (0, 2, 4)) 

184 

185 

186def map_palette(): 

187 """隣接色分けの仮の色(RGB のタプルを :data:`view_preview.MAP_PALETTE` と同じ数)。 

188 

189 保存されていない色・読めない色は、その番号の既定の色にする。 

190 """ 

191 defaults = view_preview.MAP_PALETTE 

192 stored = config.get_value(MAP_PALETTE_KEY) 

193 if not isinstance(stored, list): 

194 return defaults 

195 colors = [] 

196 for index, default in enumerate(defaults): 

197 try: 

198 colors.append(_rgb_from_hex(stored[index])) 

199 except (IndexError, TypeError, ValueError): 

200 colors.append(default) 

201 return tuple(colors) 

202 

203 

204def set_map_palette(colors): 

205 """隣接色分けの仮の色を保存する。既定と同じなら設定から消す。""" 

206 colors = tuple(tuple(int(channel) for channel in rgb[:3]) for rgb in colors) 

207 if colors == view_preview.MAP_PALETTE: 

208 config.set_value(MAP_PALETTE_KEY, None) 

209 return 

210 config.set_value( 

211 MAP_PALETTE_KEY, ["#{:02x}{:02x}{:02x}".format(*rgb) for rgb in colors] 

212 ) 

213 

214 

215def tool_hold_enabled(): 

216 """ツールのキーを長押ししたら、離したときに元のツールへ戻すか(一時切り替え)。""" 

217 return bool(config.get_value(TOOL_HOLD_ENABLED_KEY, False)) 

218 

219 

220def set_tool_hold_enabled(enabled): 

221 config.set_value(TOOL_HOLD_ENABLED_KEY, bool(enabled)) 

222 

223 

224def tool_hold_ms(): 

225 """一時切り替えになる長押しの長さ(ミリ秒)。0 なら押せば必ず一時切り替え。""" 

226 return _clamped_int(TOOL_HOLD_MS_KEY, DEFAULT_TOOL_HOLD_MS, TOOL_HOLD_MS_RANGE) 

227 

228 

229def set_tool_hold_ms(milliseconds): 

230 config.set_value(TOOL_HOLD_MS_KEY, int(milliseconds)) 

231 

232 

233def check_updates_automatically(): 

234 """起動したあと裏で新しい版を確かめ、メニューバーで知らせるか。""" 

235 return bool(config.get_value(AUTO_UPDATE_CHECK_KEY, True)) 

236 

237 

238def set_check_updates_automatically(enabled): 

239 config.set_value(AUTO_UPDATE_CHECK_KEY, bool(enabled)) 

240 

241 

242def skipped_update_version(): 

243 """「この版は知らせない」にした版(無ければ空)。""" 

244 return str(config.get_value(SKIPPED_UPDATE_KEY, "") or "") 

245 

246 

247def set_skipped_update_version(version): 

248 config.set_value(SKIPPED_UPDATE_KEY, str(version or "") or None)