# -*- coding: utf-8 -*-
"""Module to define main window appearance, functions and settings.
Initialize the software appearance and defines interactions with the user.
:Contains:
:Class:
- MainWindow
- _ProcDeleter
"""
##########################################################################
# Populse_mia - Copyright (C) IRMaGe/CEA, 2018
# Distributed under the terms of the CeCILL license, as published by
# the CEA-CNRS-INRIA. Refer to the LICENSE file or to
# http://www.cecill.info/licences/Licence_CeCILL_V2.1-en.html
# for details.
##########################################################################
import glob
import os
import shutil
import subprocess
import sys
import threading
import time
import webbrowser
from datetime import datetime
from os.path import expanduser
import yaml
from packaging import version
from PyQt5.QtCore import QCoreApplication, Qt
# PyQt5 imports
from PyQt5.QtGui import QCursor, QIcon
from PyQt5.QtWidgets import (
QAction,
QApplication,
QMainWindow,
QMenu,
QMessageBox,
QPushButton,
QTabWidget,
QVBoxLayout,
QWidget,
)
import populse_mia.data_manager.data_loader as data_loader
from populse_mia.data_manager.project import (
COLLECTION_CURRENT,
TAG_HISTORY,
Project,
)
# Populse_MIA imports
from populse_mia.data_manager.project_properties import SavedProjects
from populse_mia.software_properties import Config
from populse_mia.user_interface.data_browser.data_browser import DataBrowser
from populse_mia.user_interface.data_viewer.data_viewer_tab import (
DataViewerTab,
)
from populse_mia.user_interface.pipeline_manager.pipeline_manager_tab import (
PipelineManagerTab,
)
from populse_mia.user_interface.pipeline_manager.process_library import (
InstallProcesses,
PackageLibraryDialog,
)
from populse_mia.user_interface.pop_ups import (
PopUpDeletedProject,
PopUpDeleteProject,
PopUpNewProject,
PopUpOpenProject,
PopUpPreferences,
PopUpProperties,
PopUpQuit,
PopUpSaveProjectAs,
PopUpSeeAllProjects,
)
CLINICAL_TAGS = [
"Site",
"Spectro",
"MR",
"PatientRef",
"Pathology",
"Age",
"Sex",
"Message",
]
console_shell_running = False
_ipsubprocs_lock = threading.RLock()
_ipsubprocs = []
class _ProcDeleter(threading.Thread):
"""Used by open_shell."""
def __init__(self, o):
threading.Thread.__init__(self)
self.o = o
def __del__(self):
"""Blabla"""
try:
self.o.kill()
except Exception:
pass
if getattr(self, "console", False):
global console_shell_running
console_shell_running = False
def run(self):
"""Blabla"""
try:
self.o.communicate()
except Exception as e:
print("exception in ipython process:", e)
global _ipsubprocs
try:
with _ipsubprocs_lock:
_ipsubprocs.remove(self)
except Exception:
pass
[docs]
class MainWindow(QMainWindow):
"""Initialize software appearance and define interactions with the user.
.. Methods:
- __init__ : initialise the object MainWindow
- add_clinical_tags: add the clinical tags to the database and the
data browser
- check_unsaved_modifications: check if there are differences
between the current project and the database
- check_database: check if files in database have been modified or
removed since they have been converted for the
first time
- closeEvent: override the closing event to check if there are
unsaved modifications
- create_view_actions: create the actions in each menu
- create_view_menus: create the menu-bar
- create_view_window: create the main window view
- create_project_pop_up: create a new project
- create_tabs: create the tabs
- credits: open the credits in a web browser
- del_clinical_tags: Remove the clinical tags to the database and the
data browser
- documentation: open the documentation in a web browser
- get_controller_version: returns controller_version_changed attribute
- import_data: call the import software (MRI File Manager)
- install_processes_pop_up: open the install processes pop-up
- open_project_pop_up: open a pop-up to open a project and updates
the recent projects
- open_recent_project: open a recent project
- package_library_pop_up: open the package library pop-up
- project_properties_pop_up: open the project properties pop-up
- redo: redo the last action made by the user
- remove_raw_files_useless: remove the useless raw files of the
current project
- save: save either the current project or the current pipeline
- save_as: save either the current project or the current pipeline
under a new name
- save_project_as: open a pop-up to save the current project as
- saveChoice: checks if the project needs to be saved as or just saved
- see_all_projects: open a pop-up to show the recent projects
- set_controller_version: Reverses controller_version_changed attribute
- software_preferences_pop_up: open the Mia preferences pop-up
- switch_project: switches project if it's possible
- tab_changed: method called when the tab is changed
- undo: undoes the last action made by the user
- update_package_library_action: update the package library action
depending on the mode
- update_project: update the project once the database has been
updated
- update_recent_projects_actions: update the list of recent projects
"""
[docs]
def __init__(self, project, test=False, deleted_projects=None):
"""Main window class, initializes the software appearance and defines
interactions with the user.
:Parameter:
- :project: current project in the software
- :test: boolean if the widget is launched from unit tests
or not
- :deleted_projects: projects that have been deleted
"""
super(MainWindow, self).__init__()
QApplication.restoreOverrideCursor()
# We associate these methods and the instance to be able to call them
# from anywhere.
QCoreApplication.instance().title = self.windowTitle
QCoreApplication.instance().set_title = self.setWindowTitle
if deleted_projects is not None and deleted_projects:
self.msg = PopUpDeletedProject(deleted_projects)
self.config = Config()
self.config.setSourceImageDir(
os.path.join(
os.path.dirname(os.path.dirname(os.path.realpath(__file__))),
"sources_images",
)
)
self.windowName = "MIA - Multiparametric Image Analysis"
self.projectName = "Unnamed project"
self.project = project
self.test = test
self.saved_projects = SavedProjects()
self.saved_projects_list = self.saved_projects.pathsList
self.saved_projects_actions = []
self.controller_version_changed = False
# Define main window view
self.create_view_window()
# Initialize menu
self.menu_file = self.menuBar().addMenu("File")
self.menu_edition = self.menuBar().addMenu("Edit")
self.menu_help = self.menuBar().addMenu("Help")
self.menu_about = self.menuBar().addMenu("About")
self.menu_more = self.menuBar().addMenu("More")
self.menu_install_process = QMenu("Install processes", self)
self.menu_saved_projects = QMenu("Saved projects", self)
# Initialize tabs
self.tabs = QTabWidget()
self.data_browser = DataBrowser(self.project, self)
self.data_viewer = DataViewerTab(self)
self.pipeline_manager = PipelineManagerTab(self.project, [], self)
self.centralWindow = QWidget()
# Initialize menu actions
sources_images_dir = Config().getSourceImageDir()
self.action_save_project = self.menu_file.addAction("Save project")
self.action_save_project_as = self.menu_file.addAction(
"Save project as"
)
self.action_delete_project = self.menu_file.addAction("Delete project")
self.action_create = QAction("New project", self)
self.action_open = QAction("Open project", self)
self.action_save = QAction("Save", self)
self.action_save_as = QAction("Save as", self)
self.action_delete = QAction("Delete project", self)
self.action_import = QAction(
QIcon(os.path.join(sources_images_dir, "Blue.png")), "Import", self
)
self.action_check_database = QAction("Check the whole database", self)
self.action_see_all_projects = QAction("See all projects", self)
self.action_project_properties = QAction("Project properties", self)
self.action_software_preferences = QAction("MIA preferences", self)
self.action_package_library = QAction("Package library manager", self)
self.action_open_shell = QAction("Open python shell", self)
self.action_exit = QAction(
QIcon(os.path.join(sources_images_dir, "exit.png")), "Exit", self
)
self.action_undo = QAction("Undo", self)
self.action_redo = QAction("Redo", self)
self.action_documentation = QAction("Documentation", self)
self.action_credits = QAction("Credits", self)
self.action_install_processes_folder = QAction("From folder", self)
self.action_install_processes_zip = QAction("From zip file", self)
# Connect actions & menus views
self.create_view_actions()
self.create_view_menus()
# Create Tabs
self.create_tabs()
self.setCentralWidget(self.centralWindow)
if self.config.get_mainwindow_maximized():
self.showMaximized()
else:
size = self.config.get_mainwindow_size()
if size:
self.resize(size[0], size[1])
[docs]
@staticmethod
def last_window_closed():
"""Force exit the event loop after ipython console is closed.
If the ipython console has been run, something prevents Qt from
quitting after the window is closed. The cause is not known yet.
So: force exit the event loop.
"""
from soma.qt_gui.qt_backend import Qt
Qt.QTimer.singleShot(10, Qt.qApp.exit)
[docs]
def add_clinical_tags(self):
"""Add the clinical tags to the database and the data browser"""
added_tags = self.project.add_clinical_tags()
for tag in added_tags:
column = self.data_browser.table_data.get_index_insertion(tag)
self.data_browser.table_data.add_column(column, tag)
# self.project.unsavedModifications = True
[docs]
def check_unsaved_modifications(self):
"""Check if there are differences between the current project and the
database.
:return: Boolean. True if there are unsaved modifications,
False otherwise
"""
if self.project.isTempProject:
if (
len(
self.project.session.get_documents_names(
COLLECTION_CURRENT
)
)
> 0
):
return True
else:
return False
elif self.project.hasUnsavedModifications():
return True
else:
return False
[docs]
def check_database(self):
"""Check if files in database have been modified since first import."""
if self.project is None:
return
QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
print("verify scans...")
t0 = time.time()
problem_list = data_loader.verify_scans(self.project)
print("check time:", time.time() - t0)
QApplication.restoreOverrideCursor()
# Message if invalid files
if problem_list:
str_msg = ""
for element in problem_list:
str_msg += element + "\n\n"
msg = QMessageBox()
msg.setIcon(QMessageBox.Warning)
msg.setText(
"These files have been modified or removed since "
"they have been converted for the first time:"
)
msg.setInformativeText(str_msg)
msg.setWindowTitle("Warning")
msg.setStandardButtons(QMessageBox.Ok)
msg.buttonClicked.connect(msg.close)
msg.exec()
[docs]
def closeEvent(self, event):
"""Override the QWidget closing event to check if there are unsaved
modifications
:param event: closing event
"""
if self.check_unsaved_modifications() is False or self.test:
can_exit = True
else:
self.pop_up_close = PopUpQuit(self.project)
self.pop_up_close.save_as_signal.connect(self.saveChoice)
self.pop_up_close.exec()
can_exit = self.pop_up_close.can_exit()
if can_exit:
if self.pipeline_manager.init_clicked:
self.project.unsaveModifications()
for brick in self.pipeline_manager.brick_list:
self.data_browser.table_data.delete_from_brick(brick)
# Clean up
config = Config()
opened_projects = config.get_opened_projects()
if self.project.folder in opened_projects:
opened_projects.remove(self.project.folder)
config.set_opened_projects(opened_projects)
# Change controller version if needed
if self.controller_version_changed:
self.msg = QMessageBox()
self.msg.setIcon(QMessageBox.Warning)
self.msg.setText("Controller version change")
self.msg.setInformativeText(
"A change of controller version, from {0} to {1}, "
"is planned for next start-up. Do you confirm that "
"you would like to perform this "
"change?".format(
"V1" if config.isControlV1() else "V2",
"V2" if config.isControlV1() else "V1",
)
)
self.msg.setWindowTitle("Warning")
self.msg.setStandardButtons(
QMessageBox.Yes | QMessageBox.Cancel
)
return_value = self.msg.exec()
if return_value == QMessageBox.Yes:
config.setControlV1(not config.isControlV1())
self.msg.close()
config.saveConfig()
self.remove_raw_files_useless()
event.accept()
else:
event.ignore()
if self.data_browser.viewer:
self.data_browser.viewer.clear()
if self.data_viewer:
self.data_viewer.clear()
[docs]
def create_view_actions(self):
"""Create the actions and their shortcuts in each menu"""
self.action_create.setShortcut("Ctrl+N")
self.action_open.setShortcut("Ctrl+O")
self.action_save.setShortcut("Ctrl+S")
self.addAction(self.action_save)
self.action_save_as.setShortcut("Ctrl+Shift+S")
self.addAction(self.action_save_as)
self.addAction(self.action_delete)
self.action_import.setShortcut("Ctrl+I")
for i in range(self.config.get_max_projects()):
self.saved_projects_actions.append(
QAction(
self, visible=False, triggered=self.open_recent_project
)
)
# if Config().get_user_mode() == True:
# self.action_package_library.setDisabled(True)
# else:
# self.action_package_library.setEnabled(True)
if Config().get_user_mode() is True:
self.action_delete_project.setDisabled(True)
else:
self.action_delete_project.setEnabled(True)
self.action_exit.setShortcut("Ctrl+W")
self.action_undo.setShortcut("Ctrl+Z")
self.action_redo.setShortcut("Ctrl+Y")
# if Config().get_user_mode() == True:
# self.action_install_processes.setDisabled(True)
# else:
# self.action_install_processes.setEnabled(True)
# Connection of the several triggered signals of the actions to some
# other methods
self.action_create.triggered.connect(self.create_project_pop_up)
self.action_open.triggered.connect(self.open_project_pop_up)
self.action_exit.triggered.connect(self.close)
self.action_check_database.triggered.connect(self.check_database)
self.action_open_shell.triggered.connect(self.open_shell)
self.action_save.triggered.connect(self.save)
self.action_save_as.triggered.connect(self.save_as)
self.action_delete.triggered.connect(self.delete_project)
self.action_import.triggered.connect(self.import_data)
self.action_see_all_projects.triggered.connect(self.see_all_projects)
self.action_project_properties.triggered.connect(
self.project_properties_pop_up
)
self.action_software_preferences.triggered.connect(
self.software_preferences_pop_up
)
self.action_package_library.triggered.connect(
self.package_library_pop_up
)
self.action_undo.triggered.connect(self.undo)
self.action_redo.triggered.connect(self.redo)
self.action_documentation.triggered.connect(self.documentation)
self.action_credits.triggered.connect(self.credits)
self.action_install_processes_folder.triggered.connect(
lambda: self.install_processes_pop_up(folder=True)
)
self.action_install_processes_zip.triggered.connect(
lambda: self.install_processes_pop_up(folder=False)
)
[docs]
def create_view_menus(self):
"""Create the menu-bar view."""
self.menu_more.addMenu(self.menu_install_process)
# Actions in the "File" menu
self.menu_file.addAction(self.action_create)
self.menu_file.addAction(self.action_open)
self.menu_file.addAction(self.action_check_database)
self.action_save_project.triggered.connect(self.saveChoice)
self.action_save_project_as.triggered.connect(self.save_project_as)
self.action_delete_project.triggered.connect(self.delete_project)
self.menu_file.addSeparator()
self.menu_file.addAction(self.action_import)
self.menu_file.addSeparator()
self.menu_file.addMenu(self.menu_saved_projects)
for i in range(self.config.get_max_projects()):
self.menu_saved_projects.addAction(self.saved_projects_actions[i])
self.menu_saved_projects.addSeparator()
self.menu_saved_projects.addAction(self.action_see_all_projects)
self.menu_file.addSeparator()
self.menu_file.addAction(self.action_software_preferences)
self.menu_file.addAction(self.action_project_properties)
self.menu_file.addAction(self.action_package_library)
self.menu_file.addSeparator()
self.menu_file.addAction(self.action_open_shell)
self.menu_file.addSeparator()
self.menu_file.addAction(self.action_exit)
self.update_recent_projects_actions()
# Actions in the "Edition" menu
self.menu_edition.addAction(self.action_undo)
self.menu_edition.addAction(self.action_redo)
# Actions in the "Help" menu
self.menu_help.addAction(self.action_documentation)
self.menu_help.addAction(self.action_credits)
# Actions in the "More > Install processes" menu
self.menu_install_process.addAction(
self.action_install_processes_folder
)
self.menu_install_process.addAction(self.action_install_processes_zip)
[docs]
def create_view_window(self):
"""Create the main window view."""
sources_images_dir = Config().getSourceImageDir()
app_icon = QIcon(
os.path.join(sources_images_dir, "Logo_populse_mia_LR.jpeg")
)
self.setWindowIcon(app_icon)
background_color = self.config.getBackgroundColor()
text_color = self.config.getTextColor()
if not self.config.get_user_mode():
self.windowName += " (Admin mode)"
self.windowName += " - "
self.setStyleSheet(
"background-color:"
+ background_color
+ ";color:"
+ text_color
+ ";"
)
self.statusBar().showMessage(
"Please create a new project (Ctrl+N) or "
"open an existing project (Ctrl+O)"
)
self.setWindowTitle(self.windowName + self.projectName)
[docs]
def create_project_pop_up(self):
"""Create a new project."""
if self.check_unsaved_modifications():
self.pop_up_close = PopUpQuit(self.project)
self.pop_up_close.save_as_signal.connect(self.saveChoice)
self.pop_up_close.exec()
can_switch = self.pop_up_close.can_exit()
else:
can_switch = True
if can_switch:
# Opens a pop-up when the 'New project' action is clicked and
# updates the recent projects
try:
self.exPopup = PopUpNewProject()
except Exception as e:
print("\ncreate_project_pop_up: ", e)
self.msg = QMessageBox()
self.msg.setIcon(QMessageBox.Critical)
self.msg.setText("Invalid projects folder path")
self.msg.setInformativeText(
"The projects folder path in MIA preferences is invalid!"
)
self.msg.setWindowTitle("Error")
yes_button = self.msg.addButton(
"Open MIA preferences", QMessageBox.YesRole
)
self.msg.addButton(QMessageBox.Ok)
self.msg.exec()
if self.msg.clickedButton() == yes_button:
self.software_preferences_pop_up()
self.msg.close()
else:
self.msg.close()
else:
if self.exPopup.exec():
self.exPopup.get_filename(self.exPopup.selectedFiles())
file_name = self.exPopup.relative_path
# Removing the old project from the list of
# currently opened projects
config = Config()
opened_projects = config.get_opened_projects()
opened_projects.remove(self.project.folder)
config.set_opened_projects(opened_projects)
config.saveConfig()
self.remove_raw_files_useless() # We remove the useless
# files from the old project
self.project = Project(self.exPopup.relative_path, True)
self.update_project(
file_name
) # project updated everywhere
[docs]
def create_tabs(self):
"""Create the tabs and initializes the DataBrowser and PipelineManager
classes."""
self.config = Config()
self.tabs.setAutoFillBackground(False)
self.tabs.setStyleSheet("QTabBar{font-size:16pt;text-align: center}")
self.tabs.setMovable(True)
self.tabs.addTab(self.data_browser, "Data Browser")
self.tabs.addTab(self.data_viewer, "Data Viewer")
self.tabs.addTab(self.pipeline_manager, "Pipeline Manager")
self.tabs.currentChanged.connect(self.tab_changed)
vertical_layout = QVBoxLayout()
vertical_layout.addWidget(self.tabs)
self.centralWindow.setLayout(vertical_layout)
[docs]
def credits(self):
"""Open the credits in a web browser"""
webbrowser.open(
"https://github.com/populse/populse_mia/graphs/contributors"
)
[docs]
def del_clinical_tags(self):
"""Remove the clinical tags to the database and the data browser"""
removed_tags = self.project.del_clinical_tags()
for tag in removed_tags:
self.data_browser.table_data.removeColumn(
self.data_browser.table_data.get_tag_column(tag)
)
# self.project.unsavedModifications = True
[docs]
def delete_project(self):
"""Open a pop-up to open a project and updates the recent projects."""
try:
self.exPopup = PopUpDeleteProject(self)
except Exception as e:
print("\ndelete_project: ", e)
self.msg = QMessageBox()
self.msg.setIcon(QMessageBox.Critical)
self.msg.setText("Invalid projects folder path")
self.msg.setInformativeText(
"The projects folder path in MIA preferences is invalid!"
)
self.msg.setWindowTitle("Error")
yes_button = self.msg.addButton(
"Open MIA preferences", QMessageBox.YesRole
)
self.msg.addButton(QMessageBox.Ok)
self.msg.exec()
if self.msg.clickedButton() == yes_button:
self.software_preferences_pop_up()
self.msg.close()
else:
self.msg.close()
else:
self.exPopup.exec()
[docs]
@staticmethod
def documentation():
"""Open the documentation in a web browser."""
webbrowser.open(
"https://populse.github.io/populse_mia/html/index.html"
)
[docs]
def get_controller_version(self):
"""Gives the value of the controller_version_changed attribute.
:return: Boolean
"""
return self.controller_version_changed
[docs]
def install_processes_pop_up(self, folder=False):
"""Open the install processes pop-up.
:param folder: boolean, True if installing from a folder
"""
self.pop_up_install_processes = InstallProcesses(self, folder=folder)
self.pop_up_install_processes.show()
self.pop_up_install_processes.process_installed.connect(
self.pipeline_manager.processLibrary.update_process_library
)
self.pop_up_install_processes.process_installed.connect(
self.pipeline_manager.processLibrary.pkg_library.update_config
)
[docs]
def import_data(self):
"""Call the import software (MRI File Manager), reads the imported
files and loads them into the database.
"""
# Opens the conversion software to convert the MRI files in Nifti/Json
config = Config()
home = expanduser("~")
print("\nmri_conv opening ...\n")
try:
code_exit = subprocess.call(
[
"java",
"-Xmx4096M",
"-jar",
config.get_mri_conv_path(),
"[ProjectsDir] " + home,
"[ExportNifti] "
+ os.path.join(self.project.folder, "data", "raw_data"),
"[ExportToMIA] PatientName-StudyName-"
"CreationDate-SeqNumber-Protocol-"
"SequenceName-AcquisitionTime",
"CloseAfterExport",
"[ExportOptions] 00013",
]
)
if code_exit != 0 and code_exit != 100:
raise ValueError("mri_conv did not run properly!")
except ValueError:
print("\nTrial with a lower maximum heap size ...\n")
code_exit = subprocess.call(
[
"java",
"-Xmx1024M",
"-jar",
config.get_mri_conv_path(),
"[ProjectsDir] " + home,
"[ExportNifti] "
+ os.path.join(self.project.folder, "data", "raw_data"),
"[ExportToMIA] PatientName-StudyName-"
"CreationDate-SeqNumber-Protocol-"
"SequenceName-AcquisitionTime",
"CloseAfterExport",
"[ExportOptions] 00013",
]
)
# 'NoLogExport'if we don't want log export
if code_exit == 0:
# Database filled
new_scans = data_loader.read_log(self.project, self)
# Table updated
documents = self.project.session.get_documents_names(
COLLECTION_CURRENT
)
self.data_browser.table_data.scans_to_visualize = documents
self.data_browser.table_data.scans_to_search = documents
self.data_browser.table_data.add_columns()
self.data_browser.table_data.fill_headers()
self.data_browser.table_data.add_rows(new_scans)
self.data_browser.reset_search_bar()
self.data_browser.frame_advanced_search.setHidden(True)
self.data_browser.advanced_search.rows = []
self.project.unsavedModifications = True
elif code_exit == 100: # User only close mri_conv and do nothing
pass
else:
print(
"\nmri_conv, did not work properly. Current absolute"
"path to MRIManager.jar defined in File > MIA Preferences:"
"\n{0}\n".format(config.get_mri_conv_path())
)
if not os.path.isfile(config.get_mri_conv_path()):
mssgText = (
"Warning: mri_conv did not work properly. The "
"current absolute path to MRIManager.jar doesn't "
"seem to be correctly defined.\nCurrent absolute "
"path to MRIManager.jar defined in\nFile > MIA "
"Preferences:\n{0}".format(config.get_mri_conv_path())
)
else:
mssgText = (
"Warning : mri_conv did not work properly. Please "
"check if the currently installed mri_conv Java "
"ARchive is not corrupted.\nCurrent absolute path "
"to MRIManager.jar defined in\nFile > MIA "
"Preferences:\n{0}".format(config.get_mri_conv_path())
)
msg = QMessageBox()
msg.setIcon(QMessageBox.Warning)
msg.setWindowTitle("populse_mia - Warning: Data import issue!")
msg.setText(mssgText)
msg.setStandardButtons(QMessageBox.Ok)
msg.buttonClicked.connect(msg.close)
msg.exec()
[docs]
def open_project_pop_up(self):
"""Open a pop-up to open a project and updates the recent projects."""
# Ui_Dialog() is defined in pop_ups.py
# We check for unsaved modifications
if self.check_unsaved_modifications():
# If there are unsaved modifications, we ask the user what he
# wants to do
self.pop_up_close = PopUpQuit(self.project)
self.pop_up_close.save_as_signal.connect(self.saveChoice)
self.pop_up_close.exec()
can_switch = self.pop_up_close.can_exit()
else:
can_switch = True
# We can open a new project
if can_switch:
try:
self.exPopup = PopUpOpenProject()
except Exception as e:
print("\nopen_project_pop_up: ", e)
self.msg = QMessageBox()
self.msg.setIcon(QMessageBox.Critical)
self.msg.setText("Invalid projects folder path")
self.msg.setInformativeText(
"The projects folder path in MIA preferences is invalid!"
)
self.msg.setWindowTitle("Error")
yes_button = self.msg.addButton(
"Open MIA preferences", QMessageBox.YesRole
)
self.msg.addButton(QMessageBox.Ok)
self.msg.exec()
if self.msg.clickedButton() == yes_button:
self.software_preferences_pop_up()
self.msg.close()
else:
self.msg.close()
else:
if self.exPopup.exec():
project_name = self.exPopup.selectedFiles()
self.exPopup.get_filename(project_name)
project_name = self.exPopup.relative_path
self.data_browser.data_sent = False
# We switch the project
self.switch_project(project_name, self.exPopup.name)
field_names = self.project.session.get_fields_names(
COLLECTION_CURRENT
)
if all(ele in field_names for ele in CLINICAL_TAGS):
Config().set_clinical_mode(True)
else:
Config().set_clinical_mode(False)
# Update the history and brick tables in the newly opened
# project, if it comes from outside.
path_name = os.path.dirname(
os.path.abspath(os.path.normpath(project_name))
)
projectsPath = os.path.abspath(
self.config.getPathToProjectsFolder()
)
if path_name != projectsPath:
self.project.update_db_for_paths(path_name)
[docs]
def open_recent_project(self):
"""Open a recent project."""
# We check for unsaved modifications
if self.check_unsaved_modifications():
# If there are unsaved modifications, we ask the user what he
# wants to do
self.pop_up_close = PopUpQuit(self.project)
self.pop_up_close.save_as_signal.connect(self.saveChoice)
self.pop_up_close.exec()
can_switch = self.pop_up_close.can_exit()
else:
can_switch = True
# We can open a new project
if can_switch:
action = self.sender()
if action:
project_name = action.data()
entire_path = os.path.abspath(project_name)
path, name = os.path.split(entire_path)
relative_path = os.path.relpath(project_name)
self.switch_project(relative_path, name)
# We switch the project
field_names = self.project.session.get_fields_names(
COLLECTION_CURRENT
)
documents = self.project.session.get_documents_names(
COLLECTION_CURRENT
)
self.data_viewer.set_documents(self.project, documents)
if all(ele in field_names for ele in CLINICAL_TAGS):
Config().set_clinical_mode(True)
else:
Config().set_clinical_mode(False)
[docs]
def package_library_pop_up(self):
"""Open the package library pop-up"""
self.pop_up_package_library = PackageLibraryDialog(
mia_main_window=self
)
self.pop_up_package_library.setGeometry(300, 200, 800, 600)
self.pop_up_package_library.show()
self.pop_up_package_library.signal_save.connect(
self.pipeline_manager.processLibrary.update_process_library
)
[docs]
def project_properties_pop_up(self):
"""Open the project properties pop-up"""
old_tags = self.project.session.get_shown_tags()
self.pop_up_settings = PopUpProperties(
self.project, self.data_browser, old_tags
)
self.pop_up_settings.setGeometry(300, 200, 800, 600)
self.pop_up_settings.show()
if self.pop_up_settings.exec():
self.data_browser.table_data.update_visualized_columns(
old_tags, self.project.session.get_shown_tags()
)
[docs]
def redo(self):
"""Redo the last action made by the user."""
if (
self.tabs.tabText(self.tabs.currentIndex()).replace("&", "", 1)
== "Data Browser"
):
# In Data Browser
self.project.redo(self.data_browser.table_data)
# Action remade in the Database
elif (
self.tabs.tabText(self.tabs.currentIndex()).replace("&", "", 1)
== "Pipeline Manager"
):
# In Pipeline Manager
self.pipeline_manager.redo()
[docs]
def remove_raw_files_useless(self):
"""Remove the useless raw files of the current project, close the
database connection. The project is not valid any longer after this
call."""
folder = self.project.folder
# If it's unnamed project, we can remove the whole project
if self.project.isTempProject:
# close database, and files
self.project.session = None
self.project.database.__exit__(None, None, None)
self.project.database = None
shutil.rmtree(folder)
else:
# I don't understand why files from raw_data are automatically
# transferred to derived_data, if they are not in db. I comment
# on this feature in the following lines. We can uncomment if
# this action makes sense ...
# for filename in glob.glob(
# os.path.join(os.path.abspath(folder), "data", "raw_data", "*")
# ):
# scan = os.path.basename(filename)
# # The file is removed only if it's not a scan in the project,
# # and if it's not a logExport
# # Json files associated to nii files are kept for the raw_
# # data folder
# file_name, file_extension = os.path.splitext(scan)
# file_in_database = False
#
# for database_scan in self.project.session.get_documents_names(
# COLLECTION_CURRENT
# ):
# if file_name in database_scan:
# file_in_database = True
#
# if "logExport" in scan:
# file_in_database = True
#
# if not file_in_database:
# os.rename(filename, filename.replace("raw_data",
# "derived_data"))
# I don't understand why files from derived_data are automatically
# deleted if they are not in db. I comment on this feature in the
# following lines. We can uncomment if this action makes sense ...
# for filename in glob.glob(
# os.path.join(os.path.relpath(
# self.project.folder), 'data', 'derived_data', '*')):
# scan = os.path.basename(filename)
# # The file is removed only if it's not a scan in the project,
# # and if it's not a logExport
# if (self.project.session.get_document(
# COLLECTION_CURRENT, os.path.join(
# "data", "derived_data", scan)) is None and
# "logExport" not in scan):
# os.remove(filename)
# I don't understand why files in downloaded_data are
# automatically deleted if they are not in db. I comment on this
# feature in the following line. We can uncomment if this action
# makes sense...
# for filename in glob.glob(
# os.path.join(
# os.path.abspath(self.project.folder),
# "data",
# "downloaded_data",
# "*",
# )
# ):
# scan = os.path.basename(filename)
#
# # The file is removed only if it's not a scan in the project,
# # and if it's not a logExport
# if (
# self.project.session.get_document(
# COLLECTION_CURRENT,
# os.path.join("data", "downloaded_data", scan),
# )
# is None
# and "logExport" not in scan
# ):
# os.remove(filename)
# self.project.unsavedModifications = True
# close database, and files
self.project.session = None
self.project.database.__exit__(None, None, None)
self.project.database = None
self.project = None
[docs]
def save(self):
"""Save either the current project or the current pipeline"""
if (
self.tabs.tabText(self.tabs.currentIndex()).replace("&", "", 1)
== "Data Browser"
):
# In Data Browser
self.saveChoice()
elif (
self.tabs.tabText(self.tabs.currentIndex()).replace("&", "", 1)
== "Pipeline Manager"
):
# In Pipeline Manager
self.pipeline_manager.savePipeline()
[docs]
def save_as(self):
"""Save either the current project or the current pipeline under a new
name.
"""
if (
self.tabs.tabText(self.tabs.currentIndex()).replace("&", "", 1)
== "Data Browser"
):
# In Data Browser
self.save_project_as()
elif (
self.tabs.tabText(self.tabs.currentIndex()).replace("&", "", 1)
== "Pipeline Manager"
):
# In Pipeline Manager
self.pipeline_manager.savePipelineAs()
[docs]
def save_project_as(self):
"""Open a pop-up to save the current project as"""
try:
self.exPopup = PopUpSaveProjectAs()
except Exception as e:
print("\nsave_project_as: ", e)
self.msg = QMessageBox()
self.msg.setIcon(QMessageBox.Critical)
self.msg.setText("Invalid projects folder path")
self.msg.setInformativeText(
"The projects folder path in MIA preferences is invalid!"
)
self.msg.setWindowTitle("Error")
yes_button = self.msg.addButton(
"Open MIA preferences", QMessageBox.YesRole
)
self.msg.addButton(QMessageBox.Ok)
self.msg.exec()
if self.msg.clickedButton() == yes_button:
self.software_preferences_pop_up()
self.msg.close()
else:
self.msg.close()
else:
if self.test:
self.exPopup.exec = lambda x=0: True
self.exPopup.validate = True
self.exPopup.new_project.text = lambda x=0: "something"
self.exPopup.return_value()
self.exPopup.exec()
if self.exPopup.validate:
old_folder_rel = self.project.folder
old_folder = os.path.abspath(old_folder_rel)
as_folder_rel = self.exPopup.relative_path
as_folder = os.path.abspath(as_folder_rel)
if as_folder_rel == old_folder_rel:
self.project.saveModifications()
return True
database_path = os.path.join(as_folder, "database")
properties_path = os.path.join(as_folder, "properties")
filters_path = os.path.join(as_folder, "filters")
data_path = os.path.join(as_folder, "data")
raw_data_path = os.path.join(data_path, "raw_data")
downloaded_data_path = os.path.join(
data_path, "downloaded_data"
)
# List of projects updated
if not self.test:
self.saved_projects_list = (
self.saved_projects
).addSavedProject(as_folder_rel)
self.update_recent_projects_actions()
if os.path.exists(as_folder_rel):
# Prevent by a careful message
# see PopUpSaveProjectAs/return_value
# in admin mode only
shutil.rmtree(as_folder_rel)
if not os.path.exists(as_folder_rel):
os.makedirs(as_folder_rel)
os.mkdir(data_path)
os.mkdir(raw_data_path)
# os.mkdir(derived_data_path)
os.mkdir(downloaded_data_path)
os.mkdir(filters_path)
# Data files copied
if os.path.exists(os.path.join(old_folder_rel, "data")):
for filename in glob.glob(
os.path.join(old_folder, "data", "raw_data", "*")
):
shutil.copy(
filename, os.path.join(data_path, "raw_data")
)
# for filename in glob.glob(
# os.path.join(old_folder, "data", "derived_data", "*")
# ):
# shutil.copy(
# filename, os.path.join(data_path, "derived_data")
# )
shutil.copytree(
os.path.join(old_folder, "data", "derived_data"),
os.path.join(data_path, "derived_data"),
)
for filename in glob.glob(
os.path.join(
old_folder, "data", "downloaded_data", "*"
)
):
shutil.copy(
filename,
os.path.join(data_path, "downloaded_data"),
)
if os.path.exists(os.path.join(old_folder_rel, "filters")):
for filename in glob.glob(
os.path.join(old_folder, "filters", "*")
):
shutil.copy(filename, os.path.join(filters_path))
# First we register the Database before commiting the last
# pending modifications
shutil.copy(
os.path.join(old_folder, "database", "mia.db"),
os.path.join(
old_folder, "database", "mia_before_commit.db"
),
)
# We commit the last pending modifications
self.project.saveModifications()
os.mkdir(properties_path)
shutil.copy(
os.path.join(old_folder, "properties", "properties.yml"),
properties_path,
)
# We copy the Database with all the modifications commited in
# the new project
os.mkdir(database_path)
shutil.copy(
os.path.join(old_folder, "database", "mia.db"),
database_path,
)
reset_old_db = not self.project.isTempProject
# Removing the old project from the list of
# currently opened projects
config = Config()
opened_projects = config.get_opened_projects()
if self.project.folder in opened_projects:
opened_projects.remove(self.project.folder)
config.set_opened_projects(opened_projects)
config.saveConfig()
# We remove the useless files from the old project
self.remove_raw_files_useless()
if reset_old_db:
# We remove the Database with all the modifications saved
# in the old project
os.remove(os.path.join(old_folder, "database", "mia.db"))
# We reput the Database without the last modifications
# in the old project
shutil.copy(
os.path.join(
old_folder, "database", "mia_before_commit.db"
),
os.path.join(old_folder, "database", "mia.db"),
)
os.remove(
os.path.join(
old_folder, "database", "mia_before_commit.db"
)
)
# project updated everywhere
self.project = Project(as_folder_rel, False)
self.project.setName(os.path.basename(as_folder_rel))
self.project.setDate(
datetime.now().strftime("%d/%m/%Y %H:%M:%S")
)
self.project.saveModifications()
self.update_project(as_folder_rel, call_update_table=False)
# project updated everywhere
# If some files have been set in the pipeline editors,
# display a warning message
if (
self.pipeline_manager.pipelineEditorTabs
).has_pipeline_nodes():
msg = QMessageBox()
msg.setIcon(QMessageBox.Warning)
msg.setText(
"This action moves the current database. "
"All pipelines will need to be initialized "
"again before they can run."
)
msg.setWindowTitle("Warning")
msg.setStandardButtons(QMessageBox.Ok)
msg.buttonClicked.connect(msg.close)
msg.exec()
# Update of the history and the brick table in the newly
# created project
self.project.update_db_for_paths()
[docs]
def saveChoice(self):
"""Check if the project needs to be 'saved as' or just 'saved'."""
if self.project.isTempProject:
self.save_project_as()
else:
self.project.saveModifications()
[docs]
def see_all_projects(self):
"""Open a pop-up to show the recent projects."""
# Ui_Dialog() is defined in pop_ups.py
self.exPopup = PopUpSeeAllProjects(self.saved_projects, self)
if self.exPopup.exec():
file_path = self.exPopup.relative_path
if not self.test:
self.saved_projects_list = self.saved_projects.addSavedProject(
file_path
)
self.update_recent_projects_actions()
[docs]
def set_controller_version(self):
"""Reverses the value of the controller_version_changed attribute.
From False to True and vice versa
"""
self.controller_version_changed = not self.controller_version_changed
[docs]
def software_preferences_pop_up(self):
"""Open the Mia preferences pop-up."""
self.pop_up_preferences = PopUpPreferences(self)
self.pop_up_preferences.setGeometry(300, 200, 800, 600)
self.pop_up_preferences.show()
self.pop_up_preferences.use_clinical_mode_signal.connect(
self.add_clinical_tags
)
self.pop_up_preferences.not_use_clinical_mode_signal.connect(
self.del_clinical_tags
)
# Modifying the options in the Pipeline Manager
# (verify if user mode)
self.pop_up_preferences.signal_preferences_change.connect(
self.pipeline_manager.update_user_mode
)
# self.pop_up_preferences.signal_preferences_change.connect(
# self.update_package_library_action)
[docs]
def open_shell(self):
"""Open a Qt console shell with an IPython kernel seeing the program
internals
"""
from soma.qt_gui import qt_backend
ipfunc = None
mode = "qtconsole"
print("startShell")
try:
# to check it is installed
import jupyter_core.application # noqa: F401
import qtconsole # noqa: F401
ipfunc = (
"from jupyter_core import application; "
"app = application.JupyterApp(); app.initialize(); app.start()"
)
except ImportError:
print("failed to run Qt console")
return
if ipfunc:
import soma.subprocess
ipConsole = self.run_ipconsole_kernel(mode)
if ipConsole:
global _ipsubprocs
qt_api = qt_backend.get_qt_backend()
qt_apis = {
"PyQt4": "pyqt",
"PyQt5": "pyqt5",
"PySide": "pyside",
}
qt_api_code = qt_apis.get(qt_api, "pyq5t")
cmd = [
sys.executable,
"-c",
'import os; os.environ["QT_API"] = "%s"; %s'
% (qt_api_code, ipfunc),
mode,
"--existing",
"--shell=%d" % ipConsole.shell_port,
"--iopub=%d" % ipConsole.iopub_port,
"--stdin=%d" % ipConsole.stdin_port,
"--hb=%d" % ipConsole.hb_port,
]
sp = soma.subprocess.Popen(cmd)
pd = _ProcDeleter(sp)
with _ipsubprocs_lock:
_ipsubprocs.append(pd)
pd.start()
# hack the lastWindowClosed event because it becomes inactive
# otherwise
QApplication.instance().lastWindowClosed.connect(
self.last_window_closed
)
[docs]
@staticmethod
def run_ipconsole_kernel(mode="qtconsole"):
"""blabla"""
print("run_ipconsole_kernel:", mode)
import IPython # noqa: F401
from IPython.lib import guisupport
from soma.qt_gui.qt_backend import Qt
qtapp = Qt.QApplication.instance()
qtapp._in_event_loop = True
guisupport.in_event_loop = True
# ipversion = [int(x) for x in IPython.__version__.split(".")]
from ipykernel.kernelapp import IPKernelApp
app = IPKernelApp.instance()
if not app.initialized() or not app.kernel:
print("runing IP console kernel")
app.hb_port = 50042 # don't know why this is not set automatically
app.initialize(
[
mode,
"--gui=qt", # '--pylab=qt',
"--KernelApp.parent_appname='ipython-%s'" % mode,
]
)
# in ipython >= 1.2, app.start() blocks until a ctrl-c is issued in
# the terminal. Seems to block in tornado.ioloop.PollIOLoop.start()
#
# So, don't call app.start because it would begin a zmq/tornado
# loop instead we must just initialize its callback.
# if app.poller is not None:
# app.poller.start()
app.kernel.start()
# IP 2 allows just calling the current callbacks.
# For IP 1 it is not sufficient.
import tornado
from zmq.eventloop import ioloop
if tornado.version_info >= (4, 5):
# tornado 5 is using a decque for _callbacks, not a
# list + explicit locking
def my_start_ioloop_callbacks(self):
"""Blabla"""
if hasattr(self, "_callbacks"):
ncallbacks = len(self._callbacks)
for i in range(ncallbacks):
self._run_callback(self._callbacks.popleft())
else:
def my_start_ioloop_callbacks(self):
"""Blabla"""
with self._callback_lock:
callbacks = self._callbacks
self._callbacks = []
for callback in callbacks:
self._run_callback(callback)
my_start_ioloop_callbacks(ioloop.IOLoop.instance())
return app
[docs]
def switch_project(self, file_path, name):
"""Check if it's possible to open the selected project
and quit the current one.
:param file_path: raw file_path
:param name: project name
:return: Boolean
"""
# /!\ file_path and path are the same param
# Switching project only if it's a different one
if file_path != self.project.folder:
# If the file exists
if os.path.exists(os.path.join(file_path)):
# If it is a MIA project
if (
os.path.exists(
os.path.join(file_path, "properties", "properties.yml")
)
and os.path.exists(
os.path.join(file_path, "database", "mia.db")
)
and os.path.exists(
os.path.join(file_path, "data", "raw_data")
)
and os.path.exists(
os.path.join(file_path, "data", "derived_data")
)
and os.path.exists(
os.path.join(file_path, "data", "downloaded_data")
)
and os.path.exists(os.path.join(file_path, "filters"))
):
# We check if the name of the project directory is the
# same in its properties
with open(
os.path.join(
file_path, "properties", "properties.yml"
),
"r+",
) as stream:
if version.parse(yaml.__version__) > version.parse(
"5.1"
):
properties = yaml.load(
stream, Loader=yaml.FullLoader
)
else:
properties = yaml.load(stream)
path, name = os.path.split(file_path)
if properties["name"] != name:
properties["name"] = name
yaml.dump(
properties,
stream,
default_flow_style=False,
allow_unicode=True,
)
# We check for invalid scans in the project
try:
temp_database = Project(file_path, False)
except IOError:
msg = QMessageBox()
msg.setIcon(QMessageBox.Warning)
msg.setText("project already opened")
msg.setInformativeText(
"The project at "
+ str(file_path)
+ " is already opened in another "
"instance of the software."
)
msg.setWindowTitle("Warning")
msg.setStandardButtons(QMessageBox.Ok)
msg.buttonClicked.connect(msg.close)
msg.exec()
return False
# We check for valid version of the project
if not (temp_database.session.get_fields_names)(
COLLECTION_CURRENT
) or (
TAG_HISTORY
not in (temp_database.session.get_fields_names)(
COLLECTION_CURRENT
)
):
msg = QMessageBox()
msg.setIcon(QMessageBox.Warning)
msg.setText(
"The project cannot be read by Mia. Please check "
"if the version of the project is compatible with "
"the version of the running mia..."
)
msg.setWindowTitle("Warning")
msg.setStandardButtons(QMessageBox.Ok)
msg.buttonClicked.connect(msg.close)
msg.exec()
config = Config()
opened_projects = config.get_opened_projects()
if file_path in opened_projects:
opened_projects.remove(file_path)
config.set_opened_projects(opened_projects)
config.saveConfig()
return False
# project removed from the opened projects list
config = Config()
opened_projects = config.get_opened_projects()
if self.project.folder in opened_projects:
opened_projects.remove(self.project.folder)
config.set_opened_projects(opened_projects)
config.saveConfig()
# We remove the useless files from the old project
self.remove_raw_files_useless()
self.project = temp_database # New Database
self.update_project(file_path)
# project updated everywhere
return True
# Not a MIA project
else:
msg = QMessageBox()
msg.setIcon(QMessageBox.Warning)
msg.setText(
"The project selected isn't a valid MIA project"
)
msg.setInformativeText(
"The project selected " + name + " isn't a MIA project"
".\nPlease select a "
"valid one."
)
msg.setWindowTitle("Warning")
msg.setStandardButtons(QMessageBox.Ok)
msg.buttonClicked.connect(msg.close)
msg.exec()
return False
# The project doesn't exist anymore
else:
msg = QMessageBox()
msg.setIcon(QMessageBox.Warning)
msg.setText("The project selected doesn't exist anymore")
msg.setInformativeText(
"The project selected " + name + " doesn't exist anymore."
"\nPlease select "
"another one."
)
msg.setWindowTitle("Warning")
msg.setStandardButtons(QMessageBox.Ok)
msg.buttonClicked.connect(msg.close)
msg.exec()
return False
[docs]
def tab_changed(self):
"""Update the window when the tab is changed."""
if (
self.tabs.tabText(self.tabs.currentIndex()).replace("&", "", 1)
== "Data Browser"
):
# data_browser refreshed after working with pipelines
old_scans = self.data_browser.table_data.scans_to_visualize
documents = self.project.session.get_documents_names(
COLLECTION_CURRENT
)
self.data_browser.table_data.add_columns()
self.data_browser.table_data.fill_headers()
self.data_browser.table_data.add_rows(documents)
self.data_browser.table_data.scans_to_visualize = documents
self.data_browser.table_data.scans_to_search = documents
self.data_browser.table_data.itemChanged.disconnect()
self.data_browser.table_data.fill_cells_update_table()
self.data_browser.table_data.itemChanged.connect(
self.data_browser.table_data.change_cell_color
)
self.data_browser.table_data.update_visualized_rows(old_scans)
# Advanced search + search_bar opened
old_search = self.project.currentFilter.search_bar
self.data_browser.reset_search_bar()
self.data_browser.search_bar.setText(old_search)
if len(self.project.currentFilter.nots) > 0:
self.data_browser.frame_advanced_search.setHidden(False)
self.data_browser.advanced_search.scans_list = (
self.data_browser.table_data.scans_to_visualize
)
self.data_browser.advanced_search.show_search()
self.data_browser.advanced_search.apply_filter(
self.project.currentFilter
)
elif (
self.tabs.tabText(self.tabs.currentIndex()).replace("&", "", 1)
== "Data Viewer"
):
self.data_viewer.load_viewer(self.data_viewer.current_viewer())
documents = self.project.session.get_documents_names(
COLLECTION_CURRENT
)
self.data_viewer.set_documents(self.project, documents)
elif (
self.tabs.tabText(self.tabs.currentIndex()).replace("&", "", 1)
== "Pipeline Manager"
):
if self.data_browser.data_sent is False:
scans = self.project.session.get_documents_names(
COLLECTION_CURRENT
)
self.pipeline_manager.scan_list = scans
self.pipeline_manager.nodeController.scan_list = scans
self.pipeline_manager.pipelineEditorTabs.scan_list = scans
self.pipeline_manager.pipelineEditorTabs.update_scans_list()
self.pipeline_manager.update_user_buttons_states()
# fmt: off
if (
self.pipeline_manager.pipelineEditorTabs.
get_current_editor().iterated_tag
):
self.pipeline_manager.iterationTable.update_iterated_tag(
self.pipeline_manager.pipelineEditorTabs.
get_current_editor().iterated_tag
)
# fmt: on
# Pipeline Manager
# The pending modifications must be saved before
# working with pipelines (auto_commit)
if self.project.hasUnsavedModifications():
msg = QMessageBox()
msg.setIcon(QMessageBox.Warning)
msg.setText("Unsaved modifications in the Data Browser !")
msg.setInformativeText(
"There are unsaved modifications in the database, "
"you need to save or remove them before working "
"with pipelines."
)
msg.setWindowTitle("Warning")
save_button = QPushButton("Save")
save_button.clicked.connect(self.project.saveModifications)
unsave_button = QPushButton("Not Save")
unsave_button.clicked.connect(self.project.unsaveModifications)
msg.addButton(save_button, QMessageBox.AcceptRole)
msg.addButton(unsave_button, QMessageBox.AcceptRole)
msg.exec()
[docs]
def undo(self):
"""Undo the last action made by the user."""
if (
self.tabs.tabText(self.tabs.currentIndex()).replace("&", "", 1)
== "Data Browser"
):
# In Data Browser
self.project.undo(self.data_browser.table_data)
# Action reverted in the Database
elif (
self.tabs.tabText(self.tabs.currentIndex()).replace("&", "", 1)
== "Pipeline Manager"
):
# In Pipeline Manager
self.pipeline_manager.undo()
# def update_package_library_action(self):
# """Update the package library action depending on the mode."""
# if Config().get_user_mode() == True:
# self.action_package_library.setDisabled(True)
# # self.action_install_processes.setDisabled(True)
# else:
# self.action_package_library.setEnabled(True)
# # self.action_install_processes.setEnabled(True)
[docs]
def update_project(self, file_path, call_update_table=True):
"""Update the project once the database has been updated.
Update the database, the window title and the recent and saved
projects menus.
:param file_path: File name of the new project
:param call_update_table: boolean, True if we need to call
"""
self.data_browser.update_database(self.project)
# Database update data_browser
self.pipeline_manager.update_project(self.project)
if call_update_table:
self.data_browser.table_data.update_table() # Table updated
# Window name updated
if self.project.isTempProject:
self.projectName = "Unnamed project"
else:
self.projectName = self.project.getName()
self.setWindowTitle(self.windowName + self.projectName)
# List of project updated
if not self.test and not self.project.isTempProject:
self.saved_projects_list = self.saved_projects.addSavedProject(
file_path
)
self.update_recent_projects_actions()
[docs]
def update_recent_projects_actions(self):
"""Update the list of recent projects."""
for j in range(0, self.config.get_max_projects()):
self.saved_projects_actions[j].setVisible(False)
if self.saved_projects_list:
if len(self.saved_projects_list) > 0:
for i in range(
min(
len(self.saved_projects_list),
self.config.get_max_projects(),
)
):
text = os.path.basename(self.saved_projects_list[i])
self.saved_projects_actions[i].setText(text)
self.saved_projects_actions[i].setData(
self.saved_projects_list[i]
)
self.saved_projects_actions[i].setVisible(True)