Coverage for paintmaskanimator/preferences.py: 93%
147 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"""User preferences shown in the Preferences window (Qt-independent).
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
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"
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
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)
45def background_color_tool():
46 value = config.get_value("background_color_tool", "brush")
47 return value if value in ("brush", "bucket") else "brush"
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)
56def background_checker_enabled():
57 return bool(config.get_value(BACKGROUND_CHECKER_ENABLED_KEY, False))
60def set_background_checker_enabled(enabled):
61 config.set_value(BACKGROUND_CHECKER_ENABLED_KEY, bool(enabled))
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)
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))
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
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)
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)
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))
109def autosave_enabled():
110 return bool(config.get_value(AUTOSAVE_ENABLED_KEY, True))
113def set_autosave_enabled(enabled):
114 config.set_value(AUTOSAVE_ENABLED_KEY, bool(enabled))
117def snapshot_folder():
118 """キャンバスの書き出し(カメラボタン)の保存先。未設定なら空文字。"""
119 value = config.get_value(SNAPSHOT_FOLDER_KEY, "")
120 return str(value) if value else ""
123def set_snapshot_folder(path):
124 config.set_value(SNAPSHOT_FOLDER_KEY, str(path or ""))
127def keep_initial_state_on_cut_folder():
128 """カットフォルダーを読み込んだら、尋ねずに色塗り前の初期状態として保持するか。"""
129 return bool(config.get_value(KEEP_INITIAL_STATE_KEY, False))
132def set_keep_initial_state_on_cut_folder(enabled):
133 config.set_value(KEEP_INITIAL_STATE_KEY, bool(enabled))
136def autosave_interval_minutes():
137 return _clamped_int(
138 AUTOSAVE_INTERVAL_KEY, DEFAULT_AUTOSAVE_INTERVAL, AUTOSAVE_INTERVAL_RANGE
139 )
142def set_autosave_interval_minutes(minutes):
143 config.set_value(AUTOSAVE_INTERVAL_KEY, int(minutes))
146def undo_max_steps():
147 return _clamped_int(UNDO_STEPS_KEY, DEFAULT_UNDO_STEPS, UNDO_STEPS_RANGE)
150def set_undo_max_steps(steps):
151 config.set_value(UNDO_STEPS_KEY, int(steps))
154def undo_max_memory_mb():
155 return _clamped_int(UNDO_MEMORY_KEY, DEFAULT_UNDO_MEMORY_MB, UNDO_MEMORY_RANGE)
158def set_undo_max_memory_mb(megabytes):
159 config.set_value(UNDO_MEMORY_KEY, int(megabytes))
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)))
175def set_new_canvas_size(width, height):
176 config.set_value(NEW_CANVAS_SIZE_KEY, [int(width), int(height)])
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))
186def map_palette():
187 """隣接色分けの仮の色(RGB のタプルを :data:`view_preview.MAP_PALETTE` と同じ数)。
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)
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 )
215def tool_hold_enabled():
216 """ツールのキーを長押ししたら、離したときに元のツールへ戻すか(一時切り替え)。"""
217 return bool(config.get_value(TOOL_HOLD_ENABLED_KEY, False))
220def set_tool_hold_enabled(enabled):
221 config.set_value(TOOL_HOLD_ENABLED_KEY, bool(enabled))
224def tool_hold_ms():
225 """一時切り替えになる長押しの長さ(ミリ秒)。0 なら押せば必ず一時切り替え。"""
226 return _clamped_int(TOOL_HOLD_MS_KEY, DEFAULT_TOOL_HOLD_MS, TOOL_HOLD_MS_RANGE)
229def set_tool_hold_ms(milliseconds):
230 config.set_value(TOOL_HOLD_MS_KEY, int(milliseconds))
233def check_updates_automatically():
234 """起動したあと裏で新しい版を確かめ、メニューバーで知らせるか。"""
235 return bool(config.get_value(AUTO_UPDATE_CHECK_KEY, True))
238def set_check_updates_automatically(enabled):
239 config.set_value(AUTO_UPDATE_CHECK_KEY, bool(enabled))
242def skipped_update_version():
243 """「この版は知らせない」にした版(無ければ空)。"""
244 return str(config.get_value(SKIPPED_UPDATE_KEY, "") or "")
247def set_skipped_update_version(version):
248 config.set_value(SKIPPED_UPDATE_KEY, str(version or "") or None)