Coverage for graphica/gui/dialogs/data_edit.py: 100.0%

359 statements  

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

1"""データの編集と列の操作のダイアログ。呼び出し側は `from graphica.gui.dialogs import X` で参照する。""" 

2 

3from PySide6.QtWidgets import ( 

4 QComboBox, 

5 QDialog, 

6 QDialogButtonBox, 

7 QFormLayout, 

8 QGroupBox, 

9 QHBoxLayout, 

10 QLabel, 

11 QLineEdit, 

12 QListWidget, 

13 QListWidgetItem, 

14 QPushButton, 

15 QSpinBox, 

16 QStackedWidget, 

17 QTextBrowser, 

18 QVBoxLayout, 

19 QWidget, 

20) 

21from PySide6.QtCore import Qt 

22from graphica.core.safe_eval import column_reference 

23from graphica.gui.theme import apply_form_spacing 

24 

25 

26class ColumnCalculatorDialog(QDialog): 

27 """列の計算の出力先の列と式(式は safe_eval_column_formula で評価する)。""" 

28 

29 def __init__(self, column_names, parent=None): 

30 super().__init__(parent) 

31 self.setWindowTitle("列の計算") 

32 

33 self.column_names = column_names 

34 

35 

36 self.output_col_label = QLabel("出力先の列 (既存または新規)") 

37 

38 self.output_col_combo = QComboBox() 

39 self.output_col_combo.addItems(self.column_names) 

40 # 新しい列名も打ち込めるように 

41 self.output_col_combo.setEditable(True) 

42 

43 self.formula_label = QLabel("計算式 (例: A + B * 2)") 

44 

45 self.formula_edit = QLineEdit() 

46 self.formula_edit.setPlaceholderText("例: (A + B) / 2 や log(C)") 

47 

48 help_text = QLabel("列名はそのまま使えます (例: `A`)。\n数値や `log(A)`, `sin(A)` なども利用可能です。") 

49 help_text.setStyleSheet("font-size: 9pt; color: gray;") 

50 

51 preset_group = QGroupBox("プリセット (対象列を選んでボタンを押すと計算式が自動入力されます)") 

52 preset_layout = QVBoxLayout() 

53 

54 preset_source_row = QHBoxLayout() 

55 preset_source_row.addWidget(QLabel("対象列")) 

56 self.preset_source_combo = QComboBox() 

57 self.preset_source_combo.addItems(self.column_names) 

58 preset_source_row.addWidget(self.preset_source_combo) 

59 preset_source_row.addWidget(QLabel("移動平均の窓幅")) 

60 self.preset_window_spinbox = QSpinBox() 

61 self.preset_window_spinbox.setRange(2, 100000) 

62 self.preset_window_spinbox.setValue(5) 

63 preset_source_row.addWidget(self.preset_window_spinbox) 

64 preset_layout.addLayout(preset_source_row) 

65 

66 preset_button_row = QHBoxLayout() 

67 moving_avg_button = QPushButton("移動平均") 

68 moving_avg_button.clicked.connect(self._apply_preset_moving_average) 

69 diff_button = QPushButton("微分(差分)") 

70 diff_button.clicked.connect(self._apply_preset_diff) 

71 normalize_button = QPushButton("正規化") 

72 normalize_button.clicked.connect(self._apply_preset_normalize) 

73 cumsum_button = QPushButton("累積和") 

74 cumsum_button.clicked.connect(self._apply_preset_cumsum) 

75 for btn in (moving_avg_button, diff_button, normalize_button, cumsum_button): 

76 preset_button_row.addWidget(btn) 

77 preset_layout.addLayout(preset_button_row) 

78 

79 preset_group.setLayout(preset_layout) 

80 

81 button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | 

82 QDialogButtonBox.StandardButton.Cancel) 

83 button_box.accepted.connect(self.accept) 

84 button_box.rejected.connect(self.reject) 

85 

86 layout = QVBoxLayout(self) 

87 layout.addWidget(self.output_col_label) 

88 layout.addWidget(self.output_col_combo) 

89 layout.addWidget(self.formula_label) 

90 layout.addWidget(self.formula_edit) 

91 layout.addWidget(help_text) 

92 layout.addWidget(preset_group) 

93 layout.addWidget(button_box) 

94 

95 def _apply_preset_moving_average(self): 

96 name = self.preset_source_combo.currentText() 

97 if not name: 

98 return 

99 col = column_reference(name) 

100 window = self.preset_window_spinbox.value() 

101 self.formula_edit.setText(f"{col}.rolling({window}).mean()") 

102 self.output_col_combo.setCurrentText(f"{name}_moving_avg{window}") 

103 

104 def _apply_preset_diff(self): 

105 name = self.preset_source_combo.currentText() 

106 if not name: 

107 return 

108 col = column_reference(name) 

109 self.formula_edit.setText(f"{col}.diff()") 

110 self.output_col_combo.setCurrentText(f"{name}_diff") 

111 

112 def _apply_preset_normalize(self): 

113 """平均0・標準偏差1にする式。""" 

114 name = self.preset_source_combo.currentText() 

115 if not name: 

116 return 

117 col = column_reference(name) 

118 self.formula_edit.setText(f"({col} - {col}.mean()) / {col}.std()") 

119 self.output_col_combo.setCurrentText(f"{name}_normalized") 

120 

121 def _apply_preset_cumsum(self): 

122 name = self.preset_source_combo.currentText() 

123 if not name: 

124 return 

125 col = column_reference(name) 

126 self.formula_edit.setText(f"{col}.cumsum()") 

127 self.output_col_combo.setCurrentText(f"{name}_cumsum") 

128 

129 def get_formula(self): 

130 """(出力先の列名, 式)""" 

131 output_column_name = self.output_col_combo.currentText() 

132 formula_string = self.formula_edit.text() 

133 

134 return output_column_name, formula_string 

135 

136 

137class CalcHelpDialog(QDialog): 

138 """列の計算で使える書き方のリファレンス。""" 

139 

140 def __init__(self, parent=None): 

141 super().__init__(parent) 

142 self.setWindowTitle("列計算機能 リファレンス") 

143 self.resize(600, 700) 

144 

145 layout = QVBoxLayout(self) 

146 

147 text_browser = QTextBrowser() 

148 text_browser.setReadOnly(True) 

149 text_browser.setOpenExternalLinks(True) 

150 

151 help_html = r""" 

152 <h1>列計算機能 リファレンス</h1> 

153 <p> 

154 計算式を使って、列データをまとめて計算します。 

155 「出力先の列」に指定した列に、計算式の結果が一度に適用されます(Excelのオートフィルのように、全行に適用されます)。 

156 </p> 

157  

158 <hr> 

159  

160 <h2>1. 基本的な算術演算子 🧮</h2> 

161 <p>列名(例: <code>A</code>, <code>B</code>)や数値をそのまま使えます。空白や記号を含む列名は <code>`強度 (a.u.)`</code> のようにバッククォートで囲みます。</p> 

162  

163 <table border="1" cellpadding="5" cellspacing="0" style="border-collapse: collapse;"> 

164 <tr class="header-row"><th>計算式 (入力例)</th><th>実行内容</th></tr> 

165 <tr><td><code>A + B</code></td><td>A列とB列の各行を足し算します。</td></tr> 

166 <tr><td><code>A * 100</code></td><td>A列の全データを100倍します。</td></tr> 

167 <tr><td><code>(A + B) / 2</code></td><td>A列とB列の平均値を計算します。</td></tr> 

168 <tr><td><code>A ** 2</code></td><td>A列の値を2乗します。</td></tr> 

169 <tr><td><code>A % 5</code></td><td>A列の値を5で割った余りを計算します。</td></tr> 

170 </table> 

171  

172 <hr> 

173  

174 <h2>2. 一般的な数学関数 📈</h2> 

175 <p> 

176 以下の関数が利用可能です。 

177 </p> 

178  

179 <table border="1" cellpadding="5" cellspacing="0" style="border-collapse: collapse;"> 

180 <tr class="header-row"><th>関数 (入力例)</th><th>意味</th></tr> 

181 <tr><td><code>sqrt(A)</code></td><td>Aの平方根 (&radic;A)</td></tr> 

182 <tr><td><code>log(A)</code></td><td>Aの自然対数 (ln A)</td></tr> 

183 <tr><td><code>log10(A)</code></td><td>Aの常用対数 (log₁₀ A)</td></tr> 

184 <tr><td><code>exp(A)</code></td><td>Aの指数関数 (e<sup>A</sup>)</td></tr> 

185 <tr><td><code>abs(A)</code></td><td>Aの絶対値</td></tr> 

186 <tr><td><code>sin(A)</code></td><td>Aのサイン (ラジアン)</td></tr> 

187 <tr><td><code>cos(A)</code></td><td>Aのコサイン (ラジアン)</td></tr> 

188 <tr><td><code>tan(A)</code></td><td>Aのタンジェント (ラジアン)</td></tr> 

189 </table> 

190 

191 <hr> 

192  

193 <h2>3. 比較・論理演算子 🔍</h2> 

194 <p> 

195 条件に合うかどうかを <code>True</code> / <code>False</code> で返す新しい列を作成できます。 

196 複数の条件を組み合わせる場合は <code>and</code>, <code>or</code>, <code>not</code> を使います。 

197 </p> 

198  

199 <table border="1" cellpadding="5" cellspacing="0" style="border-collapse: collapse;"> 

200 <tr class="header-row"><th>計算式 (入力例)</th><th>実行内容</th></tr> 

201 <tr><td><code>A > 10</code></td><td>A列の値が10より大きい行はTrueになります。</td></tr> 

202 <tr><td><code>A == B</code></td><td>A列とB列の値が等しい行はTrueになります。</td></tr> 

203 <tr><td><code>A > 5 and B < 3</code></td><td>Aが5より大きく、<b>かつ</b> Bが3未満の行だけTrueになります。</td></tr> 

204 <tr><td><code>A < 0 or A > 10</code></td><td>Aが0未満、<b>または</b> Aが10より大きい行がTrueになります。</td></tr> 

205 <tr><td><code>not (A > 5)</code></td><td>Aが5より大きい、という条件を否定します (A <= 5 と同じ)。</td></tr> 

206 </table> 

207 """ 

208 self._text_browser = text_browser 

209 self.refresh_theme() 

210 text_browser.setHtml(help_html) 

211 

212 layout.addWidget(text_browser) 

213 

214 button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Close) 

215 button_box.rejected.connect(self.reject) 

216 layout.addWidget(button_box) 

217 

218 def refresh_theme(self): 

219 """開いたままダークモードを切り替えられるので、見出し行の色を今のテーマで当て直す。""" 

220 from graphica.gui import theme 

221 _tokens = theme.current_tokens() 

222 self._text_browser.document().setDefaultStyleSheet( 

223 f"tr.header-row {{ background-color: {_tokens['surface_2']}; " 

224 f"color: {_tokens['text_primary']}; }}" 

225 ) 

226 

227 

228class ColumnStringOpsDialog(QDialog): 

229 """列の分割・結合・数値の抽出の設定。処理は呼び出し側(DataEditorDialog)が行う。""" 

230 

231 MODE_SPLIT = "列の分割" 

232 MODE_MERGE = "列の結合" 

233 MODE_EXTRACT_NUMERIC = "数値抽出" 

234 MODES = [MODE_SPLIT, MODE_MERGE, MODE_EXTRACT_NUMERIC] 

235 

236 def __init__(self, column_names, parent=None): 

237 super().__init__(parent) 

238 self.setWindowTitle("文字列操作") 

239 self.resize(420, 400) 

240 self.column_names = column_names 

241 

242 layout = QVBoxLayout(self) 

243 

244 mode_form = QFormLayout() 

245 self.mode_combo = QComboBox() 

246 self.mode_combo.addItems(self.MODES) 

247 mode_form.addRow("操作", self.mode_combo) 

248 layout.addLayout(mode_form) 

249 

250 self.stack = QStackedWidget() 

251 layout.addWidget(self.stack) 

252 

253 split_page = QWidget() 

254 split_form = QFormLayout(split_page) 

255 self.split_source_combo = QComboBox() 

256 self.split_source_combo.addItems(column_names) 

257 split_form.addRow("対象列", self.split_source_combo) 

258 self.split_delimiter_edit = QLineEdit() 

259 self.split_delimiter_edit.setPlaceholderText("例: , や _ (空白なら半角スペース)") 

260 split_form.addRow("区切り文字", self.split_delimiter_edit) 

261 self.split_prefix_edit = QLineEdit() 

262 self.split_prefix_edit.setPlaceholderText("例: part (空欄なら対象列名を使用)") 

263 split_form.addRow("出力列名の接頭辞", self.split_prefix_edit) 

264 self.stack.addWidget(split_page) 

265 

266 merge_page = QWidget() 

267 merge_layout = QVBoxLayout(merge_page) 

268 merge_layout.addWidget(QLabel("結合する列を2つ以上選択してください:")) 

269 self.merge_column_list = QListWidget() 

270 for name in column_names: 

271 item = QListWidgetItem(str(name)) 

272 item.setFlags(item.flags() | Qt.ItemFlag.ItemIsUserCheckable) 

273 item.setCheckState(Qt.CheckState.Unchecked) 

274 self.merge_column_list.addItem(item) 

275 merge_layout.addWidget(self.merge_column_list) 

276 merge_form = QFormLayout() 

277 self.merge_separator_edit = QLineEdit() 

278 self.merge_separator_edit.setPlaceholderText("例: _ (空欄可)") 

279 merge_form.addRow("区切り文字", self.merge_separator_edit) 

280 self.merge_output_edit = QLineEdit() 

281 merge_form.addRow("出力列名", self.merge_output_edit) 

282 merge_layout.addLayout(merge_form) 

283 self.stack.addWidget(merge_page) 

284 

285 extract_page = QWidget() 

286 extract_form = QFormLayout(extract_page) 

287 self.extract_source_combo = QComboBox() 

288 self.extract_source_combo.addItems(column_names) 

289 extract_form.addRow("対象列", self.extract_source_combo) 

290 self.extract_pattern_edit = QLineEdit(r'[-+]?\d*\.?\d+') 

291 extract_form.addRow("正規表現", self.extract_pattern_edit) 

292 extract_help = QLabel('既定は数値(整数/小数、符号付き)を抽出します。例: "400nm" → 400') 

293 extract_help.setWordWrap(True) 

294 extract_help.setStyleSheet("font-size: 9pt; color: gray;") 

295 extract_form.addRow(extract_help) 

296 self.extract_output_edit = QLineEdit() 

297 extract_form.addRow("出力列名", self.extract_output_edit) 

298 self.stack.addWidget(extract_page) 

299 

300 self.mode_combo.currentIndexChanged.connect(self.stack.setCurrentIndex) 

301 

302 button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | 

303 QDialogButtonBox.StandardButton.Cancel) 

304 button_box.accepted.connect(self.accept) 

305 button_box.rejected.connect(self.reject) 

306 layout.addWidget(button_box) 

307 

308 apply_form_spacing(self) 

309 

310 def get_mode(self): 

311 return self.mode_combo.currentText() 

312 

313 def get_split_settings(self): 

314 """(対象の列, 区切り文字, 出力の列名の接頭辞)""" 

315 return ( 

316 self.split_source_combo.currentText(), 

317 self.split_delimiter_edit.text(), 

318 self.split_prefix_edit.text().strip(), 

319 ) 

320 

321 def get_merge_settings(self): 

322 """(列名のリスト, 区切り文字, 出力の列名)""" 

323 selected = [] 

324 for i in range(self.merge_column_list.count()): 

325 item = self.merge_column_list.item(i) 

326 if item.checkState() == Qt.CheckState.Checked: 

327 selected.append(item.text()) 

328 return selected, self.merge_separator_edit.text(), self.merge_output_edit.text().strip() 

329 

330 def get_extract_settings(self): 

331 """(対象の列, 正規表現, 出力の列名)""" 

332 return ( 

333 self.extract_source_combo.currentText(), 

334 self.extract_pattern_edit.text(), 

335 self.extract_output_edit.text().strip(), 

336 ) 

337 

338 

339class FindReplaceDialog(QDialog): 

340 """検索と置換。入力欄とボタンだけで、処理は DataEditorDialog が持つ。表を見ながら使うので非モーダル。""" 

341 

342 def __init__(self, column_names, parent=None): 

343 super().__init__(parent) 

344 self.setWindowTitle("検索/置換") 

345 # 非モーダルなので、データエディタの裏に隠れないよう上に浮かせる 

346 self.setWindowFlag(Qt.WindowType.Tool, True) 

347 self.resize(340, 200) 

348 

349 layout = QVBoxLayout(self) 

350 form = QFormLayout() 

351 self.search_edit = QLineEdit() 

352 form.addRow("検索文字列", self.search_edit) 

353 self.replace_edit = QLineEdit() 

354 form.addRow("置換後の文字列", self.replace_edit) 

355 self.column_combo = QComboBox() 

356 self.column_combo.addItem("(すべての列)") 

357 self.column_combo.addItems(column_names) 

358 form.addRow("対象列", self.column_combo) 

359 layout.addLayout(form) 

360 

361 button_row = QHBoxLayout() 

362 self.find_next_button = QPushButton("次を検索") 

363 self.replace_all_button = QPushButton("すべて置換") 

364 button_row.addWidget(self.find_next_button) 

365 button_row.addWidget(self.replace_all_button) 

366 button_row.addStretch() 

367 close_button = QPushButton("閉じる") 

368 close_button.clicked.connect(self.close) 

369 button_row.addWidget(close_button) 

370 layout.addLayout(button_row) 

371 

372 self.status_label = QLabel("") 

373 self.status_label.setWordWrap(True) 

374 layout.addWidget(self.status_label) 

375 

376 apply_form_spacing(self) 

377 

378 def get_search_text(self): 

379 return self.search_edit.text() 

380 

381 def get_replace_text(self): 

382 return self.replace_edit.text() 

383 

384 def get_target_column(self): 

385 """対象の列。「(すべての列)」なら None。""" 

386 text = self.column_combo.currentText() 

387 return None if text == "(すべての列)" else text 

388 

389 def set_status(self, text): 

390 self.status_label.setText(text) 

391 

392 

393class RowFilterDialog(QDialog): 

394 """条件式(例: "y > 0.5")を満たさない行をマスクする。式は列の計算と同じ規則。""" 

395 

396 def __init__(self, column_names, parent=None): 

397 super().__init__(parent) 

398 self.setWindowTitle("行フィルタ") 

399 self.resize(440, 240) 

400 

401 layout = QVBoxLayout(self) 

402 layout.addWidget(QLabel("条件式を満たさない行をマスク(除外)します(非破壊、いつでも解除できます)。")) 

403 

404 self.formula_edit = QLineEdit() 

405 self.formula_edit.setPlaceholderText("例: y > 0.5 や (x > 0) and (y < 100)") 

406 layout.addWidget(self.formula_edit) 

407 

408 help_text = QLabel( 

409 "列名はそのまま使えます(例: y)。比較演算子(>, <, ==, != 等)や\n" 

410 "and / or / not が使えます(core/safe_eval.pyの安全な数式評価器)。" 

411 ) 

412 help_text.setStyleSheet("font-size: 9pt; color: gray;") 

413 help_text.setWordWrap(True) 

414 layout.addWidget(help_text) 

415 

416 if column_names: 

417 columns_label = QLabel("利用可能な列: " + ", ".join(column_reference(str(c)) for c in column_names)) 

418 columns_label.setWordWrap(True) 

419 columns_label.setStyleSheet("font-size: 9pt; color: gray;") 

420 layout.addWidget(columns_label) 

421 

422 button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | 

423 QDialogButtonBox.StandardButton.Cancel) 

424 button_box.accepted.connect(self.accept) 

425 button_box.rejected.connect(self.reject) 

426 layout.addWidget(button_box) 

427 

428 apply_form_spacing(self) 

429 

430 def get_formula(self): 

431 return self.formula_edit.text().strip() 

432 

433 

434class ColumnVisibilityDialog(QDialog): 

435 """チェックを外した列を表で隠す(表示だけで dataset.df は変えない)。""" 

436 

437 def __init__(self, column_names, hidden_columns, parent=None): 

438 super().__init__(parent) 

439 self.setWindowTitle("列の表示/非表示") 

440 self.resize(280, 380) 

441 

442 layout = QVBoxLayout(self) 

443 layout.addWidget(QLabel("表示する列にチェックを入れてください:")) 

444 

445 self.column_list = QListWidget() 

446 for name in column_names: 

447 item = QListWidgetItem(str(name)) 

448 item.setFlags(item.flags() | Qt.ItemFlag.ItemIsUserCheckable) 

449 state = Qt.CheckState.Unchecked if name in hidden_columns else Qt.CheckState.Checked 

450 item.setCheckState(state) 

451 self.column_list.addItem(item) 

452 layout.addWidget(self.column_list) 

453 

454 button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | 

455 QDialogButtonBox.StandardButton.Cancel) 

456 button_box.accepted.connect(self.accept) 

457 button_box.rejected.connect(self.reject) 

458 layout.addWidget(button_box) 

459 

460 def get_hidden_columns(self): 

461 """隠す列名のリスト。""" 

462 hidden = [] 

463 for i in range(self.column_list.count()): 

464 item = self.column_list.item(i) 

465 if item.checkState() == Qt.CheckState.Unchecked: 

466 hidden.append(item.text()) 

467 return hidden 

468 

469 

470class DuplicateXDialog(QDialog): 

471 """同じ X の行を、平均した新しいデータセットにするか、先頭以外をマスクする。""" 

472 

473 MODE_AVERAGE = "平均化(新しいデータセットを作成)" 

474 MODE_REMOVE = "除去(先頭以外をマスク)" 

475 MODES = [MODE_AVERAGE, MODE_REMOVE] 

476 

477 def __init__(self, name, n_duplicate_rows, parent=None): 

478 super().__init__(parent) 

479 self.setWindowTitle("重複X値の検出") 

480 self.resize(420, 240) 

481 self._name = name 

482 

483 layout = QVBoxLayout(self) 

484 layout.addWidget(QLabel(f"対象: {name}")) 

485 layout.addWidget(QLabel(f"重複するX値を持つ行: {n_duplicate_rows}件")) 

486 

487 form = QFormLayout() 

488 self.mode_combo = QComboBox() 

489 self.mode_combo.addItems(self.MODES) 

490 form.addRow("処理方法", self.mode_combo) 

491 layout.addLayout(form) 

492 

493 self.stack = QStackedWidget() 

494 

495 average_page = QWidget() 

496 average_form = QFormLayout(average_page) 

497 self.output_name_edit = QLineEdit(f"{name}_averaged") 

498 average_form.addRow("出力データセット名", self.output_name_edit) 

499 self.stack.addWidget(average_page) 

500 

501 remove_page = QWidget() 

502 remove_form = QFormLayout(remove_page) 

503 remove_note = QLabel("同じX値のうち最初に出現した行だけを残し、残りをマスク(除外)します。") 

504 remove_note.setWordWrap(True) 

505 remove_form.addRow(remove_note) 

506 self.stack.addWidget(remove_page) 

507 

508 layout.addWidget(self.stack) 

509 self.mode_combo.currentIndexChanged.connect(self.stack.setCurrentIndex) 

510 

511 button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | 

512 QDialogButtonBox.StandardButton.Cancel) 

513 button_box.accepted.connect(self.accept) 

514 button_box.rejected.connect(self.reject) 

515 layout.addWidget(button_box) 

516 

517 apply_form_spacing(self) 

518 

519 def get_settings(self): 

520 """(方法 "average" | "remove", 出力名("remove" では使わない))""" 

521 mode = "average" if self.mode_combo.currentText() == self.MODE_AVERAGE else "remove" 

522 return mode, self.output_name_edit.text().strip() 

523 

524 

525class ReplicateErrorDialog(QDialog): 

526 """反復測定の列を選び、行ごとの平均と誤差(SD / SEM / 95%CI)の列を作る。""" 

527 

528 def __init__(self, column_names, parent=None): 

529 super().__init__(parent) 

530 self.setWindowTitle("誤差の自動計算 (反復測定)") 

531 self.resize(360, 420) 

532 

533 layout = QVBoxLayout(self) 

534 layout.addWidget(QLabel("反復測定として扱う列を2つ以上選んでください:")) 

535 

536 self.column_list = QListWidget() 

537 self.column_list.setSelectionMode(QListWidget.SelectionMode.NoSelection) 

538 for name in column_names: 

539 item = QListWidgetItem(str(name)) 

540 item.setFlags(item.flags() | Qt.ItemFlag.ItemIsUserCheckable) 

541 item.setCheckState(Qt.CheckState.Unchecked) 

542 self.column_list.addItem(item) 

543 layout.addWidget(self.column_list) 

544 

545 form = QFormLayout() 

546 self.stat_combo = QComboBox() 

547 self.stat_combo.addItems(["SD (標準偏差)", "SEM (標準誤差)", "95%CI (信頼区間)"]) 

548 form.addRow("誤差の種類", self.stat_combo) 

549 

550 self.base_name_edit = QLineEdit("measurement") 

551 form.addRow("出力列名 (ベース)", self.base_name_edit) 

552 layout.addLayout(form) 

553 

554 info_label = QLabel( 

555 "「平均」列と「誤差」列の2つが追加されます (例: measurement_mean, measurement_SD)。" 

556 "NaNを含む行は、その行にある有効な測定値だけで計算します。" 

557 ) 

558 info_label.setWordWrap(True) 

559 layout.addWidget(info_label) 

560 

561 button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | 

562 QDialogButtonBox.StandardButton.Cancel) 

563 button_box.accepted.connect(self.accept) 

564 button_box.rejected.connect(self.reject) 

565 layout.addWidget(button_box) 

566 

567 apply_form_spacing(self) 

568 

569 def get_settings(self): 

570 """(列名のリスト, 誤差の種類 'SD' / 'SEM' / '95%CI', 出力の列名のもと)""" 

571 selected = [] 

572 for i in range(self.column_list.count()): 

573 item = self.column_list.item(i) 

574 if item.checkState() == Qt.CheckState.Checked: 

575 selected.append(item.text()) 

576 stat_type = self.stat_combo.currentText().split(" ")[0] # "SD (標準偏差)" -> "SD" 

577 return selected, stat_type, self.base_name_edit.text().strip()