From 7f6cc1b9682675c32368c1caa8575eb937929ff3 Mon Sep 17 00:00:00 2001 From: bsteubing Date: Wed, 29 Jul 2026 21:40:30 +0200 Subject: [PATCH 1/5] UI: Activity details process name and location have better colors and switch with them changes --- activity_browser/app/pages/activity_details/activity_header.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/activity_browser/app/pages/activity_details/activity_header.py b/activity_browser/app/pages/activity_details/activity_header.py index 8ac7f6617..ad935474a 100644 --- a/activity_browser/app/pages/activity_details/activity_header.py +++ b/activity_browser/app/pages/activity_details/activity_header.py @@ -147,8 +147,8 @@ def __init__(self, header: ActivityHeader): font.setBold(True) font.setPointSize(font.pointSize() + 3) self.setFont(font) + # Avoid stylesheets here — QSS colors do not track theme changes. self.setFrame(False) - self.setStyleSheet("QLineEdit { padding: 0px; }") fm = QtGui.QFontMetrics(self.font()) self.setFixedHeight(fm.height() + 4) self.setSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed) @@ -260,7 +260,6 @@ def __init__(self, header: ActivityHeader): font.setBold(False) self.setFont(font) self.setFrame(False) - self.setStyleSheet("QLineEdit { padding: 0px; }") fm = QtGui.QFontMetrics(self.font()) self.setFixedHeight(fm.height() + 4) self.setSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed) From bed9d3271ac71e6c1e97db72a15396ac7a139e46 Mon Sep 17 00:00:00 2001 From: bsteubing Date: Wed, 29 Jul 2026 23:04:43 +0200 Subject: [PATCH 2/5] UI: Restructured top menu --- activity_browser/app/README.md | 2 +- .../app/actions/calculation_setup/cs_new.py | 2 +- .../database/database_export_bw2package.py | 2 +- .../actions/database/database_export_excel.py | 2 +- .../database_import_from_ecoinvent.py | 2 +- .../database/database_importer_bw2package.py | 2 +- .../database/database_importer_excel.py | 2 +- .../method/importer/method_importer_bw2io.py | 2 +- .../importer/method_importer_ecoinvent.py | 2 +- .../app/actions/project/project_delete.py | 2 +- .../app/actions/project/project_duplicate.py | 2 +- .../app/actions/project/project_export.py | 2 +- .../app/actions/project/project_import.py | 2 +- .../actions/project/project_manager_open.py | 19 +-- .../app/actions/project/project_new.py | 2 +- activity_browser/app/menu_bar.py | 160 ++++++++---------- .../app/pages/settings/settings_page.py | 32 +++- docs/getting-started/creating-databases.md | 8 +- docs/getting-started/lca-calculations.md | 2 +- docs/getting-started/project-setup.md | 6 +- 20 files changed, 129 insertions(+), 126 deletions(-) diff --git a/activity_browser/app/README.md b/activity_browser/app/README.md index 7e24d9129..cace8c3cd 100644 --- a/activity_browser/app/README.md +++ b/activity_browser/app/README.md @@ -23,7 +23,7 @@ This module orchestrates the main application components including the main wind - `main_window` - MainWindow instance - **`main_window.py`** - MainWindow class that holds the central widget and dock panes -- **`menu_bar.py`** - Application menu bar with File, Edit, View, Tools, Help menus +- **`menu_bar.py`** - Application menu bar with Project, Database, Impact categories, Calculate, View, and Help menus - **`signalling.py`** - ABSignals class that bridges bw2data signals to Qt signals ## Architecture diff --git a/activity_browser/app/actions/calculation_setup/cs_new.py b/activity_browser/app/actions/calculation_setup/cs_new.py index c5d3cedd7..52b7e4682 100644 --- a/activity_browser/app/actions/calculation_setup/cs_new.py +++ b/activity_browser/app/actions/calculation_setup/cs_new.py @@ -33,7 +33,7 @@ class CSNew(ABAction): """ icon = qicons.add - text = "New calculation setup..." + text = "New setup..." @staticmethod @exception_dialogs diff --git a/activity_browser/app/actions/database/database_export_bw2package.py b/activity_browser/app/actions/database/database_export_bw2package.py index 79e744375..bd03fb1c2 100644 --- a/activity_browser/app/actions/database/database_export_bw2package.py +++ b/activity_browser/app/actions/database/database_export_bw2package.py @@ -16,7 +16,7 @@ class DatabaseExportBW2Package(ABAction): """ # icon = icons.qicons.export_db - text = "Export to .bw2package" + text = "Export to .bw2package..." tool_tip = "Export database(s) to BW2Package format" @classmethod diff --git a/activity_browser/app/actions/database/database_export_excel.py b/activity_browser/app/actions/database/database_export_excel.py index 319c83e90..d5cc52c6d 100644 --- a/activity_browser/app/actions/database/database_export_excel.py +++ b/activity_browser/app/actions/database/database_export_excel.py @@ -16,7 +16,7 @@ class DatabaseExportExcel(ABAction): """ icon = application.style().standardIcon(QtWidgets.QStyle.SP_DriveHDIcon) - text = "Export to Excel (.xlsx)" + text = "Export to Excel (.xlsx)..." tool_tip = "Export database(s) to Excel format" @classmethod diff --git a/activity_browser/app/actions/database/database_import_from_ecoinvent.py b/activity_browser/app/actions/database/database_import_from_ecoinvent.py index 10e89eb5e..0e45431e8 100644 --- a/activity_browser/app/actions/database/database_import_from_ecoinvent.py +++ b/activity_browser/app/actions/database/database_import_from_ecoinvent.py @@ -36,7 +36,7 @@ class DatabaseImportFromEcoinvent(ABAction): """ icon = icons.qicons.import_db - text = "Import database from ecoinvent" + text = "Import from ecoinvent..." tool_tip = "Import database from ecoinvent" @staticmethod diff --git a/activity_browser/app/actions/database/database_importer_bw2package.py b/activity_browser/app/actions/database/database_importer_bw2package.py index 375ff1082..2438fddf8 100644 --- a/activity_browser/app/actions/database/database_importer_bw2package.py +++ b/activity_browser/app/actions/database/database_importer_bw2package.py @@ -19,7 +19,7 @@ class DatabaseImporterBW2Package(ABAction): """ABAction to open the DatabaseImportWizard""" icon = icons.qicons.import_db - text = "Import database from .bw2package" + text = "Import from .bw2package..." tool_tip = "Import database from .bw2package" @classmethod diff --git a/activity_browser/app/actions/database/database_importer_excel.py b/activity_browser/app/actions/database/database_importer_excel.py index 567f98552..436bef8d2 100644 --- a/activity_browser/app/actions/database/database_importer_excel.py +++ b/activity_browser/app/actions/database/database_importer_excel.py @@ -18,7 +18,7 @@ class DatabaseImporterExcel(ABAction): """ABAction to open the DatabaseImportWizard""" - text = "Import database from brightway excel format" + text = "Import from .xlsx..." tool_tip = "Import database from brightway excel format" @classmethod diff --git a/activity_browser/app/actions/method/importer/method_importer_bw2io.py b/activity_browser/app/actions/method/importer/method_importer_bw2io.py index cc2fc9c29..4d6dfc8b0 100644 --- a/activity_browser/app/actions/method/importer/method_importer_bw2io.py +++ b/activity_browser/app/actions/method/importer/method_importer_bw2io.py @@ -18,7 +18,7 @@ class MethodImporterBW2IO(MethodImporterEcoinvent): """ABAction to import ecoinvent methods shipped with BW2IO""" icon = icons.qicons.import_db - text = "Import methods from BW2IO" + text = "Import from bw2io..." tool_tip = "Import methods that come shipped with BW2IO" @classmethod diff --git a/activity_browser/app/actions/method/importer/method_importer_ecoinvent.py b/activity_browser/app/actions/method/importer/method_importer_ecoinvent.py index a847e3b1b..2300f0d80 100644 --- a/activity_browser/app/actions/method/importer/method_importer_ecoinvent.py +++ b/activity_browser/app/actions/method/importer/method_importer_ecoinvent.py @@ -17,7 +17,7 @@ class MethodImporterEcoinvent(ABAction): """ABAction to import methods from ecoinvent""" icon = icons.qicons.import_db - text = "Import methods from ecoinvent excel format" + text = "Import from ecoinvent excel..." tool_tip = "Import methods from ecoinvent excel format" @classmethod diff --git a/activity_browser/app/actions/project/project_delete.py b/activity_browser/app/actions/project/project_delete.py index 386cf52af..3bfccb296 100644 --- a/activity_browser/app/actions/project/project_delete.py +++ b/activity_browser/app/actions/project/project_delete.py @@ -40,7 +40,7 @@ class ProjectDelete(ABAction): """ icon = qicons.delete - text = "Delete this project" + text = "Delete" tool_tip = "Delete the project" @staticmethod diff --git a/activity_browser/app/actions/project/project_duplicate.py b/activity_browser/app/actions/project/project_duplicate.py index c6701491b..380cd2b9e 100644 --- a/activity_browser/app/actions/project/project_duplicate.py +++ b/activity_browser/app/actions/project/project_duplicate.py @@ -32,7 +32,7 @@ class ProjectDuplicate(ABAction): """ icon = qicons.copy - text = "Duplicate this project" + text = "Duplicate..." tool_tip = "Duplicate the project" @staticmethod diff --git a/activity_browser/app/actions/project/project_export.py b/activity_browser/app/actions/project/project_export.py index 8b9a83f28..85d154095 100644 --- a/activity_browser/app/actions/project/project_export.py +++ b/activity_browser/app/actions/project/project_export.py @@ -21,7 +21,7 @@ class ProjectExport(ABAction): package the project and save it there. Saving code copied from bw2data.backup. """ icon = app.application.style().standardIcon(QtWidgets.QStyle.SP_DriveHDIcon) - text = "&Export this project..." + text = "Export..." tool_tip = "Export project to file" @staticmethod diff --git a/activity_browser/app/actions/project/project_import.py b/activity_browser/app/actions/project/project_import.py index 002797b9a..5a7e8db90 100644 --- a/activity_browser/app/actions/project/project_import.py +++ b/activity_browser/app/actions/project/project_import.py @@ -24,7 +24,7 @@ class ProjectImport(ABAction): imported project. """ icon = qicons.import_db - text = "&Import a project..." + text = "Import from tar.gz file..." tool_tip = "Import project from a file" @classmethod diff --git a/activity_browser/app/actions/project/project_manager_open.py b/activity_browser/app/actions/project/project_manager_open.py index 5bada520f..743c0b950 100644 --- a/activity_browser/app/actions/project/project_manager_open.py +++ b/activity_browser/app/actions/project/project_manager_open.py @@ -1,26 +1,17 @@ -from qtpy import QtCore - -from activity_browser import app from activity_browser.app.actions.base import ABAction, exception_dialogs from activity_browser.ui.icons import qicons class ProjectManagerOpen(ABAction): - """ - ABAction to delete a database from the project. Asks the user for confirmation. If confirmed, instructs the - DatabaseController to delete the database in question. - """ + """Open Settings focused on the Projects chapter.""" - icon = qicons.delete - text = "Open project manager" + icon = qicons.settings + text = "Manage projects" @staticmethod @exception_dialogs def run(): - from activity_browser.app.panes import ProjectManagerPane + from activity_browser.app.pages.settings import SettingsPage - project_manager = ProjectManagerPane(app.main_window) - app.main_window.addDockWidget( - QtCore.Qt.LeftDockWidgetArea, - project_manager.getDockWidget(app.main_window)) + SettingsPage.open_chapter("Projects") diff --git a/activity_browser/app/actions/project/project_new.py b/activity_browser/app/actions/project/project_new.py index 52ad7d26a..3c20d9c05 100644 --- a/activity_browser/app/actions/project/project_new.py +++ b/activity_browser/app/actions/project/project_new.py @@ -22,7 +22,7 @@ class ProjectNew(ABAction): """ icon = qicons.add - text = "New project" + text = "Empty project..." tool_tip = "Make a new project" @staticmethod diff --git a/activity_browser/app/menu_bar.py b/activity_browser/app/menu_bar.py index 2e64cc80d..9d8297d00 100644 --- a/activity_browser/app/menu_bar.py +++ b/activity_browser/app/menu_bar.py @@ -20,13 +20,17 @@ def __init__(self, window): super().__init__(parent=window) self.project_menu = ProjectMenu(self) - self.view_menu = ViewMenu(self) + self.database_menu = DatabaseMenu(self) + self.impact_categories_menu = ImpactCategoriesMenu(self) self.calculate_menu = CalculateMenu(self) + self.view_menu = ViewMenu(self) self.help_menu = HelpMenu(self) self.addMenu(self.project_menu) - self.addMenu(self.view_menu) + self.addMenu(self.database_menu) + self.addMenu(self.impact_categories_menu) self.addMenu(self.calculate_menu) + self.addMenu(self.view_menu) self.addMenu(self.help_menu) self.search_button = QtWidgets.QPushButton(self) @@ -40,52 +44,74 @@ def __init__(self, window): class ProjectMenu(QtWidgets.QMenu): - """ - Project menu: contains actions related to managing the project, such as project duplication, database importing etc. - """ + """Project lifecycle: open, create, duplicate, export, delete, and manage projects.""" def __init__(self, parent=None) -> None: super().__init__(parent) self.setTitle("&Project") - self.dup_proj_action = app.actions.ProjectDuplicate.get_QAction() - self.delete_proj_action = app.actions.ProjectDelete.get_QAction() + self.dup_proj_action = app.actions.ProjectDuplicate.get_QAction(parent=self) + self.export_proj_action = app.actions.ProjectExport.get_QAction(parent=self) + self.delete_proj_action = app.actions.ProjectDelete.get_QAction(parent=self) + self.manage_proj_action = app.actions.ProjectManagerOpen.get_QAction(parent=self) - self.import_proj_action = app.actions.ProjectImport.get_QAction() - self.export_proj_action = app.actions.ProjectExport.get_QAction() + self.open_menu = ProjectSelectionMenu(self) + self.new_menu = ProjectNewMenu(self) - self.addMenu(ProjectSelectionMenu(self)) - self.addMenu(ProjectNewMenu(self)) + self.addMenu(self.open_menu) + self.addMenu(self.new_menu) self.addAction(self.dup_proj_action) - self.addAction(self.delete_proj_action) - self.addSeparator() - self.addAction(self.import_proj_action) self.addAction(self.export_proj_action) + self.addAction(self.delete_proj_action) self.addSeparator() - self.addMenu(ImportDatabaseMenu(self)) - self.addMenu(ExportDatabaseMenu(self)) + self.addAction(self.manage_proj_action) + + +class DatabaseMenu(QtWidgets.QMenu): + """Database create, import, and export.""" + + def __init__(self, parent=None) -> None: + super().__init__(parent) + + self.setTitle("&Database") + + # Keep Python refs: QActions created with parent=None are otherwise GC'd + # and vanish from the menu (PySide). + self.new_db_action = app.actions.DatabaseNew.get_QAction(parent=self) + self.import_menu = ImportDatabaseMenu(self) + self.export_menu = ExportDatabaseMenu(self) + + self.addAction(self.new_db_action) self.addSeparator() - self.addMenu(ImportICMenu(self)) + self.addMenu(self.import_menu) + self.addMenu(self.export_menu) -class ProjectNewMenu(QtWidgets.QMenu): +class ImpactCategoriesMenu(QtWidgets.QMenu): + """Impact category (LCIA method) import.""" + def __init__(self, parent=None) -> None: super().__init__(parent) - self.setTitle("New project") - self.new_proj_action = app.actions.ProjectNew.get_QAction() - self.import_proj_action = app.actions.ProjectImport.get_QAction() + self.setTitle("&Impact categories") - self.new_proj_action.setText("Empty project") - self.import_proj_action.setText("From .tar.gz file") + self.import_menu = ImportICMenu(self) + self.addMenu(self.import_menu) - self.new_proj_action.setIcon(QtGui.QIcon()) - self.import_proj_action.setIcon(QtGui.QIcon()) + +class ProjectNewMenu(QtWidgets.QMenu): + def __init__(self, parent=None) -> None: + super().__init__(parent) + + self.setTitle("New") + self.new_proj_action = app.actions.ProjectNew.get_QAction(parent=self) + self.import_proj_action = app.actions.ProjectImport.get_QAction(parent=self) + self.template_menu = ProjectNewTemplateMenu(self) self.addAction(self.new_proj_action) self.addAction(self.import_proj_action) - self.addMenu(ProjectNewTemplateMenu(self)) + self.addMenu(self.template_menu) class ProjectNewTemplateMenu(QtWidgets.QMenu): @@ -93,18 +119,18 @@ class ProjectNewTemplateMenu(QtWidgets.QMenu): def __init__(self, parent=None): super().__init__(parent) - self.setTitle("From template") + self.setTitle("Import from template") self.actions = {} for key in get_templates(): - action = app.actions.ProjectNewFromTemplate.get_QAction(key) + action = app.actions.ProjectNewFromTemplate.get_QAction(key, parent=self) action.setText(key) self.actions[key] = action self.addAction(action) for key in self.get_projects(): - action = app.actions.ProjectNewRemote.get_QAction(key) + action = app.actions.ProjectNewRemote.get_QAction(key, parent=self) action.setText(key) self.actions[key] = action self.addAction(action) @@ -123,28 +149,6 @@ class ViewMenu(QtWidgets.QMenu): def __init__(self, parent=None) -> None: super().__init__(parent) self.setTitle("&View") - # - # - # # Populate pages - # self.page_actions = {} - # for page_name in app.pages.base_pages.keys(): - # action = QtWidgets.QAction(page_name, self) - # action.setCheckable(True) - # action.triggered.connect(lambda checked, name=page_name: app.main_window.toggle_page(name)) - # # Update checked state when menu is about to show - # self.page_actions[page_name] = action - # self.addAction(action) - # - # # # Update the checked state when menu is about to show - # # self.aboutToShow.connect(self.update_page_actions) - # - # self.addSeparator() - # - # # def update_page_actions(self): - # # """Update the checked state of page actions based on which pages are visible.""" - # # for page_name, action in self.page_actions.items(): - # # is_visible = app.main_window.is_page_visible(page_name) - # # action.setChecked(is_visible) class CalculateMenu(QtWidgets.QMenu): @@ -157,8 +161,7 @@ def __init__(self, parent=None) -> None: self.setTitle("&Calculate") self.cs_actions = [] - self.new_cs_action = app.actions.CSNew.get_QAction() - self.new_cs_action.setText("New setup...") + self.new_cs_action = app.actions.CSNew.get_QAction(parent=self) self.addAction(self.new_cs_action) self.addSeparator() @@ -168,9 +171,11 @@ def __init__(self, parent=None) -> None: def sync(self): logger.log("SYNC", f"{self.__class__.__name__}: {id(self)}") + for action in self.cs_actions: + self.removeAction(action) self.cs_actions.clear() for cs in bd.calculation_setups: - action = app.actions.CSOpen.get_QAction(cs) + action = app.actions.CSOpen.get_QAction(cs, parent=self) action.setText(cs) self.cs_actions.append(action) self.addAction(action) @@ -200,25 +205,22 @@ def __init__(self, parent=None) -> None: def about(self): """Displays an 'about' window to the user containing e.g. the version of the AB and copyright info""" - # set the window text in html format text = f""" Activity Browser - a graphical interface for Brightway2.

Application version: {version("activity_browser")}
bw2data version: {version("bw2data")}
- bw2io version: {version("bw2calc")}
- bw2calc version: {version("bw2io")}

+ bw2io version: {version("bw2io")}
+ bw2calc version: {version("bw2calc")}

All development happens on github.

For copyright information please see the copyright on this page.

For license information please see the copyright on this page.

""" - # set up the window about_window = QtWidgets.QMessageBox(parent=app.main_window) about_window.setWindowTitle("About the Activity Browser") about_window.setIconPixmap(qicons.ab.pixmap(QSize(150, 150))) about_window.setText(text) - # execute about_window.exec_() def open_wiki(self): @@ -242,7 +244,7 @@ class ProjectSelectionMenu(QtWidgets.QMenu): """ def __init__(self, parent=None): super().__init__(parent) - self.setTitle("Open project") + self.setTitle("Open") self.populate() self.aboutToShow.connect(self.populate) @@ -279,16 +281,12 @@ def populate(self): class ImportDatabaseMenu(QtWidgets.QMenu): def __init__(self, parent=None) -> None: super().__init__(parent=parent) - self.setTitle("Import database") + self.setTitle("Import") self.setIcon(qicons.import_db) - self.import_from_ecoinvent_action = app.actions.DatabaseImportFromEcoinvent.get_QAction() - self.import_from_excel_action = app.actions.DatabaseImporterExcel.get_QAction() - self.import_from_bw2package_action = app.actions.DatabaseImporterBW2Package.get_QAction() - - self.import_from_ecoinvent_action.setText("ecoinvent...") - self.import_from_excel_action.setText("from .xlsx") - self.import_from_bw2package_action.setText("from .bw2package") + self.import_from_excel_action = app.actions.DatabaseImporterExcel.get_QAction(parent=self) + self.import_from_bw2package_action = app.actions.DatabaseImporterBW2Package.get_QAction(parent=self) + self.import_from_ecoinvent_action = app.actions.DatabaseImportFromEcoinvent.get_QAction(parent=self) self.addAction(self.import_from_excel_action) self.addAction(self.import_from_bw2package_action) @@ -299,13 +297,10 @@ def __init__(self, parent=None) -> None: class ExportDatabaseMenu(QtWidgets.QMenu): def __init__(self, parent=None) -> None: super().__init__(parent=parent) - self.setTitle("Export database") + self.setTitle("Export") - self.export_to_excel_action = app.actions.DatabaseExportExcel.get_QAction() - self.export_to_bw2package_action = app.actions.DatabaseExportBW2Package.get_QAction() - - self.export_to_excel_action.setText("to .xlsx") - self.export_to_bw2package_action.setText("to .bw2package") + self.export_to_excel_action = app.actions.DatabaseExportExcel.get_QAction(parent=self) + self.export_to_bw2package_action = app.actions.DatabaseExportBW2Package.get_QAction(parent=self) self.addAction(self.export_to_excel_action) self.addAction(self.export_to_bw2package_action) @@ -314,22 +309,11 @@ def __init__(self, parent=None) -> None: class ImportICMenu(QtWidgets.QMenu): def __init__(self, parent=None) -> None: super().__init__(parent=parent) - self.setTitle("Import impact categories") + self.setTitle("Import") self.setIcon(qicons.import_db) - self.beta_warning = QtWidgets.QWidgetAction(self) - self.beta_warning.setDefaultWidget(QtWidgets.QLabel("Beta features, use at your own risk")) - - self.import_from_ei_excel_action = app.actions.MethodImporterEcoinvent.get_QAction() - self.import_from_bw2io_action = app.actions.MethodImporterBW2IO.get_QAction() + self.import_from_ei_excel_action = app.actions.MethodImporterEcoinvent.get_QAction(parent=self) + self.import_from_bw2io_action = app.actions.MethodImporterBW2IO.get_QAction(parent=self) - self.import_from_ei_excel_action.setText("from ecoinvent excel") - self.import_from_bw2io_action.setText("from bw2io") - - self.import_from_ei_excel_action.setIcon(QtGui.QIcon()) - self.import_from_bw2io_action.setIcon(QtGui.QIcon()) - - self.addAction(self.beta_warning) - self.addSeparator() self.addAction(self.import_from_ei_excel_action) self.addAction(self.import_from_bw2io_action) diff --git a/activity_browser/app/pages/settings/settings_page.py b/activity_browser/app/pages/settings/settings_page.py index 82286d5f0..fecfd32b2 100644 --- a/activity_browser/app/pages/settings/settings_page.py +++ b/activity_browser/app/pages/settings/settings_page.py @@ -59,7 +59,7 @@ def __init__(self, parent=None): # Select first chapter by default self.chapter_list.setCurrentRow(0) - + # Buttons self.button_layout = QtWidgets.QHBoxLayout() self.save_button = QtWidgets.QPushButton("Save") @@ -103,6 +103,34 @@ def build_layout(self): # Set minimum size for resizability self.setMinimumSize(400, 300) + def show_chapter(self, name: str) -> None: + """Select a settings chapter by its sidebar label (e.g. ``Projects``).""" + for index, (chapter_name, _) in enumerate(self.chapters): + if chapter_name == name: + self.chapter_list.setCurrentRow(index) + return + raise ValueError(f"Unknown settings chapter: {name!r}") + + @classmethod + def open_chapter(cls, name: str): + """Show the Settings page and select the given chapter.""" + from activity_browser import app + + central = app.main_window.central_widget + page = app.main_window.findChild(cls) + if page is None: + page = cls(parent=central) + central.addPage(page) + elif central.indexOf(page) >= 0: + central.setCurrentWidget(page) + else: + # Closed base page: re-add without going through the "already present" + # addPage branch (which would deleteLater the live page). + page.toggle_view_action.setChecked(True) + central.addPage(page) + + page.show_chapter(name) + def connect_signals(self): """Connect signals and slots.""" signals.project.changed.connect(self.reset_all) @@ -111,7 +139,7 @@ def connect_signals(self): self.save_button.clicked.connect(self.save_settings) self.cancel_button.clicked.connect(self.cancel_settings) self.restore_defaults_button.clicked.connect(self.restore_defaults) - + # Connect change signals from each chapter for name, chapter in self.chapters: if hasattr(chapter, 'changed'): diff --git a/docs/getting-started/creating-databases.md b/docs/getting-started/creating-databases.md index c4c4a683b..4433c10dd 100644 --- a/docs/getting-started/creating-databases.md +++ b/docs/getting-started/creating-databases.md @@ -17,15 +17,15 @@ Although not strictly necessary, most projects will rely on some sort of backgro If you are using a Brightway template project, it will come pre-loaded with an elementary flow database (e.g. **biosphere3**). If you are starting from an empty project, you will need to import or create one before you can start effectively modeling your own processes. ### Background data from ecoinvent -If you have access to the ecoinvent database, you can import it into your project using the Activity Browser. To do this, open the **Project** menu in the top left corner of the application window and select **Import database > ecoinvent...** This will open a wizard to guide you through importing the ecoinvent database, biosphere and impact categories. +If you have access to the ecoinvent database, you can import it into your project using the Activity Browser. To do this, open the **Database** menu in the top left corner of the application window and select **Import > Import from ecoinvent...** This will open a wizard to guide you through importing the ecoinvent database, biosphere and impact categories. ## Foreground databases Foreground databases contain the processes and products that you create and modify yourself. You can create multiple foreground databases within a single project to help organize your work in a meaningful way. For example, you might have one database for a specific product system and another for a different product system. ### Creating a new foreground database -To create a new foreground database, open the **Project** menu in the top left corner of the application window and select **New database...** You will be prompted to enter a name for your new database. Choose a descriptive name that will help you identify the database later. After entering the name, click **Ok**. The new database will be created and opened in the Activity Browser so you can start adding processes and products. +To create a new foreground database, open the **Database** menu in the top left corner of the application window and select **New database...** You will be prompted to enter a name for your new database. Choose a descriptive name that will help you identify the database later. After entering the name, click **Ok**. The new database will be created and opened in the Activity Browser so you can start adding processes and products. ### Importing and exporting databases -You can also import and export databases in Activity Browser. This is useful if you want to share your databases with others or if you want to back up your work. Activity Browser supports importing and exporting databases in several formats, including Brightway's `.bw2data` format and `.xlsx`. +You can also import and export databases in Activity Browser. This is useful if you want to share your databases with others or if you want to back up your work. Activity Browser supports importing and exporting databases in several formats, including Brightway's `.bw2package` format and `.xlsx`. -To import a database, open the **Project** menu in the top left corner of the application window and select **Import database** You will be prompted to select the file containing the database you want to import. After selecting the file, click **Ok**. The database will be imported and opened in the Activity Browser. +To import a database, open the **Database** menu in the top left corner of the application window and select **Import**, then choose the format that matches your file. You will be prompted to select the file containing the database you want to import. After selecting the file, click **Ok**. The database will be imported and opened in the Activity Browser. diff --git a/docs/getting-started/lca-calculations.md b/docs/getting-started/lca-calculations.md index 5bc3910d7..22644c672 100644 --- a/docs/getting-started/lca-calculations.md +++ b/docs/getting-started/lca-calculations.md @@ -14,7 +14,7 @@ Once you have built your system model, you can proceed to perform Life Cycle Ass In Brightway and Activity Browser, LCA calculations are performed using **Calculation Setups** in which you combine a functional unit with one or more impact assessment methods. You can create, manage, and run calculation setups directly from the Activity Browser interface. ## Creating a calculation setup -To create a new calculation setup, navigate to the **Calculate** menu at the top of the Activity Browser. Click on the **New setup..** button, this will prompt you to enter a name for your new calculation setup. Choose a descriptive name that will help you identify the setup later. After entering the name, click **Ok**. The new calculation setup will be created and opened. +To create a new calculation setup, navigate to the **Calculate** menu at the top of the Activity Browser. Click on **New setup...**, this will prompt you to enter a name for your new calculation setup. Choose a descriptive name that will help you identify the setup later. After entering the name, click **Ok**. The new calculation setup will be created and opened. On the **Calculation Setup Page** you can define functional units and select impact assessment methods for your LCA calculations. Drag and drop products from the **Products pane** onto the Functional unit section of the calculation setup page. This will create a functional unit with the dropped product and a default amount of 1. Drag Impact Categories from the **Impact categories pane** onto the Impact Categories section of the calculation setup page to add them to your setup as well. diff --git a/docs/getting-started/project-setup.md b/docs/getting-started/project-setup.md index 163ffb942..f972bc21c 100644 --- a/docs/getting-started/project-setup.md +++ b/docs/getting-started/project-setup.md @@ -23,16 +23,16 @@ For example, on Windows: How to create a new project in the Activity Browser depends on how you want to set up the base data (e.g. elementary flows and impact categories). You can either start from scratch and import the data you need yourself, or you can use a Brightway template project, that will come pre-loaded with a specific database and impact categories. ### Create a new empty project -To create a new empty project, open the **Project** menu in the top left corner of the application window and select **New project > Empty project**. +To create a new empty project, open the **Project** menu in the top left corner of the application window and select **New > Empty project...**. You will be prompted to enter a name for your new project. Choose a descriptive name that will help you identify the project later. After entering the name, click **Ok**. The new project will be created and set as the active project in Activity Browser. ### Create a new project from a Brightway template -To create a new project from a Brightway template, open the **Project** menu in the top left corner of the application window and select **New project > From template**. This will show a list of available Brightway templates. Choose the template that best fits your needs. +To create a new project from a Brightway template, open the **Project** menu in the top left corner of the application window and select **New > Import from template**. This will show a list of available Brightway templates. Choose the template that best fits your needs. You will be prompted to enter a name for your new project. Choose a descriptive name that will help you identify the project later. After entering the name, click **Ok**. The new project will be downloaded, created, and set as the active project in Activity Browser. ## Importing an existing project or backup Brightway projects can be easily shared and transferred between different computers. If you have received a Brightway project folder from someone else, or if you have a backup of your own project, you can import it into Activity Browser. -To import an existing project or backup, open the **Project** menu in the top left corner of the application window and select **Import a project...**. You will be prompted to select the .7z file containing the Brightway project you want to import. You will be prompted to enter a name for the project after which it will be imported and set as the active project in Activity Browser. +To import an existing project or backup, open the **Project** menu in the top left corner of the application window and select **New > Import from tar.gz file...**. You will be prompted to select the archive containing the Brightway project you want to import. You will be prompted to enter a name for the project after which it will be imported and set as the active project in Activity Browser. From 8c6a144e034eca2639da45c2ceb7bf414a0ce60c Mon Sep 17 00:00:00 2001 From: bsteubing Date: Thu, 30 Jul 2026 15:24:14 +0200 Subject: [PATCH 3/5] change color of star icon in project manager --- activity_browser/static/icons/main/star.png | Bin 1187 -> 1194 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/activity_browser/static/icons/main/star.png b/activity_browser/static/icons/main/star.png index 3269055490ae08c4c768ae044aa57c6cf279d66a..1150b6d8255b8d1cff415ce2ac455f4f548850f3 100644 GIT binary patch delta 1175 zcmV;I1ZexC391Q@BYy;ONkl%ONbps6o$Xs@x{S~x-jX)$y4wRK}F>dgo!2~ zCW83bNzloRE(|V3UAho;Cy0-Ys7u8c2muw}jUr-vEFvb#IOrKJ5Q=;Q@bwA<@9Muv)I2|^ zqz3G5LUR>xTYn`rAS^gP4|8jq{MEnyMp5(7SSbzI)q?PjG2j{!AxlN5O$RqsDH90I zonIqo=o6$Ls-giCZPH5S3~T+?fKcSCV&>P1{nukU&JSjXT?2M?AiTrPW+#0{)^7q` zI<%>wHi1y&>w#~&7-+8&jfvWi45ZRY`@$j<>@1-3Ap z_{uBIqUP6z$X0q=)V_=n;$4Oh{>86YNqAaGQ$_6v;K|Z63=iDacZ!;;e@|24`+)oX zJq8ml%sry!-o8Jx!BFHk15X2|Fj(-=c8jQaoPY1{bT|~b`-PXAq?#$=NzHCiYdf3G zI-w?NA4cc+1?A&FOlvzz<+Dzy#Ae)fv5_Hh01U8%}o)jMeXOZHDJ64 zeSefjI~xQh%A}nQ0uyD@u65%BLXmHd8y?HVx^9-yvmyTaJ>elMllZ6)z;U0u$M#oQ z`9Q5x9}w$>2Ro$uI&g=m&HIl~JW>vT*A&kawPkCI@!7e+vwwZ&r6(rv6L4k=e?1%*CEjSW{~ggRJk&PB zjHr1^o<;*gkzde)@V*-J0B}*e!aGFmHQ++vp}13IC5(k4-;&Y<#+y9%lG!Dqc5geI z6_&!^6}7v8OM#ENh}v^8WO_k%u2VCpb)W%WiuUuqlcM$k1AtKE+khv1okG-n<*Kh5uphX1 pP=t4g+Iw+J-kU>nc9;Rd|G-Lueh@uV>pTDe002ovPDHLkV1fe)GMoSa delta 1168 zcmV;B1aJGQ38M*+BYyw{b3#c}2nYxWd7uw5>3_ahcPhX!es{p_lFzOS4@-+pNQq4dG5THv$R(;^`Spn|@JAigz$QKBo z0vwC^?9{wW0e_`bH*k~37hoOe6_HujSbSB$SSWW181Vtbc?Ia^H%Bn{YwZqD7LhfV zczjgA5F5z@bO8GuWAP$Dy@uz!89JQ;N~tpNEAhgw0X-s8afQis1&lVx*#sPQgavR| z0lN9k0y`U=S^>I6WIcaWZY$sbjmZOa0`KLF<&n7*Ab(ARbJh%Umg~3PpR!cIj>^|`V)gG@vaFwdNvzy5)zyNv* z>;$F@0Lqn51MMOLB2pEh75KIQ;PUnhKNW0)G_%ysh5@-i+lL_k|W=7k@BQ01~GD3-D51j`hCOB5Vh)6o5=x zzYILrkYA${v)@dA)a>R$j`5^+m%QjHKN37+HmCK%`Iz{jaLr8!xP@By$~0J35K z7dVufPuc-mguTE*0Z3PU9(c`&i^(7@LK(PT0Ph2Jx@`scy!utM!tg`@ zkI9#TPqhDM5yx&lU~vzy3-fjSC7`b+)_;q@4;Jyt&Toc*;|4(g0EX+t90vY0i2I=r z0lrEJavW$&5Vs9DpEkA+0e(veZU)$A5dR&5pV%Zh=9~%uypjOa3UCT&u@JchI1M~V zkgv}uhas>N4ZutRubL8%$hRB#xk1iRmjYaf0kNdTvEHeP$XNnD0{)4~{iPED2!Gxx zxfcOof?!#iDe;K>oxo&7{&`md41^Nj1`g&;JOamI;BE*@f7&=EE5YjOZyh)TyeuLU z263!RM7{y~fpY}pYq6yYlvepkg69FRyT0mmFueiX0KRrn0T+QiA~Iz#HWw3-p9rSr i<+NkH{qJy5>>+6D Date: Thu, 30 Jul 2026 16:53:20 +0200 Subject: [PATCH 4/5] Improving Plot/Table copy/export in LCA results --- .../app/pages/lca_results/LCA_results.py | 131 +++++------------- activity_browser/ui/widgets/plot.py | 11 ++ 2 files changed, 42 insertions(+), 100 deletions(-) diff --git a/activity_browser/app/pages/lca_results/LCA_results.py b/activity_browser/app/pages/lca_results/LCA_results.py index 2b2411305..dd089f293 100644 --- a/activity_browser/app/pages/lca_results/LCA_results.py +++ b/activity_browser/app/pages/lca_results/LCA_results.py @@ -5,7 +5,6 @@ :mod:`style`; tables and plots in sibling modules; data builders in ``bwutils``. """ -import os from collections import namedtuple from copy import deepcopy from typing import List, Optional @@ -20,7 +19,6 @@ from stats_arrays.errors import InvalidParamsError from activity_browser import app -from activity_browser.bwutils import filesystem from activity_browser.bwutils.commontasks import get_LCIA_method_name_dict from activity_browser.bwutils.contribution_labels import contribution_axis_unit from activity_browser.bwutils.export_names import ( @@ -77,8 +75,8 @@ "Show full reference-flow, process, and contributor names on axes and titles " "(wrapped where space is limited). Legend entries are always wrapped." ) -ExportTable = namedtuple("export_table", ("label", "copy", "csv", "excel")) -ExportPlot = namedtuple("export_plot", ("label", "png", "svg")) +ExportTable = namedtuple("export_table", ("label", "copy", "csv", "xlsx")) +ExportPlot = namedtuple("export_plot", ("label", "copy", "png", "svg")) PlotTableCheck = namedtuple("plot_table_space", ("plot", "table", "invert")) Combobox = namedtuple( "combobox_menu", @@ -183,46 +181,6 @@ def generate_content_on_click(self, index): # self.tabs.ft.has_been_opened = True # self.tabs.ft.update_tab() - def _scenario_export_filepath(self, default_name: str, file_filter: str): - safe_name = bd.utils.safe_filename(default_name, add_hash=False) - filepath, _ = QtWidgets.QFileDialog.getSaveFileName( - parent=self, - caption="Choose location to save lca results", - dir=str(os.path.join(filesystem.get_project_path(), safe_name)), - filter=file_filter, - ) - return str(filepath) if filepath else filepath - - @QtCore.Slot(name="lciaScenarioExport") - def generate_lcia_scenario_csv(self): - """Create a dataframe of the impact category results for all reference flows, - impact categories and scenarios, then call the 'export to csv' - """ - df = self.mlca.lca_scores_to_dataframe() - default_name = lca_export_basename(self.cs_name, "LCIA results_all") - filepath = self._scenario_export_filepath( - default_name, "Comma Separated Values (*.csv);; All Files (*.*)" - ) - if filepath: - if not filepath.endswith(".csv"): - filepath += ".csv" - df.to_csv(filepath) - - @QtCore.Slot(name="lciaScenarioExport") - def generate_lcia_scenario_excel(self): - """Create a dataframe of the impact category results for all reference flows, - impact categories and scenarios, then call the 'export to excel' - """ - df = self.mlca.lca_scores_to_dataframe() - default_name = lca_export_basename(self.cs_name, "LCIA results_all") - filepath = self._scenario_export_filepath( - default_name, "Excel (*.xlsx);; All Files (*.*)" - ) - if filepath: - if not filepath.endswith(".xlsx"): - filepath += ".xlsx" - df.to_excel(filepath) - class NewAnalysisTab(QtWidgets.QWidget): """Parent class around which all sub-tabs are built.""" @@ -494,6 +452,10 @@ def update_plot(self, *args, **kwargs): """Update the plot.""" self.plot.plot(*args, **kwargs) + def _export_plot_copy(self) -> None: + if self.plot is not None: + self.plot.to_clipboard_png() + def _export_plot_png(self) -> None: if self.plot is not None: self.plot.to_png() @@ -510,57 +472,51 @@ def _export_table_csv(self) -> None: if self.table is not None: self.table.to_csv() - def _export_table_excel(self) -> None: + def _export_table_xlsx(self) -> None: if self.table is not None: self.table.to_excel() def build_export( self, has_table: bool = True, has_plot: bool = True ) -> QtWidgets.QHBoxLayout: - """Construct a custom export button layout. - - Produces layout with buttons for export of relevant sections (plot, table). - Options for figure are: - .png (image format useful for computer generated graphics) - .svg (scalable vector graphic, image is not pixels but data on where lines are, - useful in reports) - Options for Table are: - copy (copies the table to clipboard) - .csv (a comma separated values file of the table, useful for data storage) - Excel (an excel file, useful for exchanging with people and making visualizations) + """Construct the shared plot/table export footer. + + Plot: Copy (PNG clipboard), .png, .svg (disk). + Table: Copy (TSV clipboard), .csv, .xlsx (disk). """ export_menu = QtWidgets.QHBoxLayout() - # Export Plot if has_plot: plot_layout = QtWidgets.QHBoxLayout() self.export_plot = ExportPlot( - QtWidgets.QLabel("Export plot:"), + QtWidgets.QLabel("Plot:"), + QtWidgets.QPushButton("Copy"), QtWidgets.QPushButton(".png"), QtWidgets.QPushButton(".svg"), ) + self.export_plot.copy.setToolTip("Copy the plot to the clipboard as an image") + self.export_plot.copy.clicked.connect(self._export_plot_copy) self.export_plot.png.clicked.connect(self._export_plot_png) self.export_plot.svg.clicked.connect(self._export_plot_svg) for obj in self.export_plot: plot_layout.addWidget(obj) export_menu.addLayout(plot_layout) - # Add seperator if both table and plot exist if has_table and has_plot: export_menu.addWidget(vertical_line()) - # Export Table if has_table: table_layout = QtWidgets.QHBoxLayout() self.export_table = ExportTable( - QtWidgets.QLabel("Export table:"), + QtWidgets.QLabel("Table:"), QtWidgets.QPushButton("Copy"), QtWidgets.QPushButton(".csv"), - QtWidgets.QPushButton("Excel"), + QtWidgets.QPushButton(".xlsx"), ) + self.export_table.copy.setToolTip("Copy the table to the clipboard as text") self.export_table.copy.clicked.connect(self._export_table_copy) self.export_table.csv.clicked.connect(self._export_table_csv) - self.export_table.excel.clicked.connect(self._export_table_excel) + self.export_table.xlsx.clicked.connect(self._export_table_xlsx) for obj in self.export_table: table_layout.addWidget(obj) export_menu.addLayout(table_layout) @@ -1021,25 +977,6 @@ def configure_scenario(self): super().configure_scenario() self._update_selector_visibility() - def build_export( - self, has_table: bool = True, has_plot: bool = True - ) -> QtWidgets.QHBoxLayout: - layout = super().build_export(has_table, has_plot) - if self.has_scenarios: - stretch = layout.takeAt(layout.count() - 1) - exp_layout = QtWidgets.QHBoxLayout() - exp_layout.addWidget(QtWidgets.QLabel("Export all data")) - csv_btn = QtWidgets.QPushButton(".csv") - csv_btn.clicked.connect(self.parent.generate_lcia_scenario_csv) - excel_btn = QtWidgets.QPushButton("Excel") - excel_btn.clicked.connect(self.parent.generate_lcia_scenario_excel) - exp_layout.addWidget(csv_btn) - exp_layout.addWidget(excel_btn) - layout.addWidget(vertical_line()) - layout.addLayout(exp_layout) - layout.addSpacerItem(stretch) - return layout - def update_tab(self): self.update_combobox( self.method_box, [str(m) for m in self.parent.mlca.methods] @@ -2422,7 +2359,7 @@ def export_gsa_data(self, *, as_csv: bool = False): caption = "Export GSA data (CSV)" else: file_filter = self.table.EXCEL_FILTER - caption = "Export GSA data (Excel)" + caption = "Export GSA data (.xlsx)" filepath = self.table.savefilepath( default_name, caption=caption, @@ -2458,33 +2395,27 @@ def update_table(self): def build_export( self, has_table: bool = True, has_plot: bool = True ) -> QtWidgets.QHBoxLayout: - """Plot export, copy-results, and full GSA data export (inputs + outputs).""" + """Plot export plus GSA input/output data export (no standard table group).""" export_layout = super().build_export(has_table=False, has_plot=has_plot) stretch = export_layout.takeAt(export_layout.count() - 1) if has_plot: export_layout.addWidget(vertical_line()) - copy_btn = QtWidgets.QPushButton("Copy") - copy_btn.setToolTip("Copy the GSA results table shown in the UI to the clipboard.") - copy_btn.clicked.connect(self._export_table_copy) - export_layout.addWidget(copy_btn) - - export_layout.addWidget(vertical_line()) gsa_data_layout = QtWidgets.QHBoxLayout() - gsa_data_layout.addWidget(QtWidgets.QLabel("Export GSA data:")) - export_gsa_csv_btn = QtWidgets.QPushButton(".csv") - export_gsa_csv_btn.setToolTip( + gsa_data_layout.addWidget(QtWidgets.QLabel("GSA data:")) + self.export_gsa_csv_btn = QtWidgets.QPushButton(".csv") + self.export_gsa_csv_btn.setToolTip( "Save GSA results and MC inputs as two CSV files (*_output.csv and *_input.csv)" ) - export_gsa_csv_btn.clicked.connect(lambda: self.export_gsa_data(as_csv=True)) - gsa_data_layout.addWidget(export_gsa_csv_btn) - export_gsa_excel_btn = QtWidgets.QPushButton("Excel") - export_gsa_excel_btn.setToolTip( - "Save GSA results and MC inputs to one Excel file (GSA output and GSA input sheets)." + self.export_gsa_csv_btn.clicked.connect(lambda: self.export_gsa_data(as_csv=True)) + gsa_data_layout.addWidget(self.export_gsa_csv_btn) + self.export_gsa_xlsx_btn = QtWidgets.QPushButton(".xlsx") + self.export_gsa_xlsx_btn.setToolTip( + "Save GSA results and MC inputs to one .xlsx file (GSA output and GSA input sheets)." ) - export_gsa_excel_btn.clicked.connect(self.export_gsa_data) - gsa_data_layout.addWidget(export_gsa_excel_btn) + self.export_gsa_xlsx_btn.clicked.connect(self.export_gsa_data) + gsa_data_layout.addWidget(self.export_gsa_xlsx_btn) export_layout.addLayout(gsa_data_layout) if stretch is not None: diff --git a/activity_browser/ui/widgets/plot.py b/activity_browser/ui/widgets/plot.py index b400cbde3..cd461e688 100644 --- a/activity_browser/ui/widgets/plot.py +++ b/activity_browser/ui/widgets/plot.py @@ -868,6 +868,17 @@ def _save_figure(self, extension: str, file_filter: str) -> None: filepath += extension self.figure.savefig(filepath) + def to_clipboard_png(self) -> None: + """Copy the current figure to the system clipboard as a PNG image.""" + import io + + buf = io.BytesIO() + self.figure.savefig(buf, format="png") + image = QtGui.QImage.fromData(buf.getvalue(), "PNG") + if image.isNull(): + return + QtWidgets.QApplication.clipboard().setImage(image) + def to_png(self) -> None: self._save_figure(".png", self.PNG_FILTER) From aca4166df6572dac02d9231cc3c3bf680c206ff9 Mon Sep 17 00:00:00 2001 From: bsteubing Date: Thu, 30 Jul 2026 17:13:21 +0200 Subject: [PATCH 5/5] Enabling Duplicate to database from locked databases; fixing error on "cancel" --- .../app/actions/activity/activity_duplicate_to_db.py | 2 ++ activity_browser/app/panes/database_products.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/activity_browser/app/actions/activity/activity_duplicate_to_db.py b/activity_browser/app/actions/activity/activity_duplicate_to_db.py index d677590a1..8b6770d48 100644 --- a/activity_browser/app/actions/activity/activity_duplicate_to_db.py +++ b/activity_browser/app/actions/activity/activity_duplicate_to_db.py @@ -37,6 +37,8 @@ def run(cls, nodes: List[tuple | int | bd.Node], to_db_name: str = None): return else: to_db_name = cls.request_db(from_db_name) + if not to_db_name: + return to_db_backend = bd.databases[to_db_name]["backend"] diff --git a/activity_browser/app/panes/database_products.py b/activity_browser/app/panes/database_products.py index 3c91c51ea..1a0e3786d 100644 --- a/activity_browser/app/panes/database_products.py +++ b/activity_browser/app/panes/database_products.py @@ -287,7 +287,7 @@ class ContextMenu(ui.widgets.ABMenu): ), lambda m, p: m.add(app.actions.ActivityDuplicateToDB, p.selected_activities, text="Duplicate process to database" if len(p.selected_activities) == 1 else "Duplicate processes to database", - enable=len(p.selected_activities) > 0 and not database_is_locked(p.db_name), + enable=len(p.selected_activities) > 0, ), lambda m: m.addSeparator(), lambda m, p: m.add(app.actions.EditElementaryFlow, p.selected_elementary_flows,