Coverage for graphica/gui/app_settings.py: 100.0%

83 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-12 16:38 +0000

1"""アプリの設定(QSettings)のキー・型・既定値の表と、設定を開く唯一の場所。 

2 

3読み方は今の挙動をそのまま写している。型を指定しないキー(value_type が None)は INI 形式では真偽や数が文字列で返り、 

4要素が 1 つのリストは文字列で返ることがある。どちらも読む側の扱いまで含めて挙動なので、型の有無を変えないこと。 

5""" 

6from dataclasses import dataclass 

7from typing import Any 

8 

9from PySide6.QtCore import QSettings 

10 

11from graphica.core.i18n import DEFAULT_LANGUAGE 

12from graphica.gui.theme_color import DEFAULT_THEME_COLOR 

13 

14ORGANIZATION_NAME = "Graphica" 

15APPLICATION_NAME = "Graphica" 

16 

17DEFAULT_AUTOSAVE_INTERVAL_MIN = 5 # 0 で無効 

18# これより点が多いデータセットには点ラベルを描かない(annotate が点の数だけ呼ばれて固まる)。環境設定で変えられる 

19DEFAULT_POINT_LABEL_MAX_POINTS = 1000 

20DEFAULT_SNAP_TO_GRID_ENABLED = False 

21DEFAULT_SNAP_GRID_INTERVAL_PX = 10 

22 

23CANVAS_DETACHED_GEOMETRY_KEY = "canvas_detached_geometry" 

24CANVAS_WAS_DETACHED_KEY = "canvas_was_detached" 

25# 名前を付けて保存するドック配置(起動時に戻す直近の window_state とは別) 

26# 閉じている節のキーの JSON 配列。無い・空なら全部開く(節を足しても既定は開いたまま) 

27DATASET_PROPERTY_COLLAPSED_SECTIONS_KEY = "dataset_property_collapsed_sections" 

28DISABLED_PLUGINS_SETTINGS_KEY = "disabled_plugins" 

29QUICK_ACCESS_SETTINGS_KEY = "quick_access_pinned_actions" 

30COLOR_PALETTES_SETTINGS_KEY = "custom_color_palettes_json" 

31ACTIVE_PALETTE_SETTINGS_KEY = "active_color_palette" 

32RECENT_COLORS_SETTINGS_KEY = "recent_colors" 

33 

34_TABLE_DEFAULT = object() 

35 

36 

37def open_settings() -> QSettings: 

38 """アプリの設定を開く。QSettings を作るのはここだけ(テストはこのモジュールの QSettings を差し替えて隔離する)。""" 

39 return QSettings(ORGANIZATION_NAME, APPLICATION_NAME) 

40 

41 

42@dataclass(frozen=True) 

43class Setting: 

44 key: str 

45 default: Any = None 

46 value_type: type | None = None # None なら型を指定せずに読む 

47 export_as: type | None = None # 設定の書き出しに入れるときの型(list は要素 1 つを直して読む) 

48 export_default: Any = _TABLE_DEFAULT # 書き出しのときだけ既定値が違うもの 

49 

50 def read(self, settings: QSettings, default: Any = _TABLE_DEFAULT) -> Any: 

51 """default を渡すのは、読む場所によって既定値が違うところ(今の挙動)だけ。""" 

52 fallback = self.default if default is _TABLE_DEFAULT else default 

53 if self.value_type is None: 

54 return settings.value(self.key, fallback) 

55 return settings.value(self.key, fallback, type=self.value_type) 

56 

57 def write(self, settings: QSettings, value: Any) -> None: 

58 settings.setValue(self.key, value) 

59 

60 def read_for_export(self, settings: QSettings) -> Any: 

61 default = self.default if self.export_default is _TABLE_DEFAULT else self.export_default 

62 if self.export_as is list: 

63 return as_list(settings.value(self.key, default)) 

64 return settings.value(self.key, default, type=self.export_as) 

65 

66 

67def as_list(value: Any) -> Any: 

68 """要素 1 つのリストが文字列で返ったのを直す。空の文字列は空のリスト(設定の書き出し・最近使った色の読み方)。""" 

69 if isinstance(value, str): 

70 return [value] if value else [] 

71 return value 

72 

73 

74def as_list_keeping_empty_string(value: Any) -> list[Any]: 

75 """要素 1 つのリストが文字列で返ったのを直す(最近使ったファイル・クイックアクセスの読み方)。""" 

76 if isinstance(value, str): 

77 value = [value] 

78 return list(value) if value else [] 

79 

80 

81LANGUAGE = Setting("language", DEFAULT_LANGUAGE, export_as=str, export_default="") 

82DARK_MODE = Setting("dark_mode", False, bool, export_as=bool) 

83# UI の強調に使う色(利用者が選んだ色そのもの。読みやすさの補正は gui/theme_color.py が毎回する) 

84THEME_COLOR = Setting("theme_color", DEFAULT_THEME_COLOR, str, export_as=str) 

85# 表示のモード: "lite"(シンプル)/ "advanced"(アドバンスド)。空なら初回に gui/ui_mode.initial_mode() が決める 

86UI_MODE = Setting("ui_mode", "", str, export_as=str) 

87AUTOSAVE_INTERVAL_MIN = Setting("autosave_interval_min", DEFAULT_AUTOSAVE_INTERVAL_MIN, int, export_as=int) 

88AUTOSAVE_DIR = Setting("autosave_dir", "", str) 

89# 新しいタブ・プロジェクトに当てる書式テンプレートのパス(空なら使わない)。パスはその PC だけのものなので書き出さない 

90DEFAULT_STYLE_TEMPLATE = Setting("default_style_template", "", str) 

91# ファイルダイアログを用途ごとに前回のフォルダで開く(gui/dialog_dirs.py)。{用途: フォルダ} の JSON。PC ごとの場所なので書き出さない 

92LAST_DIALOG_DIRS = Setting("last_dialog_dirs_json", "", str) 

93# プラグインの更新を最後に調べた日(起動時の確認は1日1回) 

94PLUGIN_UPDATE_LAST_CHECK = Setting("plugin_update_last_check", "", str) 

95POINT_LABEL_MAX_POINTS = Setting("point_label_max_points", DEFAULT_POINT_LABEL_MAX_POINTS, int, export_as=int) 

96SNAP_TO_GRID_ENABLED = Setting("snap_to_grid_enabled", DEFAULT_SNAP_TO_GRID_ENABLED, bool, export_as=bool) 

97SNAP_GRID_INTERVAL_PX = Setting("snap_grid_interval_px", DEFAULT_SNAP_GRID_INTERVAL_PX, int, export_as=int) 

98# 軸の上のホイールで拡大する速さ: "slow" / "normal" / "fast"(gui/tools/view_navigation.WHEEL_ZOOM_BASES) 

99WHEEL_ZOOM_SPEED = Setting("wheel_zoom_speed", "normal", str, export_as=str) 

100CUSTOM_COLOR_PALETTES = Setting(COLOR_PALETTES_SETTINGS_KEY, "", export_as=str) 

101# 画面の既定は配色パレットのダイアログの既定の名前(読む側が渡す)。書き出しでは空 

102ACTIVE_COLOR_PALETTE = Setting(ACTIVE_PALETTE_SETTINGS_KEY, None, export_as=str, export_default="") 

103QUICK_ACCESS_PINNED_ACTIONS = Setting(QUICK_ACCESS_SETTINGS_KEY, [], export_as=list) 

104DISABLED_PLUGINS = Setting(DISABLED_PLUGINS_SETTINGS_KEY, [], export_as=list) 

105 

106CLEAN_EXIT = Setting("clean_exit", True, bool) 

107HAS_SHOWN_WELCOME = Setting("has_shown_welcome", False, bool) 

108MINIMAP_VISIBLE = Setting("minimap_visible", True, bool) 

109RECENT_FILES = Setting("recent_files", []) 

110RECENT_COLORS = Setting(RECENT_COLORS_SETTINGS_KEY, []) 

111WINDOW_GEOMETRY = Setting("window_geometry") 

112WINDOW_STATE = Setting("window_state") 

113DOCK_LAYOUT_VERSION = Setting("dock_layout_version", 0, int) 

114DATASET_PROPERTY_COLLAPSED_SECTIONS = Setting(DATASET_PROPERTY_COLLAPSED_SECTIONS_KEY, "[]") 

115CANVAS_WAS_DETACHED = Setting(CANVAS_WAS_DETACHED_KEY, False, bool) 

116CANVAS_DETACHED_GEOMETRY = Setting(CANVAS_DETACHED_GEOMETRY_KEY) 

117 

118# 設定の書き出し(別の PC や研究室での共有用)に入れるもの。JSON の並びはこの順 

119EXPORTED_SETTINGS: tuple[Setting, ...] = ( 

120 LANGUAGE, DARK_MODE, AUTOSAVE_INTERVAL_MIN, POINT_LABEL_MAX_POINTS, SNAP_TO_GRID_ENABLED, SNAP_GRID_INTERVAL_PX, 

121 CUSTOM_COLOR_PALETTES, ACTIVE_COLOR_PALETTE, QUICK_ACCESS_PINNED_ACTIONS, DISABLED_PLUGINS, 

122 WHEEL_ZOOM_SPEED, THEME_COLOR, UI_MODE, 

123) 

124 

125 

126def disabled_plugin_names(settings: QSettings) -> set[str]: 

127 """QSettings は要素が1つのリストを文字列で返すことがあるので直す。""" 

128 names = DISABLED_PLUGINS.read(settings) 

129 if isinstance(names, str): 

130 names = [names] 

131 return set(names) if names else set()