graphica-plot 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- graphica/Graphica.ico +0 -0
- graphica/__init__.py +1 -0
- graphica/__main__.py +111 -0
- graphica/assets/__init__.py +0 -0
- graphica/assets/icons/__init__.py +0 -0
- graphica/assets/icons/arrow-right.svg +21 -0
- graphica/assets/icons/bold.svg +20 -0
- graphica/assets/icons/calculator.svg +26 -0
- graphica/assets/icons/chart-histogram.svg +24 -0
- graphica/assets/icons/chart-line.svg +20 -0
- graphica/assets/icons/chevron-down.svg +19 -0
- graphica/assets/icons/chevron-right.svg +19 -0
- graphica/assets/icons/color-swatch.svg +22 -0
- graphica/assets/icons/column-insert-right.svg +21 -0
- graphica/assets/icons/column-remove.svg +21 -0
- graphica/assets/icons/copy.svg +20 -0
- graphica/assets/icons/download.svg +21 -0
- graphica/assets/icons/edit.svg +21 -0
- graphica/assets/icons/eye-off.svg +21 -0
- graphica/assets/icons/eye.svg +20 -0
- graphica/assets/icons/file-plus.svg +22 -0
- graphica/assets/icons/folder-plus.svg +21 -0
- graphica/assets/icons/folder.svg +19 -0
- graphica/assets/icons/highlight.svg +22 -0
- graphica/assets/icons/history.svg +20 -0
- graphica/assets/icons/italic.svg +21 -0
- graphica/assets/icons/layout-grid.svg +22 -0
- graphica/assets/icons/math-function.svg +22 -0
- graphica/assets/icons/message-2.svg +21 -0
- graphica/assets/icons/mountain.svg +20 -0
- graphica/assets/icons/palette.svg +22 -0
- graphica/assets/icons/pointer.svg +19 -0
- graphica/assets/icons/refresh.svg +20 -0
- graphica/assets/icons/row-insert-bottom.svg +21 -0
- graphica/assets/icons/row-remove.svg +21 -0
- graphica/assets/icons/search.svg +20 -0
- graphica/assets/icons/select-all.svg +35 -0
- graphica/assets/icons/subscript.svg +20 -0
- graphica/assets/icons/superscript.svg +20 -0
- graphica/assets/icons/table.svg +21 -0
- graphica/assets/icons/trash.svg +23 -0
- graphica/assets/icons/typography.svg +23 -0
- graphica/assets/icons/x.svg +20 -0
- graphica/core/__init__.py +0 -0
- graphica/core/analysis.py +1278 -0
- graphica/core/app_paths.py +35 -0
- graphica/core/axis_settings.py +128 -0
- graphica/core/caption_export.py +47 -0
- graphica/core/color_palettes.py +56 -0
- graphica/core/commands.py +226 -0
- graphica/core/cvd_simulation.py +49 -0
- graphica/core/dataset.py +443 -0
- graphica/core/diagnostics.py +105 -0
- graphica/core/excel_utils.py +58 -0
- graphica/core/fit_models.py +379 -0
- graphica/core/grid_data.py +128 -0
- graphica/core/i18n.py +44 -0
- graphica/core/json_utils.py +27 -0
- graphica/core/label_utils.py +21 -0
- graphica/core/methods_text.py +115 -0
- graphica/core/named_colors.py +126 -0
- graphica/core/plugin_api.py +515 -0
- graphica/core/plugin_context.py +167 -0
- graphica/core/plugin_install.py +82 -0
- graphica/core/plugin_manifest.py +67 -0
- graphica/core/plugin_testing.py +213 -0
- graphica/core/plugin_types.py +118 -0
- graphica/core/provenance.py +19 -0
- graphica/core/report_export.py +51 -0
- graphica/core/safe_eval.py +194 -0
- graphica/core/script_export.py +272 -0
- graphica/core/translations_en.py +530 -0
- graphica/core/unit_conversion.py +51 -0
- graphica/core/update_check.py +43 -0
- graphica/core/version.py +14 -0
- graphica/gui/__init__.py +0 -0
- graphica/gui/app_settings.py +119 -0
- graphica/gui/axis_bindings.py +173 -0
- graphica/gui/binding.py +135 -0
- graphica/gui/builders/__init__.py +1 -0
- graphica/gui/builders/axis_panel.py +431 -0
- graphica/gui/builders/canvas_area.py +176 -0
- graphica/gui/builders/common.py +120 -0
- graphica/gui/builders/dataset_panel.py +433 -0
- graphica/gui/builders/property_sections.py +208 -0
- graphica/gui/canvas.py +519 -0
- graphica/gui/color_history.py +47 -0
- graphica/gui/color_picker_widget.py +198 -0
- graphica/gui/crash_handler.py +77 -0
- graphica/gui/cvd_preview.py +23 -0
- graphica/gui/data_editor.py +761 -0
- graphica/gui/data_import_flow.py +355 -0
- graphica/gui/dataset_bindings.py +107 -0
- graphica/gui/dataset_style_icon.py +80 -0
- graphica/gui/datasets/__init__.py +1 -0
- graphica/gui/datasets/actions_menu.py +114 -0
- graphica/gui/datasets/colors.py +218 -0
- graphica/gui/datasets/fitting.py +70 -0
- graphica/gui/datasets/host.py +162 -0
- graphica/gui/datasets/operations/__init__.py +1 -0
- graphica/gui/datasets/operations/fitting.py +468 -0
- graphica/gui/datasets/operations/peaks.py +109 -0
- graphica/gui/datasets/operations/processing.py +659 -0
- graphica/gui/datasets/operations/runner.py +148 -0
- graphica/gui/datasets/operations/transfer.py +227 -0
- graphica/gui/datasets/order.py +310 -0
- graphica/gui/datasets/overlays.py +74 -0
- graphica/gui/datasets/peaks.py +21 -0
- graphica/gui/datasets/plugin_runs.py +99 -0
- graphica/gui/datasets/processing.py +68 -0
- graphica/gui/datasets/property_panel.py +538 -0
- graphica/gui/datasets/transfer.py +36 -0
- graphica/gui/detached_canvas_window.py +22 -0
- graphica/gui/dialogs/__init__.py +137 -0
- graphica/gui/dialogs/analysis.py +1322 -0
- graphica/gui/dialogs/app.py +1015 -0
- graphica/gui/dialogs/appearance.py +851 -0
- graphica/gui/dialogs/data_edit.py +577 -0
- graphica/gui/dialogs/data_import.py +679 -0
- graphica/gui/dialogs/export.py +433 -0
- graphica/gui/dock_layout.py +176 -0
- graphica/gui/export_preview_panel.py +321 -0
- graphica/gui/export_settings.py +16 -0
- graphica/gui/file_association.py +97 -0
- graphica/gui/icon_utils.py +44 -0
- graphica/gui/main_app_window.py +210 -0
- graphica/gui/main_window.py +1151 -0
- graphica/gui/mathtext_preview.py +168 -0
- graphica/gui/menu_bar.py +320 -0
- graphica/gui/minimap_widget.py +151 -0
- graphica/gui/mixins/__init__.py +0 -0
- graphica/gui/mixins/export_mixin.py +520 -0
- graphica/gui/mixins/help_mixin.py +127 -0
- graphica/gui/mixins/project_io_mixin.py +289 -0
- graphica/gui/mixins/quick_access_mixin.py +194 -0
- graphica/gui/mixins/ui_setup_mixin.py +206 -0
- graphica/gui/notify.py +61 -0
- graphica/gui/panels/__init__.py +43 -0
- graphica/gui/panels/axis_settings.py +605 -0
- graphica/gui/panels/dataset_tree.py +424 -0
- graphica/gui/plot_type_drawers.py +131 -0
- graphica/gui/plugin_context.py +119 -0
- graphica/gui/project_files.py +361 -0
- graphica/gui/provenance_panel.py +65 -0
- graphica/gui/rendering/__init__.py +1 -0
- graphica/gui/rendering/annotations.py +177 -0
- graphica/gui/rendering/appearance.py +368 -0
- graphica/gui/rendering/common.py +316 -0
- graphica/gui/rendering/data_1d.py +376 -0
- graphica/gui/rendering/data_2d.py +66 -0
- graphica/gui/residual_panel.py +62 -0
- graphica/gui/resources.py +16 -0
- graphica/gui/single_instance.py +141 -0
- graphica/gui/splash.py +83 -0
- graphica/gui/task_runner.py +41 -0
- graphica/gui/theme.py +864 -0
- graphica/gui/tools/__init__.py +56 -0
- graphica/gui/tools/annotation.py +275 -0
- graphica/gui/tools/cursor.py +259 -0
- graphica/gui/tools/layout_edit.py +285 -0
- graphica/gui/tools/manager.py +78 -0
- graphica/gui/tools/peak_placement.py +132 -0
- graphica/gui/tools/range_select.py +191 -0
- graphica/gui/tools/region_highlight.py +222 -0
- graphica/gui/tools/slice_extraction.py +200 -0
- graphica/gui/widget_translation.py +46 -0
- graphica/gui/workers.py +280 -0
- graphica/models/__init__.py +0 -0
- graphica/models/project.py +197 -0
- graphica/plugin/__init__.py +20 -0
- graphica/plugin/testing.py +57 -0
- graphica/sample_data/__init__.py +0 -0
- graphica/sample_data/cooling_curve_sample.csv +42 -0
- graphica/ui_main_window.py +581 -0
- graphica_plot-2.0.0.dist-info/METADATA +317 -0
- graphica_plot-2.0.0.dist-info/RECORD +181 -0
- graphica_plot-2.0.0.dist-info/WHEEL +5 -0
- graphica_plot-2.0.0.dist-info/entry_points.txt +2 -0
- graphica_plot-2.0.0.dist-info/licenses/LICENSE +30 -0
- graphica_plot-2.0.0.dist-info/licenses/THIRD_PARTY_LICENSES.md +91 -0
- graphica_plot-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""データセットの色の欄: 色見本のボタン(クリックで色を選ぶ)と、#RRGGBB の入力欄。"""
|
|
2
|
+
from PySide6.QtCore import Signal, Qt
|
|
3
|
+
from PySide6.QtGui import QColor, QIcon, QPainter, QPen, QPixmap
|
|
4
|
+
from PySide6.QtWidgets import (QWidget, QHBoxLayout, QPushButton, QLineEdit,
|
|
5
|
+
QMenu)
|
|
6
|
+
|
|
7
|
+
from graphica.gui import notify
|
|
8
|
+
from graphica.core.i18n import tr
|
|
9
|
+
from graphica.core.named_colors import (
|
|
10
|
+
POPUP_LIMIT, NamedColorError, add_named_color, load_named_colors,
|
|
11
|
+
save_named_colors,
|
|
12
|
+
)
|
|
13
|
+
from graphica.gui.color_history import get_color_with_history
|
|
14
|
+
|
|
15
|
+
_SWATCH_ICON_SIZE = 14
|
|
16
|
+
|
|
17
|
+
DEFAULT_COLOR = "#1f77b4"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ColorPickerWidget(QWidget):
|
|
21
|
+
"""colorChanged(str) は利用者の操作で色が変わったときだけ出る(set_color() では出ない)。"""
|
|
22
|
+
colorChanged = Signal(str)
|
|
23
|
+
|
|
24
|
+
def __init__(self, settings, parent=None, initial_color=DEFAULT_COLOR):
|
|
25
|
+
super().__init__(parent)
|
|
26
|
+
self._settings = settings
|
|
27
|
+
self._color = QColor(initial_color)
|
|
28
|
+
|
|
29
|
+
layout = QHBoxLayout(self)
|
|
30
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
|
31
|
+
layout.setSpacing(6)
|
|
32
|
+
|
|
33
|
+
self.swatch_button = QPushButton(self)
|
|
34
|
+
self.swatch_button.setFixedSize(30, 24)
|
|
35
|
+
self.swatch_button.setCursor(Qt.CursorShape.PointingHandCursor)
|
|
36
|
+
self.swatch_button.setToolTip(tr("クリックしてパレットを開く"))
|
|
37
|
+
self.swatch_button.clicked.connect(self._on_swatch_clicked)
|
|
38
|
+
|
|
39
|
+
self.hex_edit = QLineEdit(self)
|
|
40
|
+
self.hex_edit.setPlaceholderText("#RRGGBB")
|
|
41
|
+
self.hex_edit.setMaxLength(7)
|
|
42
|
+
self.hex_edit.editingFinished.connect(self._on_hex_edited)
|
|
43
|
+
self.hex_edit.textChanged.connect(self._on_hex_text_changed)
|
|
44
|
+
|
|
45
|
+
layout.addWidget(self.swatch_button)
|
|
46
|
+
layout.addWidget(self.hex_edit, 1)
|
|
47
|
+
|
|
48
|
+
self._update_swatch()
|
|
49
|
+
self.hex_edit.setText(self._color.name())
|
|
50
|
+
|
|
51
|
+
def color(self):
|
|
52
|
+
return QColor(self._color)
|
|
53
|
+
|
|
54
|
+
def color_name(self):
|
|
55
|
+
return self._color.name()
|
|
56
|
+
|
|
57
|
+
def set_color(self, color):
|
|
58
|
+
"""表示だけ変える(colorChanged は出ない)。"""
|
|
59
|
+
new_color = QColor(color)
|
|
60
|
+
if not new_color.isValid():
|
|
61
|
+
return
|
|
62
|
+
self._color = new_color
|
|
63
|
+
self._update_swatch()
|
|
64
|
+
self.hex_edit.blockSignals(True)
|
|
65
|
+
self.hex_edit.setText(self._color.name())
|
|
66
|
+
self.hex_edit.blockSignals(False)
|
|
67
|
+
|
|
68
|
+
def blockSignals(self, block):
|
|
69
|
+
# ほかの欄と同じく blockSignals で値を入れられるよう、中の部品にも伝える
|
|
70
|
+
self.swatch_button.blockSignals(block)
|
|
71
|
+
self.hex_edit.blockSignals(block)
|
|
72
|
+
return super().blockSignals(block)
|
|
73
|
+
|
|
74
|
+
def _update_swatch(self, preview_color=None):
|
|
75
|
+
# preview_color は入力中(未確定)の色。None なら確定した self._color
|
|
76
|
+
from graphica.gui import theme
|
|
77
|
+
border_color = theme.current_tokens()["border_strong"]
|
|
78
|
+
color_name = (preview_color or self._color).name()
|
|
79
|
+
self.swatch_button.setStyleSheet(
|
|
80
|
+
f"background-color: {color_name};"
|
|
81
|
+
f"border: 1px solid {border_color};"
|
|
82
|
+
f"border-radius: 4px;"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def refresh_theme(self):
|
|
86
|
+
"""ダークモードの切り替えで枠の色を当て直す(塗りはデータの色なのでテーマと関係ない)。"""
|
|
87
|
+
self._update_swatch()
|
|
88
|
+
|
|
89
|
+
def _on_swatch_clicked(self):
|
|
90
|
+
"""登録した色(core/named_colors.py)を先に並べ、自由な色選びは「その他の色...」から。"""
|
|
91
|
+
menu = QMenu(self)
|
|
92
|
+
entries = load_named_colors(self._settings)
|
|
93
|
+
|
|
94
|
+
# 登録は際限なく増えるので、並べるのは先頭 POPUP_LIMIT 件まで。残りは検索できる一覧から選ぶ
|
|
95
|
+
for entry in entries[:POPUP_LIMIT]:
|
|
96
|
+
action = menu.addAction(_color_icon(entry["color"]),
|
|
97
|
+
f'{entry["name"]} {entry["color"]}')
|
|
98
|
+
action.setData(entry["color"])
|
|
99
|
+
action.triggered.connect(
|
|
100
|
+
lambda checked=False, c=entry["color"]: self._apply_color_name(c))
|
|
101
|
+
if len(entries) > POPUP_LIMIT:
|
|
102
|
+
more_action = menu.addAction(
|
|
103
|
+
tr("すべての登録色... (%d件)") % len(entries))
|
|
104
|
+
more_action.triggered.connect(self._on_choose_from_all_named_colors)
|
|
105
|
+
if entries:
|
|
106
|
+
menu.addSeparator()
|
|
107
|
+
|
|
108
|
+
register_action = menu.addAction(tr("この色を登録..."))
|
|
109
|
+
register_action.triggered.connect(self._on_register_current_color)
|
|
110
|
+
manage_action = menu.addAction(tr("色名の管理..."))
|
|
111
|
+
manage_action.triggered.connect(self._on_manage_named_colors)
|
|
112
|
+
menu.addSeparator()
|
|
113
|
+
other_action = menu.addAction(tr("その他の色..."))
|
|
114
|
+
other_action.triggered.connect(self._on_pick_other_color)
|
|
115
|
+
|
|
116
|
+
# menu が exec() の間生きていることに頼っている(データセットの右クリックメニューと同じ)
|
|
117
|
+
menu.exec(self.swatch_button.mapToGlobal(
|
|
118
|
+
self.swatch_button.rect().bottomLeft()))
|
|
119
|
+
|
|
120
|
+
def _apply_color_name(self, color_name):
|
|
121
|
+
"""値が変わったときだけ知らせる。"""
|
|
122
|
+
candidate = QColor(color_name)
|
|
123
|
+
if not candidate.isValid() or candidate.name() == self._color.name():
|
|
124
|
+
return
|
|
125
|
+
self.set_color(candidate)
|
|
126
|
+
self.colorChanged.emit(self._color.name())
|
|
127
|
+
|
|
128
|
+
def _on_choose_from_all_named_colors(self):
|
|
129
|
+
from graphica.gui.dialogs import NamedColorPickerDialog
|
|
130
|
+
dialog = NamedColorPickerDialog(self._settings, self)
|
|
131
|
+
if dialog.exec() != dialog.DialogCode.Accepted:
|
|
132
|
+
return
|
|
133
|
+
entry = dialog.selected_entry()
|
|
134
|
+
if entry:
|
|
135
|
+
self._apply_color_name(entry["color"])
|
|
136
|
+
|
|
137
|
+
def _on_pick_other_color(self):
|
|
138
|
+
color = get_color_with_history(self._settings, self, initial=self._color)
|
|
139
|
+
if not color.isValid():
|
|
140
|
+
return
|
|
141
|
+
if color.name() == self._color.name():
|
|
142
|
+
return
|
|
143
|
+
self.set_color(color)
|
|
144
|
+
self.colorChanged.emit(self._color.name())
|
|
145
|
+
|
|
146
|
+
def _on_register_current_color(self):
|
|
147
|
+
name, ok = notify.get_text(
|
|
148
|
+
self, tr("色を登録"),
|
|
149
|
+
tr("この色の登録名 (%s)") % self._color.name())
|
|
150
|
+
if not ok:
|
|
151
|
+
return
|
|
152
|
+
entries = load_named_colors(self._settings)
|
|
153
|
+
try:
|
|
154
|
+
entries = add_named_color(entries, name, self._color.name())
|
|
155
|
+
except NamedColorError as e:
|
|
156
|
+
notify.warning(self, tr("色を登録"), str(e))
|
|
157
|
+
return
|
|
158
|
+
save_named_colors(self._settings, entries)
|
|
159
|
+
|
|
160
|
+
def _on_manage_named_colors(self):
|
|
161
|
+
"""dialogs がこのモジュールを import しているので、ここで遅れて import する。"""
|
|
162
|
+
from graphica.gui.dialogs import NamedColorManagerDialog
|
|
163
|
+
dialog = NamedColorManagerDialog(self._settings, self)
|
|
164
|
+
dialog.exec()
|
|
165
|
+
|
|
166
|
+
def _on_hex_text_changed(self, text):
|
|
167
|
+
"""入力中でも有効な色になったら色見本に先に出す。確定(反映・Undo・colorChanged)は _on_hex_edited だけ(打つたびに Undo を積まないように)。"""
|
|
168
|
+
candidate = QColor(text.strip())
|
|
169
|
+
if candidate.isValid():
|
|
170
|
+
self._update_swatch(preview_color=candidate)
|
|
171
|
+
else:
|
|
172
|
+
self._update_swatch()
|
|
173
|
+
|
|
174
|
+
def _on_hex_edited(self):
|
|
175
|
+
text = self.hex_edit.text().strip()
|
|
176
|
+
candidate = QColor(text)
|
|
177
|
+
if not candidate.isValid():
|
|
178
|
+
# 無効な入力なら今の色の表記に戻す
|
|
179
|
+
self.hex_edit.setText(self._color.name())
|
|
180
|
+
return
|
|
181
|
+
if candidate.name() == self._color.name():
|
|
182
|
+
return
|
|
183
|
+
self.set_color(candidate)
|
|
184
|
+
self.colorChanged.emit(self._color.name())
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _color_icon(color_name, size=_SWATCH_ICON_SIZE):
|
|
188
|
+
"""メニューの色見本。白や淡い色が背景に溶けないよう1pxの枠を描く。"""
|
|
189
|
+
pixmap = QPixmap(size, size)
|
|
190
|
+
pixmap.fill(Qt.GlobalColor.transparent)
|
|
191
|
+
painter = QPainter(pixmap)
|
|
192
|
+
painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
|
|
193
|
+
from graphica.gui import theme
|
|
194
|
+
painter.setPen(QPen(QColor(theme.current_tokens()["border_strong"]), 1))
|
|
195
|
+
painter.setBrush(QColor(color_name))
|
|
196
|
+
painter.drawRoundedRect(0, 0, size - 1, size - 1, 3, 3)
|
|
197
|
+
painter.end()
|
|
198
|
+
return QIcon(pixmap)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""未処理の例外で、ログの場所とオートセーブからの復元の手順を案内する(exe ではコンソールが無く、何も見えない)。"""
|
|
2
|
+
import sys
|
|
3
|
+
import os
|
|
4
|
+
import logging
|
|
5
|
+
import traceback
|
|
6
|
+
|
|
7
|
+
from PySide6.QtWidgets import QMessageBox
|
|
8
|
+
|
|
9
|
+
from graphica.core.version import APP_NAME, LOG_FILE_NAME
|
|
10
|
+
from graphica.core.app_paths import get_app_data_dir
|
|
11
|
+
from graphica.gui import app_settings
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
_original_excepthook = sys.excepthook
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def install_crash_handler():
|
|
19
|
+
sys.excepthook = _handle_uncaught_exception
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _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
|
|
27
|
+
|
|
28
|
+
logger.critical("未処理の例外が発生しました。", exc_info=(exc_type, exc_value, exc_traceback))
|
|
29
|
+
|
|
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))
|
|
32
|
+
|
|
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("クラッシュ案内ダイアログの表示に失敗しました。")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# セーフモードで起動するかの確認。clean_exit を読むだけで、書き換えるのは PlotterApp だけ
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def should_prompt_safe_mode(settings):
|
|
54
|
+
"""前回が正常に終わらなかったか。PlotterApp が clean_exit を False にする前に呼ぶこと(後だと自分を異常終了と見誤る)。"""
|
|
55
|
+
return not app_settings.CLEAN_EXIT.read(settings)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def prompt_safe_mode_and_apply(settings, parent=None):
|
|
59
|
+
"""前回が異常終了なら、プラグインなしで起動するか尋ねる。セーフモードにしたら True。
|
|
60
|
+
|
|
61
|
+
既定のボタンは「いいえ」(いつものプラグインが知らないうちに無効になる方が驚く)。
|
|
62
|
+
"""
|
|
63
|
+
if not should_prompt_safe_mode(settings):
|
|
64
|
+
return False
|
|
65
|
+
|
|
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
|
|
74
|
+
|
|
75
|
+
from graphica.core.plugin_api import set_safe_mode
|
|
76
|
+
set_safe_mode(True)
|
|
77
|
+
return True
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""QImage と numpy 配列の変換(色の変換は Qt に依存しない core/cvd_simulation.py)。"""
|
|
2
|
+
import numpy as np
|
|
3
|
+
from PySide6.QtGui import QImage
|
|
4
|
+
|
|
5
|
+
from graphica.core.cvd_simulation import simulate_rgb_array
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def simulate_qimage(image, cvd_type):
|
|
9
|
+
"""変換した新しい QImage(アルファと元の image は変えない)。"""
|
|
10
|
+
src = image.convertToFormat(QImage.Format.Format_RGBA8888)
|
|
11
|
+
width, height = src.width(), src.height()
|
|
12
|
+
# 行の末尾に詰め物があるので、bytesPerLine() の幅で読んでから width*4 に切り詰める
|
|
13
|
+
bytes_per_line = src.bytesPerLine()
|
|
14
|
+
buf = bytes(src.constBits())
|
|
15
|
+
arr = np.frombuffer(buf, dtype=np.uint8).reshape((height, bytes_per_line))
|
|
16
|
+
arr = arr[:, : width * 4].reshape((height, width, 4)).copy()
|
|
17
|
+
|
|
18
|
+
rgb = arr[:, :, :3].astype(np.float64)
|
|
19
|
+
simulated = np.clip(simulate_rgb_array(rgb, cvd_type), 0, 255).astype(np.uint8)
|
|
20
|
+
arr[:, :, :3] = simulated
|
|
21
|
+
|
|
22
|
+
result = QImage(arr.tobytes(), width, height, width * 4, QImage.Format.Format_RGBA8888)
|
|
23
|
+
return result.copy() # arr のバッファの寿命から切り離す
|