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.
Files changed (181) hide show
  1. graphica/Graphica.ico +0 -0
  2. graphica/__init__.py +1 -0
  3. graphica/__main__.py +111 -0
  4. graphica/assets/__init__.py +0 -0
  5. graphica/assets/icons/__init__.py +0 -0
  6. graphica/assets/icons/arrow-right.svg +21 -0
  7. graphica/assets/icons/bold.svg +20 -0
  8. graphica/assets/icons/calculator.svg +26 -0
  9. graphica/assets/icons/chart-histogram.svg +24 -0
  10. graphica/assets/icons/chart-line.svg +20 -0
  11. graphica/assets/icons/chevron-down.svg +19 -0
  12. graphica/assets/icons/chevron-right.svg +19 -0
  13. graphica/assets/icons/color-swatch.svg +22 -0
  14. graphica/assets/icons/column-insert-right.svg +21 -0
  15. graphica/assets/icons/column-remove.svg +21 -0
  16. graphica/assets/icons/copy.svg +20 -0
  17. graphica/assets/icons/download.svg +21 -0
  18. graphica/assets/icons/edit.svg +21 -0
  19. graphica/assets/icons/eye-off.svg +21 -0
  20. graphica/assets/icons/eye.svg +20 -0
  21. graphica/assets/icons/file-plus.svg +22 -0
  22. graphica/assets/icons/folder-plus.svg +21 -0
  23. graphica/assets/icons/folder.svg +19 -0
  24. graphica/assets/icons/highlight.svg +22 -0
  25. graphica/assets/icons/history.svg +20 -0
  26. graphica/assets/icons/italic.svg +21 -0
  27. graphica/assets/icons/layout-grid.svg +22 -0
  28. graphica/assets/icons/math-function.svg +22 -0
  29. graphica/assets/icons/message-2.svg +21 -0
  30. graphica/assets/icons/mountain.svg +20 -0
  31. graphica/assets/icons/palette.svg +22 -0
  32. graphica/assets/icons/pointer.svg +19 -0
  33. graphica/assets/icons/refresh.svg +20 -0
  34. graphica/assets/icons/row-insert-bottom.svg +21 -0
  35. graphica/assets/icons/row-remove.svg +21 -0
  36. graphica/assets/icons/search.svg +20 -0
  37. graphica/assets/icons/select-all.svg +35 -0
  38. graphica/assets/icons/subscript.svg +20 -0
  39. graphica/assets/icons/superscript.svg +20 -0
  40. graphica/assets/icons/table.svg +21 -0
  41. graphica/assets/icons/trash.svg +23 -0
  42. graphica/assets/icons/typography.svg +23 -0
  43. graphica/assets/icons/x.svg +20 -0
  44. graphica/core/__init__.py +0 -0
  45. graphica/core/analysis.py +1278 -0
  46. graphica/core/app_paths.py +35 -0
  47. graphica/core/axis_settings.py +128 -0
  48. graphica/core/caption_export.py +47 -0
  49. graphica/core/color_palettes.py +56 -0
  50. graphica/core/commands.py +226 -0
  51. graphica/core/cvd_simulation.py +49 -0
  52. graphica/core/dataset.py +443 -0
  53. graphica/core/diagnostics.py +105 -0
  54. graphica/core/excel_utils.py +58 -0
  55. graphica/core/fit_models.py +379 -0
  56. graphica/core/grid_data.py +128 -0
  57. graphica/core/i18n.py +44 -0
  58. graphica/core/json_utils.py +27 -0
  59. graphica/core/label_utils.py +21 -0
  60. graphica/core/methods_text.py +115 -0
  61. graphica/core/named_colors.py +126 -0
  62. graphica/core/plugin_api.py +515 -0
  63. graphica/core/plugin_context.py +167 -0
  64. graphica/core/plugin_install.py +82 -0
  65. graphica/core/plugin_manifest.py +67 -0
  66. graphica/core/plugin_testing.py +213 -0
  67. graphica/core/plugin_types.py +118 -0
  68. graphica/core/provenance.py +19 -0
  69. graphica/core/report_export.py +51 -0
  70. graphica/core/safe_eval.py +194 -0
  71. graphica/core/script_export.py +272 -0
  72. graphica/core/translations_en.py +530 -0
  73. graphica/core/unit_conversion.py +51 -0
  74. graphica/core/update_check.py +43 -0
  75. graphica/core/version.py +14 -0
  76. graphica/gui/__init__.py +0 -0
  77. graphica/gui/app_settings.py +119 -0
  78. graphica/gui/axis_bindings.py +173 -0
  79. graphica/gui/binding.py +135 -0
  80. graphica/gui/builders/__init__.py +1 -0
  81. graphica/gui/builders/axis_panel.py +431 -0
  82. graphica/gui/builders/canvas_area.py +176 -0
  83. graphica/gui/builders/common.py +120 -0
  84. graphica/gui/builders/dataset_panel.py +433 -0
  85. graphica/gui/builders/property_sections.py +208 -0
  86. graphica/gui/canvas.py +519 -0
  87. graphica/gui/color_history.py +47 -0
  88. graphica/gui/color_picker_widget.py +198 -0
  89. graphica/gui/crash_handler.py +77 -0
  90. graphica/gui/cvd_preview.py +23 -0
  91. graphica/gui/data_editor.py +761 -0
  92. graphica/gui/data_import_flow.py +355 -0
  93. graphica/gui/dataset_bindings.py +107 -0
  94. graphica/gui/dataset_style_icon.py +80 -0
  95. graphica/gui/datasets/__init__.py +1 -0
  96. graphica/gui/datasets/actions_menu.py +114 -0
  97. graphica/gui/datasets/colors.py +218 -0
  98. graphica/gui/datasets/fitting.py +70 -0
  99. graphica/gui/datasets/host.py +162 -0
  100. graphica/gui/datasets/operations/__init__.py +1 -0
  101. graphica/gui/datasets/operations/fitting.py +468 -0
  102. graphica/gui/datasets/operations/peaks.py +109 -0
  103. graphica/gui/datasets/operations/processing.py +659 -0
  104. graphica/gui/datasets/operations/runner.py +148 -0
  105. graphica/gui/datasets/operations/transfer.py +227 -0
  106. graphica/gui/datasets/order.py +310 -0
  107. graphica/gui/datasets/overlays.py +74 -0
  108. graphica/gui/datasets/peaks.py +21 -0
  109. graphica/gui/datasets/plugin_runs.py +99 -0
  110. graphica/gui/datasets/processing.py +68 -0
  111. graphica/gui/datasets/property_panel.py +538 -0
  112. graphica/gui/datasets/transfer.py +36 -0
  113. graphica/gui/detached_canvas_window.py +22 -0
  114. graphica/gui/dialogs/__init__.py +137 -0
  115. graphica/gui/dialogs/analysis.py +1322 -0
  116. graphica/gui/dialogs/app.py +1015 -0
  117. graphica/gui/dialogs/appearance.py +851 -0
  118. graphica/gui/dialogs/data_edit.py +577 -0
  119. graphica/gui/dialogs/data_import.py +679 -0
  120. graphica/gui/dialogs/export.py +433 -0
  121. graphica/gui/dock_layout.py +176 -0
  122. graphica/gui/export_preview_panel.py +321 -0
  123. graphica/gui/export_settings.py +16 -0
  124. graphica/gui/file_association.py +97 -0
  125. graphica/gui/icon_utils.py +44 -0
  126. graphica/gui/main_app_window.py +210 -0
  127. graphica/gui/main_window.py +1151 -0
  128. graphica/gui/mathtext_preview.py +168 -0
  129. graphica/gui/menu_bar.py +320 -0
  130. graphica/gui/minimap_widget.py +151 -0
  131. graphica/gui/mixins/__init__.py +0 -0
  132. graphica/gui/mixins/export_mixin.py +520 -0
  133. graphica/gui/mixins/help_mixin.py +127 -0
  134. graphica/gui/mixins/project_io_mixin.py +289 -0
  135. graphica/gui/mixins/quick_access_mixin.py +194 -0
  136. graphica/gui/mixins/ui_setup_mixin.py +206 -0
  137. graphica/gui/notify.py +61 -0
  138. graphica/gui/panels/__init__.py +43 -0
  139. graphica/gui/panels/axis_settings.py +605 -0
  140. graphica/gui/panels/dataset_tree.py +424 -0
  141. graphica/gui/plot_type_drawers.py +131 -0
  142. graphica/gui/plugin_context.py +119 -0
  143. graphica/gui/project_files.py +361 -0
  144. graphica/gui/provenance_panel.py +65 -0
  145. graphica/gui/rendering/__init__.py +1 -0
  146. graphica/gui/rendering/annotations.py +177 -0
  147. graphica/gui/rendering/appearance.py +368 -0
  148. graphica/gui/rendering/common.py +316 -0
  149. graphica/gui/rendering/data_1d.py +376 -0
  150. graphica/gui/rendering/data_2d.py +66 -0
  151. graphica/gui/residual_panel.py +62 -0
  152. graphica/gui/resources.py +16 -0
  153. graphica/gui/single_instance.py +141 -0
  154. graphica/gui/splash.py +83 -0
  155. graphica/gui/task_runner.py +41 -0
  156. graphica/gui/theme.py +864 -0
  157. graphica/gui/tools/__init__.py +56 -0
  158. graphica/gui/tools/annotation.py +275 -0
  159. graphica/gui/tools/cursor.py +259 -0
  160. graphica/gui/tools/layout_edit.py +285 -0
  161. graphica/gui/tools/manager.py +78 -0
  162. graphica/gui/tools/peak_placement.py +132 -0
  163. graphica/gui/tools/range_select.py +191 -0
  164. graphica/gui/tools/region_highlight.py +222 -0
  165. graphica/gui/tools/slice_extraction.py +200 -0
  166. graphica/gui/widget_translation.py +46 -0
  167. graphica/gui/workers.py +280 -0
  168. graphica/models/__init__.py +0 -0
  169. graphica/models/project.py +197 -0
  170. graphica/plugin/__init__.py +20 -0
  171. graphica/plugin/testing.py +57 -0
  172. graphica/sample_data/__init__.py +0 -0
  173. graphica/sample_data/cooling_curve_sample.csv +42 -0
  174. graphica/ui_main_window.py +581 -0
  175. graphica_plot-2.0.0.dist-info/METADATA +317 -0
  176. graphica_plot-2.0.0.dist-info/RECORD +181 -0
  177. graphica_plot-2.0.0.dist-info/WHEEL +5 -0
  178. graphica_plot-2.0.0.dist-info/entry_points.txt +2 -0
  179. graphica_plot-2.0.0.dist-info/licenses/LICENSE +30 -0
  180. graphica_plot-2.0.0.dist-info/licenses/THIRD_PARTY_LICENSES.md +91 -0
  181. 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)