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,210 @@
|
|
|
1
|
+
"""最上位のウィンドウ。各タブは独立した PlotterApp(QMainWindow)で、Undo もメニューもドックも別々に持つ。
|
|
2
|
+
|
|
3
|
+
アプリ全体で1回だけの処理(オートセーブの復元確認、初回の案内、ドック配置の保存、clean_exit)は最初のタブだけが行う。
|
|
4
|
+
"""
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
from PySide6.QtCore import Qt, QSize
|
|
8
|
+
from PySide6.QtGui import QIcon, QUndoGroup
|
|
9
|
+
from PySide6.QtWidgets import (QMainWindow, QTabWidget, QToolButton, QDockWidget,
|
|
10
|
+
QUndoView, QWidget, QHBoxLayout)
|
|
11
|
+
|
|
12
|
+
from graphica.gui import notify
|
|
13
|
+
from graphica.gui import app_settings
|
|
14
|
+
from graphica.gui.main_window import PlotterApp, resource_path
|
|
15
|
+
from graphica.gui.icon_utils import icon as svg_icon
|
|
16
|
+
from graphica.gui import theme
|
|
17
|
+
from graphica.core.version import APP_NAME, __version__
|
|
18
|
+
from graphica.models.project import LEGACY_PICKLE_EXTENSION, PROJECT_FILE_EXTENSIONS
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
DEFAULT_WINDOW_WIDTH = 1300
|
|
23
|
+
DEFAULT_WINDOW_HEIGHT = 850
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class MainAppWindow(QMainWindow):
|
|
27
|
+
def __init__(self):
|
|
28
|
+
super().__init__()
|
|
29
|
+
self.setWindowTitle(f"{APP_NAME} {__version__}")
|
|
30
|
+
icon_path = resource_path("Graphica.ico")
|
|
31
|
+
self.setWindowIcon(QIcon(icon_path))
|
|
32
|
+
|
|
33
|
+
# ウィンドウ全体の大きさと位置はここで持つ
|
|
34
|
+
self._settings = app_settings.open_settings()
|
|
35
|
+
|
|
36
|
+
# 各タブは自分の QUndoStack を持ち、ここでグループにまとめて、表示中のタブのものを有効にする
|
|
37
|
+
# (履歴パネルはグループを通して表示中のタブの履歴を出す)
|
|
38
|
+
self.undo_group = QUndoGroup(self)
|
|
39
|
+
self._create_undo_history_dock()
|
|
40
|
+
# 履歴パネルはこのウィンドウのドックなので、タブとは別にここでもフォーカスの強調を付ける
|
|
41
|
+
theme.install_dock_focus_highlight(self)
|
|
42
|
+
|
|
43
|
+
self._next_tab_id = 1
|
|
44
|
+
|
|
45
|
+
self.tab_widget = QTabWidget()
|
|
46
|
+
self.tab_widget.setTabsClosable(True)
|
|
47
|
+
self.tab_widget.setMovable(True)
|
|
48
|
+
self.tab_widget.tabCloseRequested.connect(self._on_tab_close_requested)
|
|
49
|
+
self.tab_widget.currentChanged.connect(self._on_current_tab_changed)
|
|
50
|
+
self.setCentralWidget(self.tab_widget)
|
|
51
|
+
|
|
52
|
+
add_tab_button = QToolButton()
|
|
53
|
+
add_tab_button.setObjectName("add_tab_button")
|
|
54
|
+
add_tab_button.setIcon(svg_icon("file-plus", size=18))
|
|
55
|
+
add_tab_button.setIconSize(QSize(18, 18))
|
|
56
|
+
add_tab_button.setToolTip("新しいプロジェクトタブを開く")
|
|
57
|
+
add_tab_button.setCursor(Qt.CursorShape.PointingHandCursor)
|
|
58
|
+
add_tab_button.setFocusPolicy(Qt.FocusPolicy.NoFocus)
|
|
59
|
+
add_tab_button.clicked.connect(lambda: self.add_new_project_tab())
|
|
60
|
+
|
|
61
|
+
# setDefaultAction() はボタンのアイコンとツールチップを QAction のもので上書きするので、先に QAction に設定する
|
|
62
|
+
undo_history_action = self.undo_history_dock.toggleViewAction()
|
|
63
|
+
undo_history_action.setIcon(svg_icon("history", size=18))
|
|
64
|
+
undo_history_action.setToolTip("Undo履歴パネルの表示/非表示")
|
|
65
|
+
undo_history_button = QToolButton()
|
|
66
|
+
undo_history_button.setObjectName("undo_history_button")
|
|
67
|
+
undo_history_button.setIconSize(QSize(18, 18))
|
|
68
|
+
undo_history_button.setCursor(Qt.CursorShape.PointingHandCursor)
|
|
69
|
+
undo_history_button.setFocusPolicy(Qt.FocusPolicy.NoFocus)
|
|
70
|
+
undo_history_button.setCheckable(True)
|
|
71
|
+
undo_history_button.setDefaultAction(undo_history_action)
|
|
72
|
+
|
|
73
|
+
corner_widget = QWidget()
|
|
74
|
+
corner_layout = QHBoxLayout(corner_widget)
|
|
75
|
+
corner_layout.setContentsMargins(0, 0, 0, 0)
|
|
76
|
+
corner_layout.setSpacing(2)
|
|
77
|
+
corner_layout.addWidget(undo_history_button)
|
|
78
|
+
corner_layout.addWidget(add_tab_button)
|
|
79
|
+
self.tab_widget.setCornerWidget(corner_widget, Qt.Corner.TopRightCorner)
|
|
80
|
+
|
|
81
|
+
saved_geometry = app_settings.WINDOW_GEOMETRY.read(self._settings)
|
|
82
|
+
if saved_geometry is not None:
|
|
83
|
+
# 先にネイティブのハンドルを作る。窓が実体化する前に restoreGeometry() すると枠の幅が確定しておらず、
|
|
84
|
+
# 画面上の位置の認識がずれて、ポップアップやクリックの位置が全部ずれる
|
|
85
|
+
self.winId()
|
|
86
|
+
self.restoreGeometry(saved_geometry)
|
|
87
|
+
else:
|
|
88
|
+
self.resize(DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT)
|
|
89
|
+
|
|
90
|
+
# 最初のタブは起動時の確認をするタブ
|
|
91
|
+
self.add_new_project_tab(run_startup_checks=True)
|
|
92
|
+
|
|
93
|
+
def _create_undo_history_dock(self):
|
|
94
|
+
"""表示中のタブの Undo 履歴(既定では隠す)。"""
|
|
95
|
+
self.undo_history_dock = QDockWidget("Undo履歴", self)
|
|
96
|
+
self.undo_history_dock.setObjectName("undo_history_dock")
|
|
97
|
+
undo_view = QUndoView(self.undo_group, self.undo_history_dock)
|
|
98
|
+
self.undo_history_dock.setWidget(undo_view)
|
|
99
|
+
self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.undo_history_dock)
|
|
100
|
+
self.undo_history_dock.setVisible(False)
|
|
101
|
+
|
|
102
|
+
def add_new_project_tab(self, run_startup_checks=False):
|
|
103
|
+
tab_id = self._next_tab_id
|
|
104
|
+
self._next_tab_id += 1
|
|
105
|
+
|
|
106
|
+
project_window = PlotterApp(
|
|
107
|
+
run_startup_checks=run_startup_checks,
|
|
108
|
+
tab_id=(None if run_startup_checks else tab_id),
|
|
109
|
+
)
|
|
110
|
+
project_window.project_state_changed.connect(
|
|
111
|
+
lambda pw=project_window: self._refresh_tab_title(pw)
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
self.undo_group.addStack(project_window.undo_stack)
|
|
115
|
+
|
|
116
|
+
# 埋め込んだ QMainWindow は最上位ウィンドウのフラグが残るので外す。親を確定させてから1回だけ変える
|
|
117
|
+
# (変更と付け替えの2段階にすると、macOS でタブが入力を受け付けなくなる)
|
|
118
|
+
index = self.tab_widget.addTab(project_window, self._tab_title_for(project_window))
|
|
119
|
+
project_window.setWindowFlags(Qt.WindowType.Widget)
|
|
120
|
+
self.tab_widget.setCurrentIndex(index)
|
|
121
|
+
project_window.show()
|
|
122
|
+
return project_window
|
|
123
|
+
|
|
124
|
+
def open_files(self, paths, reuse_empty_tab=False):
|
|
125
|
+
"""外から渡されたファイル(起動の引数・起動中のアプリへの受け渡し・macOS の Finder)を開く。
|
|
126
|
+
|
|
127
|
+
プロジェクトは 1 つずつ新しいタブに開き、データファイルは最後に開いたタブ(無ければ新しいタブ)に加える。
|
|
128
|
+
reuse_empty_tab なら、今のタブが空で未保存の変更も無いときだけ最初のプロジェクトをそこに開く(起動直後)。
|
|
129
|
+
"""
|
|
130
|
+
project_suffixes = PROJECT_FILE_EXTENSIONS + (LEGACY_PICKLE_EXTENSION,)
|
|
131
|
+
project_paths = [p for p in paths if p.lower().endswith(project_suffixes)]
|
|
132
|
+
data_paths = [p for p in paths if not p.lower().endswith(project_suffixes)]
|
|
133
|
+
|
|
134
|
+
target = None
|
|
135
|
+
current = self.tab_widget.currentWidget()
|
|
136
|
+
if reuse_empty_tab and current is not None and not current.project.datasets \
|
|
137
|
+
and not current.has_unsaved_changes():
|
|
138
|
+
target = current
|
|
139
|
+
for index, path in enumerate(project_paths):
|
|
140
|
+
if target is None or index > 0:
|
|
141
|
+
target = self.add_new_project_tab()
|
|
142
|
+
target._load_project_from_path(path)
|
|
143
|
+
if data_paths:
|
|
144
|
+
if target is None:
|
|
145
|
+
target = current if reuse_empty_tab and current is not None else self.add_new_project_tab()
|
|
146
|
+
target._queue_data_files(data_paths)
|
|
147
|
+
self.bring_to_front()
|
|
148
|
+
|
|
149
|
+
def bring_to_front(self):
|
|
150
|
+
if self.isMinimized():
|
|
151
|
+
self.showNormal()
|
|
152
|
+
self.raise_()
|
|
153
|
+
self.activateWindow()
|
|
154
|
+
|
|
155
|
+
def _tab_title_for(self, project_window):
|
|
156
|
+
return project_window.document_title()
|
|
157
|
+
|
|
158
|
+
def _refresh_tab_title(self, project_window):
|
|
159
|
+
index = self.tab_widget.indexOf(project_window)
|
|
160
|
+
if index != -1:
|
|
161
|
+
self.tab_widget.setTabText(index, self._tab_title_for(project_window))
|
|
162
|
+
if index == self.tab_widget.currentIndex():
|
|
163
|
+
self._update_window_title(project_window)
|
|
164
|
+
|
|
165
|
+
def _update_window_title(self, project_window):
|
|
166
|
+
self.setWindowTitle(f"{APP_NAME} {__version__} - {self._tab_title_for(project_window)}")
|
|
167
|
+
|
|
168
|
+
def _on_current_tab_changed(self, index):
|
|
169
|
+
if index == -1:
|
|
170
|
+
return
|
|
171
|
+
project_window = self.tab_widget.widget(index)
|
|
172
|
+
if project_window is not None:
|
|
173
|
+
self._update_window_title(project_window)
|
|
174
|
+
self.undo_group.setActiveStack(project_window.undo_stack)
|
|
175
|
+
|
|
176
|
+
def _on_tab_close_requested(self, index):
|
|
177
|
+
"""最後の1つは閉じられない。"""
|
|
178
|
+
if self.tab_widget.count() <= 1:
|
|
179
|
+
notify.information(
|
|
180
|
+
self, "タブを閉じる", "最後の1つのタブは閉じられません。"
|
|
181
|
+
)
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
project_window = self.tab_widget.widget(index)
|
|
185
|
+
if project_window is not None and not project_window.confirm_unsaved_changes("タブを閉じる"):
|
|
186
|
+
return
|
|
187
|
+
self.tab_widget.removeTab(index)
|
|
188
|
+
if project_window is not None:
|
|
189
|
+
# 閉じた直後から履歴パネルの対象に残らないよう、明示的に外す
|
|
190
|
+
self.undo_group.removeStack(project_window.undo_stack)
|
|
191
|
+
project_window.close()
|
|
192
|
+
project_window.deleteLater()
|
|
193
|
+
|
|
194
|
+
def closeEvent(self, event):
|
|
195
|
+
"""全部のタブに正常終了の処理をさせてから閉じる。"""
|
|
196
|
+
# 未保存の変更は、そのタブを前に出して尋ねる。全部尋ねてから閉じ始めるので、1つでもキャンセルなら何も閉じない
|
|
197
|
+
for index in range(self.tab_widget.count()):
|
|
198
|
+
project_window = self.tab_widget.widget(index)
|
|
199
|
+
if project_window is None or not project_window.has_unsaved_changes():
|
|
200
|
+
continue
|
|
201
|
+
self.tab_widget.setCurrentIndex(index)
|
|
202
|
+
if not project_window.confirm_unsaved_changes("Graphica を終了する"):
|
|
203
|
+
event.ignore()
|
|
204
|
+
return
|
|
205
|
+
app_settings.WINDOW_GEOMETRY.write(self._settings, self.saveGeometry())
|
|
206
|
+
for index in range(self.tab_widget.count()):
|
|
207
|
+
project_window = self.tab_widget.widget(index)
|
|
208
|
+
if project_window is not None:
|
|
209
|
+
project_window.close()
|
|
210
|
+
super().closeEvent(event)
|