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
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-12 16:38 +0000
1"""データの編集と列の操作のダイアログ。呼び出し側は `from graphica.gui.dialogs import X` で参照する。"""
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
26class ColumnCalculatorDialog(QDialog):
27 """列の計算の出力先の列と式(式は safe_eval_column_formula で評価する)。"""
29 def __init__(self, column_names, parent=None):
30 super().__init__(parent)
31 self.setWindowTitle("列の計算")
33 self.column_names = column_names
36 self.output_col_label = QLabel("出力先の列 (既存または新規)")
38 self.output_col_combo = QComboBox()
39 self.output_col_combo.addItems(self.column_names)
40 # 新しい列名も打ち込めるように
41 self.output_col_combo.setEditable(True)
43 self.formula_label = QLabel("計算式 (例: A + B * 2)")
45 self.formula_edit = QLineEdit()
46 self.formula_edit.setPlaceholderText("例: (A + B) / 2 や log(C)")
48 help_text = QLabel("列名はそのまま使えます (例: `A`)。\n数値や `log(A)`, `sin(A)` なども利用可能です。")
49 help_text.setStyleSheet("font-size: 9pt; color: gray;")
51 preset_group = QGroupBox("プリセット (対象列を選んでボタンを押すと計算式が自動入力されます)")
52 preset_layout = QVBoxLayout()
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)
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)
79 preset_group.setLayout(preset_layout)
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)
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)
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}")
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")
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")
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")
129 def get_formula(self):
130 """(出力先の列名, 式)"""
131 output_column_name = self.output_col_combo.currentText()
132 formula_string = self.formula_edit.text()
134 return output_column_name, formula_string
137class CalcHelpDialog(QDialog):
138 """列の計算で使える書き方のリファレンス。"""
140 def __init__(self, parent=None):
141 super().__init__(parent)
142 self.setWindowTitle("列計算機能 リファレンス")
143 self.resize(600, 700)
145 layout = QVBoxLayout(self)
147 text_browser = QTextBrowser()
148 text_browser.setReadOnly(True)
149 text_browser.setOpenExternalLinks(True)
151 help_html = r"""
152 <h1>列計算機能 リファレンス</h1>
153 <p>
154 計算式を使って、列データをまとめて計算します。
155 「出力先の列」に指定した列に、計算式の結果が一度に適用されます(Excelのオートフィルのように、全行に適用されます)。
156 </p>
158 <hr>
160 <h2>1. 基本的な算術演算子 🧮</h2>
161 <p>列名(例: <code>A</code>, <code>B</code>)や数値をそのまま使えます。空白や記号を含む列名は <code>`強度 (a.u.)`</code> のようにバッククォートで囲みます。</p>
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>
172 <hr>
174 <h2>2. 一般的な数学関数 📈</h2>
175 <p>
176 以下の関数が利用可能です。
177 </p>
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の平方根 (√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>
191 <hr>
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>
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)
212 layout.addWidget(text_browser)
214 button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Close)
215 button_box.rejected.connect(self.reject)
216 layout.addWidget(button_box)
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 )
228class ColumnStringOpsDialog(QDialog):
229 """列の分割・結合・数値の抽出の設定。処理は呼び出し側(DataEditorDialog)が行う。"""
231 MODE_SPLIT = "列の分割"
232 MODE_MERGE = "列の結合"
233 MODE_EXTRACT_NUMERIC = "数値抽出"
234 MODES = [MODE_SPLIT, MODE_MERGE, MODE_EXTRACT_NUMERIC]
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
242 layout = QVBoxLayout(self)
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)
250 self.stack = QStackedWidget()
251 layout.addWidget(self.stack)
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)
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)
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)
300 self.mode_combo.currentIndexChanged.connect(self.stack.setCurrentIndex)
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)
308 apply_form_spacing(self)
310 def get_mode(self):
311 return self.mode_combo.currentText()
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 )
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()
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 )
339class FindReplaceDialog(QDialog):
340 """検索と置換。入力欄とボタンだけで、処理は DataEditorDialog が持つ。表を見ながら使うので非モーダル。"""
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)
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)
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)
372 self.status_label = QLabel("")
373 self.status_label.setWordWrap(True)
374 layout.addWidget(self.status_label)
376 apply_form_spacing(self)
378 def get_search_text(self):
379 return self.search_edit.text()
381 def get_replace_text(self):
382 return self.replace_edit.text()
384 def get_target_column(self):
385 """対象の列。「(すべての列)」なら None。"""
386 text = self.column_combo.currentText()
387 return None if text == "(すべての列)" else text
389 def set_status(self, text):
390 self.status_label.setText(text)
393class RowFilterDialog(QDialog):
394 """条件式(例: "y > 0.5")を満たさない行をマスクする。式は列の計算と同じ規則。"""
396 def __init__(self, column_names, parent=None):
397 super().__init__(parent)
398 self.setWindowTitle("行フィルタ")
399 self.resize(440, 240)
401 layout = QVBoxLayout(self)
402 layout.addWidget(QLabel("条件式を満たさない行をマスク(除外)します(非破壊、いつでも解除できます)。"))
404 self.formula_edit = QLineEdit()
405 self.formula_edit.setPlaceholderText("例: y > 0.5 や (x > 0) and (y < 100)")
406 layout.addWidget(self.formula_edit)
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)
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)
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)
428 apply_form_spacing(self)
430 def get_formula(self):
431 return self.formula_edit.text().strip()
434class ColumnVisibilityDialog(QDialog):
435 """チェックを外した列を表で隠す(表示だけで dataset.df は変えない)。"""
437 def __init__(self, column_names, hidden_columns, parent=None):
438 super().__init__(parent)
439 self.setWindowTitle("列の表示/非表示")
440 self.resize(280, 380)
442 layout = QVBoxLayout(self)
443 layout.addWidget(QLabel("表示する列にチェックを入れてください:"))
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)
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)
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
470class DuplicateXDialog(QDialog):
471 """同じ X の行を、平均した新しいデータセットにするか、先頭以外をマスクする。"""
473 MODE_AVERAGE = "平均化(新しいデータセットを作成)"
474 MODE_REMOVE = "除去(先頭以外をマスク)"
475 MODES = [MODE_AVERAGE, MODE_REMOVE]
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
483 layout = QVBoxLayout(self)
484 layout.addWidget(QLabel(f"対象: {name}"))
485 layout.addWidget(QLabel(f"重複するX値を持つ行: {n_duplicate_rows}件"))
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)
493 self.stack = QStackedWidget()
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)
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)
508 layout.addWidget(self.stack)
509 self.mode_combo.currentIndexChanged.connect(self.stack.setCurrentIndex)
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)
517 apply_form_spacing(self)
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()
525class ReplicateErrorDialog(QDialog):
526 """反復測定の列を選び、行ごとの平均と誤差(SD / SEM / 95%CI)の列を作る。"""
528 def __init__(self, column_names, parent=None):
529 super().__init__(parent)
530 self.setWindowTitle("誤差の自動計算 (反復測定)")
531 self.resize(360, 420)
533 layout = QVBoxLayout(self)
534 layout.addWidget(QLabel("反復測定として扱う列を2つ以上選んでください:"))
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)
545 form = QFormLayout()
546 self.stat_combo = QComboBox()
547 self.stat_combo.addItems(["SD (標準偏差)", "SEM (標準誤差)", "95%CI (信頼区間)"])
548 form.addRow("誤差の種類", self.stat_combo)
550 self.base_name_edit = QLineEdit("measurement")
551 form.addRow("出力列名 (ベース)", self.base_name_edit)
552 layout.addLayout(form)
554 info_label = QLabel(
555 "「平均」列と「誤差」列の2つが追加されます (例: measurement_mean, measurement_SD)。"
556 "NaNを含む行は、その行にある有効な測定値だけで計算します。"
557 )
558 info_label.setWordWrap(True)
559 layout.addWidget(info_label)
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)
567 apply_form_spacing(self)
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()