Coverage for graphica/gui/builders/dataset_panel.py: 100.0%

353 statements  

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

1"""データセットの一覧とプロパティ欄の欄の組み立て。""" 

2from PySide6.QtCore import Qt, Signal 

3from PySide6.QtWidgets import ( 

4 QAbstractItemView, QCheckBox, QComboBox, QDoubleSpinBox, QFormLayout, QFrame, QGridLayout, QGroupBox, QHeaderView, 

5 QLabel, QLineEdit, 

6 QMenu, QPushButton, QSpinBox, QTextEdit, QToolButton, QTreeWidget, QTreeWidgetItemIterator, QVBoxLayout, QWidget) 

7from graphica.core.dataset import WATERFALL_OFFSET_ABSOLUTE, WATERFALL_OFFSET_STEP 

8from graphica.core.i18n import tr 

9from graphica.core.marker_style import ( 

10 DEFAULT_MARKER_EDGE_WIDTH, MARKER_EDGE_COLOR, MARKER_EDGE_SAME, MARKER_FILL_COLOR, MARKER_FILL_HOLLOW, 

11 MARKER_FILL_SOLID) 

12from graphica.gui.builders import property_widgets 

13from graphica.gui.builders.common import ( 

14 COLORMAP_CHOICES, DATASET_TREE_VISIBILITY_COLUMN_WIDTH, _DatasetTreeSelectionDelegate, _insert_form_row_after, 

15 _svg_icon, cap_combo_width) 

16from graphica.gui.color_picker_widget import ColorPickerWidget 

17from graphica.gui.dataset_bindings import MARKER_CHOICES 

18from graphica.gui.dataset_style_icon import ( 

19 DATASET_TREE_NAME_COLUMN, DATASET_TREE_VISIBILITY_COLUMN, marker_choice_icon) 

20 

21 

22def build_dataset_color_picker(app): 

23 old_color_button = app.ui.color_button 

24 app.color_picker_widget = ColorPickerWidget(app.settings, app) 

25 app.ui.formLayout_4.replaceWidget(old_color_button, app.color_picker_widget) 

26 old_color_button.hide() 

27 old_color_button.deleteLater() 

28 

29 # 「データ追加」のすぐ隣 

30 app.new_dataset_button = QPushButton(tr("新規データセット作成...")) 

31 app.ui.horizontalLayout_3.insertWidget(1, app.new_dataset_button) 

32 

33 

34def build_dataset_list_buttons(app): 

35 app.duplicate_dataset_button = QPushButton(tr("プロット複製")) 

36 app.view_edit_data_button = QPushButton(tr("データ表示/編集")) 

37 app.fit_curve_button = QPushButton(tr("曲線フィット")) 

38 app.find_peaks_button = QPushButton(tr("ピーク検出")) 

39 app.multi_peak_fit_button = QPushButton(tr("多峰フィット")) 

40 app.auto_color_button = QPushButton(tr("自動配色")) 

41 app.new_folder_button = QPushButton(tr("新しいフォルダ")) 

42 

43 # ボタンをアイコンだけにして「データ処理」「解析」「整理」の3組に分ける(文字はツールチップに残す)。 

44 # ダークモードの切り替えでアイコンを作り直すので(_refresh_custom_svg_icons)、対応を持っておく 

45 app._dataset_action_button_icons = { 

46 app.ui.add_dataset_button: ("file-plus", tr("データ追加")), 

47 app.new_dataset_button: ("table", tr("新規作成")), 

48 app.duplicate_dataset_button: ("copy", tr("複製")), 

49 app.view_edit_data_button: ("edit", tr("表示/編集")), 

50 app.ui.remove_dataset_button: ("trash", tr("削除")), 

51 app.fit_curve_button: ("chart-line", tr("曲線フィット")), 

52 app.find_peaks_button: ("mountain", tr("ピーク検出")), 

53 app.multi_peak_fit_button: ("chart-histogram", tr("多峰フィット")), 

54 app.auto_color_button: ("palette", tr("自動配色")), 

55 app.new_folder_button: ("folder-plus", tr("新しいフォルダ")), 

56 } 

57 for button, (icon_name, short_label) in app._dataset_action_button_icons.items(): 

58 button.setToolTip(button.text() or short_label) 

59 button.setText("") 

60 button.setIcon(_svg_icon(icon_name, size=18)) 

61 button.setProperty("iconOnly", True) 

62 button.setFixedSize(34, 34) 

63 # フォーカスを持つと、押した後も :focus の枠が残って押されたままに見える 

64 button.setFocusPolicy(Qt.FocusPolicy.NoFocus) 

65 

66 def _make_group_separator(): 

67 sep = QFrame() 

68 sep.setFrameShape(QFrame.Shape.VLine) 

69 sep.setObjectName("button_row_separator") 

70 sep.setFixedWidth(2) 

71 return sep 

72 

73 # データ処理: 追加・新規作成・複製・表示編集・削除(削除は Designer の配置のまま) 

74 app.ui.horizontalLayout_3.insertWidget(2, app.duplicate_dataset_button) 

75 app.ui.horizontalLayout_3.insertWidget(3, app.view_edit_data_button) 

76 

77 app.ui.horizontalLayout_3.insertWidget(5, _make_group_separator()) 

78 # 解析 

79 app.ui.horizontalLayout_3.addWidget(app.fit_curve_button) 

80 app.ui.horizontalLayout_3.addWidget(app.find_peaks_button) 

81 app.ui.horizontalLayout_3.addWidget(app.multi_peak_fit_button) 

82 

83 app.ui.horizontalLayout_3.addWidget(_make_group_separator()) 

84 # 整理 

85 app.ui.horizontalLayout_3.addWidget(app.auto_color_button) 

86 app.ui.horizontalLayout_3.addWidget(app.new_folder_button) 

87 

88 app.ui.horizontalLayout_3.addStretch() 

89 

90 # 「⋯」: たまにしか使わない操作 

91 app.dataset_overflow_button = QToolButton() 

92 app.dataset_overflow_button.setText("⋯") 

93 app.dataset_overflow_button.setToolTip(tr("その他の操作")) 

94 app.dataset_overflow_button.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup) 

95 app.dataset_overflow_button.setFixedSize(34, 34) 

96 overflow_menu = QMenu(app.dataset_overflow_button) 

97 app.manage_palette_action = overflow_menu.addAction( 

98 _svg_icon("palette", size=16), tr("パレット管理...") 

99 ) 

100 app.colormap_assign_action = overflow_menu.addAction( 

101 _svg_icon("palette", size=16), tr("カラーマップから自動配色...") 

102 ) 

103 # 登録はいつでも変わるので開くたびに作り直す。QMenu と menuAction() の両方を持つ(持たないと回収される) 

104 app._named_color_apply_menu = overflow_menu.addMenu( 

105 _svg_icon("color-swatch", size=16), tr("登録した色を適用")) 

106 app._named_color_apply_menu_action = app._named_color_apply_menu.menuAction() 

107 app._named_color_apply_menu.aboutToShow.connect( 

108 app.colors.populate_named_color_menu) 

109 app.colors.populate_named_color_menu() 

110 app.dataset_overflow_button.setMenu(overflow_menu) 

111 app.ui.horizontalLayout_3.addWidget(app.dataset_overflow_button) 

112 

113 # データセットのプロパティ欄を7つの節に分ける。Designer の行もここで移すので、formLayout_4 を触る 

114 # 色欄の差し替え(_build_dataset_color_picker)より後であること。以降の行は app._prop_form(キー) に足す 

115 app._build_dataset_property_sections() 

116 

117 

118def build_marker_style_controls(app): 

119 """マーカーの形の選択肢を名前と形の絵にし、塗りと枠線の欄を作る。出し入れは property_panel.update_marker_controls_visibility。""" 

120 combo = app.ui.marker_combo 

121 combo.clear() 

122 for marker, label in MARKER_CHOICES: 

123 icon = marker_choice_icon(marker) 

124 if icon is None: 

125 combo.addItem(tr(label), marker) 

126 else: 

127 combo.addItem(icon, tr(label), marker) 

128 

129 marker_form = app._prop_form('marker') 

130 app.marker_fill_label = QLabel(tr("マーカーの塗り")) 

131 app.marker_fill_combo = QComboBox() 

132 app.marker_fill_combo.addItem(tr("塗りつぶし"), MARKER_FILL_SOLID) 

133 app.marker_fill_combo.addItem(tr("白抜き"), MARKER_FILL_HOLLOW) 

134 app.marker_fill_combo.addItem(tr("色を指定"), MARKER_FILL_COLOR) 

135 marker_form.addRow(app.marker_fill_label, app.marker_fill_combo) 

136 

137 app.marker_face_color_label = QLabel(tr("塗りの色")) 

138 app.marker_face_color_picker = ColorPickerWidget(app.settings, app, initial_color='#ffffff') 

139 marker_form.addRow(app.marker_face_color_label, app.marker_face_color_picker) 

140 

141 app.marker_edge_label = QLabel(tr("マーカーの枠線")) 

142 app.marker_edge_combo = QComboBox() 

143 app.marker_edge_combo.addItem(tr("系列の色"), MARKER_EDGE_SAME) 

144 app.marker_edge_combo.addItem(tr("色を指定"), MARKER_EDGE_COLOR) 

145 marker_form.addRow(app.marker_edge_label, app.marker_edge_combo) 

146 

147 app.marker_edge_color_label = QLabel(tr("枠線の色")) 

148 app.marker_edge_color_picker = ColorPickerWidget(app.settings, app, initial_color='#000000') 

149 marker_form.addRow(app.marker_edge_color_label, app.marker_edge_color_picker) 

150 

151 app.marker_edge_width_label = QLabel(tr("枠線の太さ")) 

152 app.marker_edge_width_spinbox = QDoubleSpinBox() 

153 app.marker_edge_width_spinbox.setRange(0.0, 10.0) 

154 app.marker_edge_width_spinbox.setSingleStep(0.5) 

155 app.marker_edge_width_spinbox.setDecimals(1) 

156 app.marker_edge_width_spinbox.setValue(DEFAULT_MARKER_EDGE_WIDTH) 

157 marker_form.addRow(app.marker_edge_width_label, app.marker_edge_width_spinbox) 

158 

159 

160def build_dataset_style_controls(app): 

161 app.x_col_combo = QComboBox() 

162 app.y_col_combo = QComboBox() 

163 app._prop_form('data').addRow("X軸の列", app.x_col_combo) 

164 app._prop_form('data').addRow("Y軸の列", app.y_col_combo) 

165 

166 app.x_err_col_combo = QComboBox() 

167 app.y_err_col_combo = QComboBox() 

168 app._prop_form('data').addRow("X誤差列", app.x_err_col_combo) 

169 app._prop_form('data').addRow("Y誤差列", app.y_err_col_combo) 

170 

171 app.alpha_label = QLabel("透明度") 

172 app.alpha_spinbox = QDoubleSpinBox() 

173 app.alpha_spinbox.setRange(0.0, 1.0) 

174 app.alpha_spinbox.setSingleStep(0.05) 

175 app.alpha_spinbox.setDecimals(2) 

176 app.alpha_spinbox.setValue(1.0) 

177 app._prop_form('style').addRow(app.alpha_label, app.alpha_spinbox) 

178 

179 build_marker_style_controls(app) 

180 

181 # グラデーション(線・面の塗り)。出し入れは property_panel.update_gradient_controls_visibility 

182 app.gradient_checkbox = QCheckBox(tr("グラデーションを適用")) 

183 app._prop_form('gradient').addRow(app.gradient_checkbox) 

184 

185 app.gradient_color2_label = QLabel(tr("終端色")) 

186 app.gradient_color2_picker = ColorPickerWidget(app.settings, app, initial_color='#ffffff') 

187 app._prop_form('gradient').addRow(app.gradient_color2_label, app.gradient_color2_picker) 

188 

189 app.gradient_target_label = QLabel(tr("対象")) 

190 app.gradient_target_combo = QComboBox() 

191 app.gradient_target_combo.addItem(tr("線"), "line") 

192 app.gradient_target_combo.addItem(tr("塗り"), "fill") 

193 app.gradient_target_combo.addItem(tr("両方"), "both") 

194 app._prop_form('gradient').addRow(app.gradient_target_label, app.gradient_target_combo) 

195 

196 # ウォーターフォールは種類ではなく、どの種類とも組み合わせられるオプション。 

197 # 出し入れは property_panel.update_waterfall_controls_visibility 

198 app.waterfall_checkbox = QCheckBox(tr("ウォーターフォール表示(積み重ね)")) 

199 app._prop_form('waterfall').addRow(app.waterfall_checkbox) 

200 

201 # オフセットの欄は大きな値や指数表記(1.5E6)も入れられる(main_window の _enable_scientific_notation_input) 

202 app.waterfall_offset_mode_label = QLabel(tr("オフセットの指定")) 

203 app.waterfall_offset_mode_combo = QComboBox() 

204 app.waterfall_offset_mode_combo.addItem(tr("段ごとの間隔(段の番号 × オフセット)"), WATERFALL_OFFSET_STEP) 

205 app.waterfall_offset_mode_combo.addItem(tr("ずらし量をそのまま"), WATERFALL_OFFSET_ABSOLUTE) 

206 cap_combo_width(app.waterfall_offset_mode_combo) 

207 app._prop_form('waterfall').addRow(app.waterfall_offset_mode_label, app.waterfall_offset_mode_combo) 

208 

209 app.waterfall_offset_x_label = QLabel(tr("Xオフセット")) 

210 app.waterfall_offset_x_spinbox = QDoubleSpinBox() 

211 app.waterfall_offset_x_spinbox.setRange(-1e6, 1e6) 

212 app.waterfall_offset_x_spinbox.setSingleStep(0.1) 

213 app.waterfall_offset_x_spinbox.setDecimals(4) 

214 app.waterfall_offset_x_spinbox.setValue(0.0) 

215 app._prop_form('waterfall').addRow(app.waterfall_offset_x_label, app.waterfall_offset_x_spinbox) 

216 

217 app.waterfall_offset_y_label = QLabel(tr("Yオフセット")) 

218 app.waterfall_offset_y_spinbox = QDoubleSpinBox() 

219 app.waterfall_offset_y_spinbox.setRange(-1e6, 1e6) 

220 app.waterfall_offset_y_spinbox.setSingleStep(0.1) 

221 app.waterfall_offset_y_spinbox.setDecimals(4) 

222 app.waterfall_offset_y_spinbox.setValue(1.0) 

223 app._prop_form('waterfall').addRow(app.waterfall_offset_y_label, app.waterfall_offset_y_spinbox) 

224 

225 app.waterfall_occlusion_checkbox = QCheckBox(tr("背面のトレースを隠す(オクルージョン)")) 

226 app.waterfall_occlusion_checkbox.setChecked(True) 

227 app._prop_form('waterfall').addRow(app.waterfall_occlusion_checkbox) 

228 

229 # 奥の段ほど Y をわずかに縮めて立体風にする 

230 app.waterfall_depth_checkbox = QCheckBox(tr("奥行き効果(奥のトレースをわずかに縮小)")) 

231 app.waterfall_depth_checkbox.setChecked(False) 

232 app._prop_form('waterfall').addRow(app.waterfall_depth_checkbox) 

233 

234 app.waterfall_depth_ratio_label = QLabel(tr("1段あたりの縮小率")) 

235 app.waterfall_depth_ratio_spinbox = QDoubleSpinBox() 

236 app.waterfall_depth_ratio_spinbox.setRange(0.0, 0.9) 

237 app.waterfall_depth_ratio_spinbox.setSingleStep(0.01) 

238 app.waterfall_depth_ratio_spinbox.setDecimals(3) 

239 app.waterfall_depth_ratio_spinbox.setValue(0.03) 

240 app._prop_form('waterfall').addRow(app.waterfall_depth_ratio_label, app.waterfall_depth_ratio_spinbox) 

241 

242 app.point_labels_checkbox = QCheckBox("データ点にラベルを表示") 

243 app._prop_form('extra').addRow(app.point_labels_checkbox) 

244 # 点が上限を超えてラベルを描かないときに、その理由を出す 

245 app.point_labels_limit_note = QLabel() 

246 app.point_labels_limit_note.setObjectName("point_labels_limit_note") 

247 app.point_labels_limit_note.setWordWrap(True) 

248 app.point_labels_limit_note.setVisible(False) 

249 app._prop_form('extra').addRow(app.point_labels_limit_note) 

250 app.point_label_col_label = QLabel("ラベルの内容") 

251 app.point_label_col_combo = QComboBox() 

252 app._prop_form('extra').addRow(app.point_label_col_label, app.point_label_col_combo) 

253 

254 # 誤差の表し方。誤差列が無ければどれを選んでも描かれない 

255 app.error_display_label = QLabel(tr("誤差の表示形式")) 

256 app.error_display_combo = QComboBox() 

257 app.error_display_combo.addItem(tr("エラーバー"), "bar") 

258 app.error_display_combo.addItem(tr("誤差バンド"), "band") 

259 app.error_display_combo.addItem(tr("両方"), "both") 

260 app._prop_form('extra').addRow(app.error_display_label, app.error_display_combo) 

261 

262 # 凡例名(= データセット名)を変えずに凡例の文字だけ変える。空ならデータセット名 

263 app.legend_label_label = QLabel(tr("凡例の表示名")) 

264 app.legend_label_edit = QLineEdit() 

265 app.legend_label_edit.setPlaceholderText(tr("空ならデータセット名")) 

266 app._prop_form('extra').addRow(app.legend_label_label, app.legend_label_edit) 

267 app.hide_from_legend_checkbox = QCheckBox(tr("凡例に表示しない")) 

268 app._prop_form('extra').addRow(app.hide_from_legend_checkbox) 

269 

270 # X/Y/Z 列の長形式を2Dマップとして描く。関係する欄の出し入れは property_panel.update_2d_controls_visibility 

271 app.data_2d_checkbox = QCheckBox(tr("2Dグリッドとして扱う(ヒートマップ)")) 

272 app._prop_form('map').addRow(app.data_2d_checkbox) 

273 

274 app.z_col_label = QLabel(tr("Z軸の列")) 

275 app.z_col_combo = QComboBox() 

276 app._prop_form('map').addRow(app.z_col_label, app.z_col_combo) 

277 

278 app.colormap_label = QLabel(tr("カラーマップ")) 

279 app.colormap_combo = QComboBox() 

280 app.colormap_combo.addItems(COLORMAP_CHOICES) 

281 app._prop_form('map').addRow(app.colormap_label, app.colormap_combo) 

282 

283 app.map_display_mode_label = QLabel(tr("表示方式")) 

284 app.map_display_mode_combo = QComboBox() 

285 app.map_display_mode_combo.addItem(tr("ヒートマップ"), "heatmap") 

286 app.map_display_mode_combo.addItem(tr("等高線(線)"), "contour") 

287 app.map_display_mode_combo.addItem(tr("等高線(塗りつぶし)"), "contour_filled") 

288 app.map_display_mode_combo.addItem(tr("ヒートマップ+等高線"), "heatmap_contour") 

289 app._prop_form('map').addRow(app.map_display_mode_label, app.map_display_mode_combo) 

290 

291 app.contour_levels_label = QLabel(tr("等高線レベル数")) 

292 app.contour_levels_spinbox = QSpinBox() 

293 app.contour_levels_spinbox.setRange(2, 100) 

294 app.contour_levels_spinbox.setValue(10) 

295 app._prop_form('map').addRow(app.contour_levels_label, app.contour_levels_spinbox) 

296 

297 app.grid_interp_method_label = QLabel(tr("補間方法")) 

298 app.grid_interp_method_combo = QComboBox() 

299 app.grid_interp_method_combo.addItems(['linear', 'cubic', 'nearest']) 

300 app._prop_form('map').addRow(app.grid_interp_method_label, app.grid_interp_method_combo) 

301 

302 app.color_range_auto_checkbox = QCheckBox(tr("値域を自動")) 

303 app.color_range_auto_checkbox.setChecked(True) 

304 app._prop_form('map').addRow(app.color_range_auto_checkbox) 

305 

306 app.vmin_label = QLabel(tr("値域の最小")) 

307 app.vmin_spinbox = QDoubleSpinBox() 

308 app.vmin_spinbox.setRange(-1e12, 1e12) 

309 app.vmin_spinbox.setDecimals(4) 

310 app.vmin_spinbox.setEnabled(False) 

311 app._prop_form('map').addRow(app.vmin_label, app.vmin_spinbox) 

312 

313 app.vmax_label = QLabel(tr("値域の最大")) 

314 app.vmax_spinbox = QDoubleSpinBox() 

315 app.vmax_spinbox.setRange(-1e12, 1e12) 

316 app.vmax_spinbox.setDecimals(4) 

317 app.vmax_spinbox.setValue(1.0) 

318 app.vmax_spinbox.setEnabled(False) 

319 app._prop_form('map').addRow(app.vmax_label, app.vmax_spinbox) 

320 

321 # 欠損値の扱いは描くときだけに効く(データ自体は変えない)。 

322 # ラベルの列幅は全部の節で揃えるので、ここが一番長いと全体の入力欄が狭まる。「(NaN)」はツールチップに回す 

323 app.nan_policy_label = QLabel(tr("欠損値の扱い")) 

324 app.nan_policy_label.setToolTip(tr("欠損値(NaN)を含む点の描画方法")) 

325 app.nan_policy_combo = QComboBox() 

326 app.nan_policy_combo.addItem(tr("線を切る(既定)"), "gap") 

327 app.nan_policy_combo.addItem(tr("前の値で埋める"), "ffill") 

328 app.nan_policy_combo.addItem(tr("無視してつなぐ"), "drop") 

329 app._prop_form('data').addRow(app.nan_policy_label, app.nan_policy_combo) 

330 

331 # 平滑化の手法。オン/オフは Designer の smoothing_checkbox 

332 app.smoothing_method_label = QLabel(tr("平滑化の手法")) 

333 app.smoothing_method_combo = QComboBox() 

334 app.smoothing_method_combo.addItem(tr("CubicSpline(既定)"), "cubic_spline") 

335 app.smoothing_method_combo.addItem(tr("移動平均"), "moving_average") 

336 app.smoothing_method_combo.addItem(tr("中央値フィルタ"), "median") 

337 app.smoothing_method_combo.addItem(tr("ガウシアンフィルタ"), "gaussian") 

338 # 「平滑化」のチェックは手法と対なので、_build_dataset_property_sections では移さずここで手法の直前に置く 

339 # (そうしないと間に「透明度」が挟まる) 

340 app._prop_form('style').addRow(app.ui.smoothing_checkbox) 

341 app._prop_form('style').addRow(app.smoothing_method_label, app.smoothing_method_combo) 

342 

343 property_widgets.build_property_widgets(app) 

344 

345 

346def build_fit_info_and_stats(app): 

347 # フィットの結果の文。置き場所は下のパネルの「フィット結果」のタブ(shell/bottom_panel.py) 

348 app.fit_info_textedit = QTextEdit() 

349 app.fit_info_textedit.setObjectName("fit_info_textedit") 

350 app.fit_info_textedit.setReadOnly(True) 

351 app.fit_info_textedit.setPlaceholderText(tr("フィットした曲線か、フィットしたデータセットを選ぶと、ここに式とパラメータが出ます。")) 

352 

353 # 統計の1行(件数・平均・標準偏差・最小・最大)。選択が変わるたびに property_panel.update_stats_summary_label が 

354 # 更新する。画面には左パネルの統計カードを出す(GUI 刷新の決定 10 でツールバーの統計ボタンはやめた) 

355 app.stats_summary_label = QLabel("-") 

356 app.stats_summary_label.setWordWrap(True) 

357 app.stats_summary_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) 

358 

359 

360Y2_SPINBOX_MIN_WIDTH = 100 

361 

362 

363def _y2_value_spinbox(decimals=2): 

364 spinbox = QDoubleSpinBox() 

365 spinbox.setRange(-2147483648.0, 2147483648.0) 

366 spinbox.setDecimals(decimals) 

367 # 既定の最小幅は範囲の桁数(-2147483648.00)で決まり、右のインスペクタ(340px)からはみ出す 

368 spinbox.setMinimumWidth(Y2_SPINBOX_MIN_WIDTH) 

369 return spinbox 

370 

371 

372def _build_secondary_y_axis_group(app): 

373 """「Y軸」タブの末尾に、第 2 Y 軸の範囲と目盛りの欄を足す(主の Y 軸の欄と同じ並び)。""" 

374 app.y2_axis_group = QGroupBox(tr("第2Y軸")) 

375 form = QFormLayout(app.y2_axis_group) 

376 app.y2_autoscale_checkbox = QCheckBox(tr("自動スケール")) 

377 app.y2_autoscale_checkbox.setChecked(True) 

378 form.addRow(app.y2_autoscale_checkbox) 

379 app.y2_min_spinbox = _y2_value_spinbox() 

380 form.addRow(tr("最小値"), app.y2_min_spinbox) 

381 app.y2_max_spinbox = _y2_value_spinbox() 

382 app.y2_max_spinbox.setValue(1.0) 

383 form.addRow(tr("最大値"), app.y2_max_spinbox) 

384 app.y2_log_checkbox = QCheckBox(tr("対数表示")) 

385 form.addRow(app.y2_log_checkbox) 

386 # 対数軸で表示されない 0 以下の値を知らせる(中身は log_axis_notes.update_log_axis_notes) 

387 app.y2_log_note = QLabel() 

388 app.y2_log_note.setObjectName("y2_log_note") 

389 app.y2_log_note.setWordWrap(True) 

390 app.y2_log_note.setVisible(False) 

391 form.addRow(app.y2_log_note) 

392 app.y2_invert_checkbox = QCheckBox(tr("軸を反転")) 

393 form.addRow(app.y2_invert_checkbox) 

394 app.y2_major_tick_mode_combo = QComboBox() 

395 # 選んだ番号で値を持つ(index の束ね)ので、表示を訳してよい 

396 app.y2_major_tick_mode_combo.addItems([tr("自動"), tr("固定間隔")]) 

397 form.addRow(tr("主目盛"), app.y2_major_tick_mode_combo) 

398 app.y2_major_tick_interval_spinbox = _y2_value_spinbox() 

399 app.y2_major_tick_interval_spinbox.setValue(1.0) 

400 form.addRow(tr("間隔"), app.y2_major_tick_interval_spinbox) 

401 app.y2_minor_ticks_visible_checkbox = QCheckBox(tr("補助目盛を表示")) 

402 form.addRow(app.y2_minor_ticks_visible_checkbox) 

403 app.y2_minor_tick_interval_spinbox = _y2_value_spinbox() 

404 app.y2_minor_tick_interval_spinbox.setValue(0.5) 

405 form.addRow(tr("補助目盛の間隔"), app.y2_minor_tick_interval_spinbox) 

406 layout = app.ui.gridLayout_3 

407 layout.addWidget(app.y2_axis_group, layout.rowCount(), 0, 1, layout.columnCount() or 1) 

408 

409 

410def build_secondary_y_controls(app): 

411 app.use_secondary_y_checkbox = QCheckBox("第2Y軸 (右側) を使用") 

412 app._prop_form('place').addRow(app.use_secondary_y_checkbox) 

413 

414 app.y2_label_text_label = QLabel("第2Y軸ラベル") 

415 app.y2_label_text_edit = QLineEdit() 

416 _insert_form_row_after(app.ui.formLayout_3, app.ui.y_label_text_edit, 

417 app.y2_label_text_label, app.y2_label_text_edit) 

418 

419 _build_secondary_y_axis_group(app) 

420 

421def add_subplot_target_row(app): 

422 app.subplot_target_label = QLabel("描画先プロット") 

423 app.subplot_target_combo = QComboBox() 

424 app._prop_form('place').addRow(app.subplot_target_label, app.subplot_target_combo) 

425 

426 

427class DatasetTreeWidget(QTreeWidget): 

428 """ドロップで項目を動かした後に items_dropped を出す。 

429 

430 QTreeWidget のドロップは項目を取り出して入れ直すので、モデルの rowsMoved は出ない。 

431 引数は、どの項目も親(フォルダ)が変わらなかったか。 

432 """ 

433 items_dropped = Signal(bool) 

434 

435 def dropEvent(self, event): 

436 parents = [(item, item.parent()) for item in self._all_items()] 

437 super().dropEvent(event) 

438 self.items_dropped.emit(all(item.parent() is parent for item, parent in parents)) 

439 

440 def _all_items(self): 

441 items = [] 

442 iterator = QTreeWidgetItemIterator(self) 

443 while iterator.value() is not None: 

444 items.append(iterator.value()) 

445 iterator += 1 

446 return items 

447 

448 

449def replace_dataset_list_with_tree(app): 

450 """Designer の QListWidget を、同じセルに「検索欄 + QTreeWidget」を縦に並べたものへ差し替える。""" 

451 old_widget = app.ui.dataset_list_widget 

452 parent_widget = old_widget.parentWidget() 

453 parent_layout = parent_widget.layout() 

454 

455 idx = parent_layout.indexOf(old_widget) 

456 row = col = rowspan = colspan = None 

457 if isinstance(parent_layout, QGridLayout): 

458 row, col, rowspan, colspan = parent_layout.getItemPosition(idx) 

459 

460 parent_layout.removeWidget(old_widget) 

461 old_widget.setParent(None) 

462 old_widget.deleteLater() 

463 

464 container = QWidget(parent_widget) 

465 container_layout = QVBoxLayout(container) 

466 container_layout.setContentsMargins(0, 0, 0, 0) 

467 container_layout.setSpacing(6) 

468 

469 search_edit = QLineEdit(container) 

470 search_edit.setObjectName("dataset_search_edit") 

471 search_edit.setPlaceholderText("データセットを検索...") 

472 search_edit.setClearButtonEnabled(True) 

473 container_layout.addWidget(search_edit) 

474 

475 tree = DatasetTreeWidget(container) 

476 tree.setObjectName("dataset_list_widget") 

477 tree.setHeaderHidden(True) 

478 # 列1は表示/非表示の目のアイコン。stretchLastSection のままだと目の列が余白を吸って広がる 

479 tree.setColumnCount(2) 

480 header = tree.header() 

481 header.setStretchLastSection(False) 

482 header.setSectionResizeMode(DATASET_TREE_NAME_COLUMN, QHeaderView.ResizeMode.Stretch) 

483 header.setSectionResizeMode(DATASET_TREE_VISIBILITY_COLUMN, QHeaderView.ResizeMode.Fixed) 

484 tree.setColumnWidth(DATASET_TREE_VISIBILITY_COLUMN, DATASET_TREE_VISIBILITY_COLUMN_WIDTH) 

485 tree.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) 

486 tree.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove) 

487 tree.setDefaultDropAction(Qt.DropAction.MoveAction) 

488 tree.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) 

489 # 行の伸びをキャンバスに譲ったので、放っておくと数件でも窮屈な高さまで縮む 

490 tree.setMinimumHeight(90) 

491 tree.setItemDelegate(_DatasetTreeSelectionDelegate(tree)) 

492 container_layout.addWidget(tree) 

493 

494 if isinstance(parent_layout, QGridLayout) and row is not None: 

495 parent_layout.addWidget(container, row, col, rowspan, colspan) 

496 else: 

497 parent_layout.addWidget(container) 

498 

499 app.ui.dataset_list_widget = tree 

500 app.dataset_search_edit = search_edit