Coverage for graphica/gui/crash_handler.py: 100.0%
40 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-12 16:38 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-12 16:38 +0000
1"""未処理の例外で、ログの場所とオートセーブからの復元の手順を案内する(exe ではコンソールが無く、何も見えない)。"""
2import sys
3import os
4import logging
5import traceback
7from PySide6.QtWidgets import QMessageBox
9from graphica.core.version import APP_NAME, LOG_FILE_NAME
10from graphica.core.app_paths import get_app_data_dir
11from graphica.gui import app_settings
13logger = logging.getLogger(__name__)
15_original_excepthook = sys.excepthook
18def install_crash_handler():
19 sys.excepthook = _handle_uncaught_exception
22def _handle_uncaught_exception(exc_type, exc_value, exc_traceback):
23 """Ctrl+C(KeyboardInterrupt)は普通の終了なので案内しない。"""
24 if issubclass(exc_type, KeyboardInterrupt):
25 _original_excepthook(exc_type, exc_value, exc_traceback)
26 return
28 logger.critical("未処理の例外が発生しました。", exc_info=(exc_type, exc_value, exc_traceback))
30 log_path = os.path.join(get_app_data_dir(), LOG_FILE_NAME)
31 detail = "".join(traceback.format_exception(exc_type, exc_value, exc_traceback))
33 try:
34 box = QMessageBox()
35 box.setIcon(QMessageBox.Icon.Critical)
36 box.setWindowTitle(f"{APP_NAME} - 予期しないエラー")
37 box.setText(
38 "予期しないエラーが発生しました。\n\n"
39 "これまでの作業内容はオートセーブされている可能性があります。"
40 f"このまま{APP_NAME}を再起動すると、自動保存からの復元を提案する画面が表示されます。\n\n"
41 f"詳細はログファイルを確認してください:\n{log_path}"
42 )
43 box.setDetailedText(detail)
44 box.setStandardButtons(QMessageBox.StandardButton.Ok)
45 box.exec()
46 except Exception:
47 logger.exception("クラッシュ案内ダイアログの表示に失敗しました。")
50# セーフモードで起動するかの確認。clean_exit を読むだけで、書き換えるのは PlotterApp だけ
53def should_prompt_safe_mode(settings):
54 """前回が正常に終わらなかったか。PlotterApp が clean_exit を False にする前に呼ぶこと(後だと自分を異常終了と見誤る)。"""
55 return not app_settings.CLEAN_EXIT.read(settings)
58def prompt_safe_mode_and_apply(settings, parent=None):
59 """前回が異常終了なら、プラグインなしで起動するか尋ねる。セーフモードにしたら True。
61 既定のボタンは「いいえ」(いつものプラグインが知らないうちに無効になる方が驚く)。
62 """
63 if not should_prompt_safe_mode(settings):
64 return False
66 reply = QMessageBox.question(
67 parent, f"{APP_NAME} - セーフモード起動",
68 "前回異常終了を検出しました。プラグインを無効にして起動しますか?",
69 QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
70 QMessageBox.StandardButton.No,
71 )
72 if reply != QMessageBox.StandardButton.Yes:
73 return False
75 from graphica.core.plugin_api import set_safe_mode
76 set_safe_mode(True)
77 return True