Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions extrap/gui/MainWidget.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,17 @@
from extrap.gui.RankingWidget import RankingWidget
from extrap.gui.SelectorWidget import SelectorWidget
from extrap.gui.StrongScalingConversionDialog import StrongScalingConversionDialog
from extrap.gui.TabBar import TabBar
from extrap.gui.comparison.comparison_wizard import ComparisonWizard
from extrap.gui.components.robust_wizard import RobustModelWizard
from extrap.gui.components import file_dialog
from extrap.gui.components.ProgressWindow import ProgressWindow
from extrap.gui.components.about_dialog import AboutDialog
from extrap.gui.components.developer_tools import init_developer_menu, DEV_CONFIG
from extrap.gui.components.model_color_map import ModelColorMap
from extrap.gui.components.plot_formatting_options import PlotFormattingOptions, PlotFormattingDialog
from extrap.modelers.model_generator import ModelGenerator
from extrap.modelers.robust import RobustModel
from extrap.util.deprecation import deprecated
from extrap.util.dynamic_options import DynamicOptions
from extrap.util.event import Event
Expand Down Expand Up @@ -115,13 +118,28 @@ def _init_ui(self):
dock.setObjectName("EP_selection")
self.selector_widget = SelectorWidget(self, dock)
dock.setWidget(self.selector_widget)
dock.setAllowedAreas(Qt.DockWidgetArea.AllDockWidgetAreas ^ Qt.DockWidgetArea.TopDockWidgetArea)
self.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, dock)

# middle: Graph

self.data_display = DataDisplayManager(self, self)
central_widget = self.data_display

# upper: Tab bar for multiple experiments
self.dock_tabs = QDockWidget(self)
self.dock_tabs.setObjectName("EP_Tabs")
self.tab_widget = TabBar(self,parent=dock)
self.tab_widget.setMinimumHeight(30)
self.tab_widget.setTabsClosable(False)
self.tab_widget.tabCloseRequested.connect(self.tab_widget.close_tab)
self.tab_widget.currentChanged.connect(self.tab_widget.on_tab_changed)
self.dock_tabs.setFeatures(QDockWidget.DockWidgetFeature.NoDockWidgetFeatures)
self.dock_tabs.setWidget(self.tab_widget)
self.addDockWidget(Qt.DockWidgetArea.TopDockWidgetArea, self.dock_tabs, Qt.Orientation.Horizontal)
self.dock_tabs.hide()


# Right side: Model configurator
dock = QDockWidget("Modeler", self)
dock.setObjectName("EP_modeler")
Expand All @@ -135,13 +153,15 @@ def _init_ui(self):
self.measurementWizard_widget = MeasurementWizardWidget(self, dock)
dock.setWidget(self.measurementWizard_widget)
self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, dock)
dock.setAllowedAreas(Qt.DockWidgetArea.AllDockWidgetAreas ^ Qt.DockWidgetArea.TopDockWidgetArea)

dock2 = QDockWidget("Aggregation and Analysis", self)
dock2.setObjectName("EP_agg_ana")
self.postprocessing_widget = PostProcessingWidget(self, dock2)
dock2.setWidget(self.postprocessing_widget)
self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, dock2)
self.tabifyDockWidget(dock2, dock)
dock.setAllowedAreas(Qt.DockWidgetArea.AllDockWidgetAreas ^ Qt.DockWidgetArea.TopDockWidgetArea)

# bottom widget
dock = QDockWidget("Color Info", self)
Expand All @@ -150,26 +170,30 @@ def _init_ui(self):
self.min_max_value_updated_event += self.color_widget.update_min_max
dock.setWidget(self.color_widget)
self.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, dock)
dock.setAllowedAreas(Qt.DockWidgetArea.AllDockWidgetAreas ^ Qt.DockWidgetArea.TopDockWidgetArea)

dock = QDockWidget("Graph Limits", self)
dock.setObjectName("EP_graph_limits")
self.graph_limits_widget = GraphLimitsWidget(self, self.data_display)
dock.setWidget(self.graph_limits_widget)
self.addDockWidget(Qt.DockWidgetArea.BottomDockWidgetArea, dock, Qt.Orientation.Horizontal)
dock.setAllowedAreas(Qt.DockWidgetArea.AllDockWidgetAreas ^ Qt.DockWidgetArea.TopDockWidgetArea)

dock2 = QDockWidget("Log", self)
dock2.setObjectName("EP_log")
self.log_widget = LogWidget(self)
dock2.setWidget(self.log_widget)
self.tabifyDockWidget(dock, dock2)
dock2.hide()
dock.setAllowedAreas(Qt.DockWidgetArea.AllDockWidgetAreas ^ Qt.DockWidgetArea.TopDockWidgetArea)

dock2 = QDockWidget("Ranking", self)
dock2.setObjectName("EP_ranking")
self.ranking_widget = RankingWidget(self)
dock2.setWidget(self.ranking_widget)
self.tabifyDockWidget(dock, dock2)
dock2.hide()
dock.setAllowedAreas(Qt.DockWidgetArea.AllDockWidgetAreas ^ Qt.DockWidgetArea.TopDockWidgetArea)
# Menu creation

# File menu
Expand Down Expand Up @@ -251,6 +275,11 @@ def _init_ui(self):
compare_action.setEnabled(False)
self.compare_action = compare_action

# robust model creation menu
create_robust_action = QAction('Create robust model', self)
create_robust_action.setStatusTip('Creates a robust model with two experiments')
create_robust_action.triggered.connect(self.create_robust_model_wizard)

# Filter menu
# filter_callpath_action = QAction('Filter Callpaths', self)
# filter_callpath_action.setShortcut('Ctrl+F')
Expand All @@ -273,6 +302,8 @@ def _init_ui(self):
file_menu.addAction(save_experiment_action)
file_menu.addAction(compare_action)
file_menu.addSeparator()
file_menu.addAction(create_robust_action)
file_menu.addSeparator()
file_menu.addAction(screenshot_action)
file_menu.addSeparator()
file_menu.addAction(exit_action)
Expand Down Expand Up @@ -338,6 +369,24 @@ def set_experiment(self, experiment, file_name="", *, compared=False):
raise ValueError("Experiment cannot be none.")
self.experiment_change = True
self._experiment = experiment
# Add tab if this is a new experiment

tab_name = Path(file_name).name if file_name else "Experiment"
is_new_experiment = not any(e == experiment for e, *_ in self.tab_widget.experiments)
if is_new_experiment:
self.tab_widget._save_tab_state(self.tab_widget._active_tab_index)
self.tab_widget.experiments.append((experiment, file_name))
new_tab_index = self.tab_widget.addTab(tab_name)
self._active_tab_index = new_tab_index
self.tab_widget.blockSignals(True)
self.tab_widget.setCurrentIndex(new_tab_index)
self.tab_widget.blockSignals(False)
self.tab_widget.setTabsClosable(True)

if len(self.tab_widget.experiments) == 1:
self.tab_widget.setTabsClosable(False)
self.dock_tabs.show()

if file_name is not None:
self._set_opened_file_name(file_name, compared=compared)
self.save_experiment_action.setEnabled(True)
Expand All @@ -351,6 +400,8 @@ def set_experiment(self, experiment, file_name="", *, compared=False):
self.experiment_change = False
self.updateMinMaxValue()
self.update()
if is_new_experiment:
self.tab_widget._restore_tab_state(self._active_tab_index)

def on_selection_changed(self):
if not self.experiment_change:
Expand Down Expand Up @@ -428,6 +479,17 @@ def on_accept():
def getFontSize(self):
return self.plot_formatting_options.font_size

def create_robust_model_wizard(self):
rw = RobustModelWizard()

def on_accept():
robust_experiment = RobustModel.createRobustModel(
experiment_BB=rw.exp_swc,experiment_TIME=rw.exp_time,use_measure=rw.use_measure)
self.set_experiment(robust_experiment)

rw.accepted.connect(on_accept)
rw.open()

def screenshot(self, _checked=False, target=None, name_addition=""):
"""
This function creates a screenshot of this or the target widget
Expand Down
126 changes: 126 additions & 0 deletions extrap/gui/TabBar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# This file is part of the Extra-P software (http://www.scalasca.org/software/extra-p)
#
# Copyright (c) 2020-2025, Technical University of Darmstadt, Germany
#
# This software may be modified and distributed under the terms of a BSD-style license.
# See the LICENSE file in the base directory for details.


from pathlib import Path

from PySide6.QtCore import * # @UnusedWildImport
from PySide6.QtGui import * # @UnusedWildImport
from PySide6.QtWidgets import * # @UnusedWildImport

import extrap
from extrap.util.event import Event
from PySide6.QtCore import * # @UnusedWildImport
from PySide6.QtGui import * # @UnusedWildImport
from PySide6.QtWidgets import * # @UnusedWildImport

class TabBar(QTabBar):
# Store experiments

def __init__(self, main_widget: MainWidget, parent):
super().__init__(parent)
self.main_widget = main_widget
self.experiments = []
self._active_tab_index = 0

def close_tab(self, tab_index: int):
if self.count() == 1:
return
self.blockSignals(True)
self.removeTab(tab_index)
self.blockSignals(False)
self.experiments.pop(tab_index)
if self._active_tab_index > tab_index:
self._active_tab_index -= 1
elif self._active_tab_index == tab_index:
self._active_tab_index = self.currentIndex()
if self.count() > 0:
index = self.currentIndex()
if 0 <= index < len(self.experiments):
self._active_tab_index = index
experiment, file_name = self.experiments[index][:2]
self.main_widget.set_experiment(experiment, file_name)
self._restore_tab_state(index)

def _save_tab_state(self, tab_index: int):
"""Save the metric index and selected callpath rows for tab_index so they can be restored later."""
if 0 <= tab_index < len(self.experiments):
exp, file_name, *_ = self.experiments[tab_index]
metric_index = self.main_widget.selector_widget.metric_selector.currentIndex()
tree_filter = self.main_widget.selector_widget.tree_display_select.currentIndex()

all_selected_nodes = [idx for idx in self.main_widget.selector_widget.tree_view.selectedIndexes() if idx.column() == 0]
traces = []

for selected_node in all_selected_nodes:
#selected_node contains with parent, row , column but only the first are the important
#get current row and check for parent
trace_list = []
trace_list.append(selected_node.row())
parent = selected_node.parent()
while parent.isValid():
trace_list.insert(0, parent.row())
parent = parent.parent()
traces.append(trace_list)

self.experiments[tab_index] = (exp, file_name, metric_index,traces,tree_filter)

def _restore_tab_state(self, index: int):
"""Restore metric selection and callpath selection for the given tab index."""
entry = self.experiments[index]
if len(entry) < 4:
# No saved state yet — auto-expand and select the first visible node
self.main_widget.selector_widget.tree_view.expandAll()
first = self.main_widget.selector_widget.tree_model.index(0, 0)
if first.isValid():
self.main_widget.selector_widget.tree_view.setCurrentIndex(first)
return
_, _, metric_index,traces,tree_filter= entry

#self.selector_widget.tree_view = tree_view

if metric_index >= 0:
self.main_widget.selector_widget.metric_selector.setCurrentIndex(metric_index)

sel_model = self.main_widget.selector_widget.tree_view.selectionModel()
sel_model.clearSelection()

self.main_widget.selector_widget.tree_display_select.setCurrentIndex(tree_filter)

for trace in traces:
parent = None
for i in range(0,len(trace)):
if i == 0:
index = self.main_widget.selector_widget.tree_model.index(trace[i], 0)
if index.isValid():
parent = index
if i == len(trace) - 1:
sel_model.select(index, sel_model.SelectionFlag.Select | sel_model.SelectionFlag.Rows)
else:
self.main_widget.selector_widget.tree_view.expand(index)
continue

if i == len(trace) - 1:
index = self.main_widget.selector_widget.tree_model.index(trace[i], 0,parent)
if index.isValid():
sel_model.select(index, sel_model.SelectionFlag.Select | sel_model.SelectionFlag.Rows)
continue

index = self.main_widget.selector_widget.tree_model.index(trace[i], 0, parent)
if index.isValid():
self.main_widget.selector_widget.tree_view.expand(index)
parent = index


def on_tab_changed(self, index: int):
if 0 <= index < len(self.experiments):
self._save_tab_state(self._active_tab_index)
self._active_tab_index = index
experiment, file_name = self.experiments[index][:2]
self.main_widget.set_experiment(experiment, file_name)
self._restore_tab_state(index)

Loading