# -*- coding: utf-8 -*-
# This software and supporting documentation are distributed by
# Institut Federatif de Recherche 49
# CEA/NeuroSpin, Batiment 145,
# 91191 Gif-sur-Yvette cedex
# France
#
# This software is governed by the CeCILL license version 2 under
# French law and abiding by the rules of distribution of free software.
# You can use, modify and/or redistribute the software under the
# terms of the CeCILL license version 2 as circulated by CEA, CNRS
# and INRIA at the following URL "http://www.cecill.info".
#
# As a counterpart to the access to the source code and rights to copy,
# modify and redistribute granted by the license, users are provided only
# with a limited warranty and the software's author, the holder of the
# economic rights, and the successive licensors have only limited
# liability.
#
# In this respect, the user's attention is drawn to the risks associated
# with loading, using, modifying and/or developing or reproducing the
# software by the user in light of its specific status of free software,
# that may mean that it is complicated to manipulate, and that also
# therefore means that it is reserved for developers and experienced
# professionals having in-depth computer knowledge. Users are therefore
# encouraged to load and test the software's suitability as regards their
# requirements in conditions enabling the security of their systems and/or
# data to be ensured and, more generally, to use and operate it in the
# same conditions as regards security.
#
# The fact that you are presently reading this means that you have had
# knowledge of the CeCILL license version 2 and that you accept its terms.
from datetime import date
from datetime import datetime
from datetime import timedelta
import StringIO
import distutils, os, sys, re
import types
from brainvisa.processing.qtgui.backwardCompatibleQt import *
from soma.qt4gui.designer import loadUi, loadUiType
from PyQt4.QtGui import QKeySequence
from PyQt4 import QtCore
from PyQt4 import QtGui
from PyQt4 import QtWebKit
from brainvisa.configuration import neuroConfig
from brainvisa.configuration.qt4gui import neuroConfigGUI
from brainvisa.processing.qt4gui import neuroLogGUI
from brainvisa.data import neuroData
from brainvisa.history import ProcessExecutionEvent
import brainvisa.processes
from brainvisa.data.neuroDiskItems import DiskItem
from brainvisa.data.readdiskitem import ReadDiskItem
from brainvisa.data.writediskitem import WriteDiskItem
from brainvisa.data.qt4gui import lockFilesGUI
import weakref
from soma.minf.xhtml import XHTML
from soma.qtgui.api import QtThreadCall, FakeQtThreadCall, WebBrowserWithSearch, bigIconSize, defaultIconSize
from soma.html import htmlEscape
from soma.wip.application.api import Application
import soma.functiontools
import threading
import socket
try:
import sip
except:
# for sip 3.x (does it work ??)
import libsip as sip
from brainvisa.processing import neuroException
from soma.qtgui.api import EditableTreeWidget, TreeListWidget
from soma.notification import ObservableList, EditableTree
from soma.signature.api import HasSignature
from soma.signature.api import Signature as SomaSignature
from soma.signature.api import FileName as SomaFileName
from soma.signature.api import Choice as SomaChoice
from soma.signature.api import Boolean as SomaBoolean
from soma.qt4gui.api import ApplicationQt4GUI
from brainvisa.data.databaseCheck import BVChecker_3_1
from brainvisa.data import neuroHierarchy
from brainvisa.tools import checkbrainvisaupdates
import urllib
try:
from soma_workflow.gui.workflowGui import SomaWorkflowWidget as ComputingResourceWidget
from soma_workflow.gui.workflowGui import SomaWorkflowMiniWidget as MiniComputingResourceWidget
import soma_workflow.gui.workflowGui
from soma_workflow.gui.workflowGui import ComputingResourcePool
from soma_workflow.gui.workflowGui import ApplicationModel as WorkflowApplicationModel
import soma_workflow.configuration
except ImportError:
_soma_workflow = False
class ComputingResourceWidget(object): pass
class MiniComputingResourceWidget(object): pass
class ComputingResourcePool(object): pass
class WorkflowApplicationModel(object): pass
else:
_soma_workflow = True
_mainThreadActions = FakeQtThreadCall()
#----------------------------------------------------------------------------
def restartAnatomist():
from brainvisa import anatomist
a = anatomist.Anatomist( create=False )
if hasattr( a, '_restartshell_launched' ):
a.launched = True
del a._restartshell_launched
def startShell():
from PyQt4.QtGui import qApp
try:
import IPython
if [ int(x) for x in IPython.__version__.split('.')[:2] ] >= [ 0, 11 ]:
# ipython >= 0.11, use client/server mode
ipConsole = brainvisa.processes.runIPConsoleKernel()
import subprocess
sp = subprocess.Popen( [ sys.executable, '-c',
'from IPython.frontend.terminal.ipapp import launch_new_instance; ' \
'launch_new_instance()', 'qtconsole', '--existing',
'--shell=%d' % ipConsole.shell_port, '--iopub=%d' % ipConsole.iopub_port,
'--stdin=%d' % ipConsole.stdin_port, '--hb=%d' % ipConsole.hb_port ] )
brainvisa.processes._ipsubprocs.append( sp )
return
except:
pass
neuroConfig.shell = True
try:
if neuroConfig.anatomistImplementation == 'socket':
from brainvisa import anatomist
a = anatomist.Anatomist( create=False )
if a and a.launched:
a.launched = False
a._restartshell_launched = True
except Exception, e:
print e
mainThreadActions().push( qApp.exit )
#----------------------------------------------------------------------------
def quitRequest():
# Called when quitting brainvisa using quit menu or closing the main window
# print '!!!!!!!!!quitRequest!!!!!!!!'
a = QMessageBox.warning( None, _t_('Quit'),_t_( 'Do you really want to quit BrainVISA ?' ), QMessageBox.Yes | QMessageBox.Default, QMessageBox.No )
if a == QMessageBox.Yes:
wids = qApp.topLevelWidgets()
for w in wids:
if isinstance( w, ProcessView ) or isinstance(w, SomaWorkflowProcessView):
w.close()
del w
from brainvisa import anatomist
a = anatomist.Anatomist( create=False )
if a:
close_viewers()
a.close()
if neuroConfig.shell:
sys.exit()
else:
qApp.exit()
#----------------------------------------------------------------------------
def cleanupGui():
# called when quitting a ipython shell
wids = qApp.topLevelWidgets()
for w in wids:
if isinstance( w, ProcessView ):
w.close()
del w
wids = qApp.topLevelWidgets()
for w in wids:
w.close()
del w
#----------------------------------------------------------------------------
_helpWidget = None
def helpRequest():
url = QUrl.fromLocalFile( neuroConfig.getDocFile(os.path.join( 'help', 'index.html' ) ) ).toString()
openWeb(url)
def runHtmlBrowser( source, existingWidget=None ):
''' run the HTML browser defined in BV config. If it is a builtin browser,
use an existing widget, or instantiate and return a new one.
'''
try:
browser = neuroConfig.HTMLBrowser
if browser is not None:
browser = distutils.spawn.find_executable( browser )
if browser:
if sys.platform == "darwin":
m=re.match("\/Applications\/.+\.app/Contents/MacOS/(.*)", browser)
if m:
if os.system("open -a "+m.group(1)+" '"+source+"'") == 0:
return
env=os.environ.copy()
if (not browser.startswith(os.path.dirname(neuroConfig.mainPath))): # external command
if neuroConfig.brainvisaSysEnv:
env.update(neuroConfig.brainvisaSysEnv.getVariables())
if os.spawnle( os.P_NOWAIT, browser, browser, source, env ) > 0:
return
except:
pass
if existingWidget is None:
existingWidget = HTMLBrowser( None )
existingWidget.setWindowTitle( _t_( 'BrainVISA help' ) )
existingWidget.resize( 800, 600 )
sys.stdout.flush()
existingWidget.setSource( source )
existingWidget.show()
existingWidget.raise_()
return existingWidget
def openWeb( source ):
global _helpWidget
widget = runHtmlBrowser( source, _helpWidget )
if widget is not None:
_helpWidget = widget
#----------------------------------------------------------------------------
def runCsvViewer( source, existingWidget=None ):
''' run the CSV viewer defined in BV config. If it is a builtin viewer,
use an existing widget, or instantiate and return a new one.
'''
try:
configuration = Application().configuration
browser = configuration.brainvisa.csvViewer
if browser is not None:
browser = distutils.spawn.find_executable( browser )
if browser:
if sys.platform == "darwin":
m=re.match("\/Applications\/.+\.app/Contents/MacOS/(.*)", browser)
if m:
if os.system("open -a "+m.group(1)+" '"+source+"'") == 0:
return
env=os.environ.copy()
if (not browser.startswith(os.path.dirname(neuroConfig.mainPath))): # external command
if neuroConfig.brainvisaSysEnv:
env.update(neuroConfig.brainvisaSysEnv.getVariables())
if os.spawnle( os.P_NOWAIT, browser, browser, source, env ) > 0:
return
except Exception, e:
print 'exception.'
print e
pass
# builtin browser, needs GenericTableEditor from datamind
try:
from datamind.gui.genericTableEditor import GenericTableEditor
if existingWidget is None:
existingWidget = GenericTableEditor( None )
existingWidget.setWindowTitle( _t_( 'CSV viewer' ) )
existingWidget.resize( 800, 600 )
existingWidget.load_from_file( source )
existingWidget.show()
existingWidget.raise_()
return existingWidget
except:
pass
# fallback to text editor
textEditor = configuration.brainvisa.textEditor
if textEditor is not None:
env=os.environ.copy()
if (not textEditor.startswith(os.path.dirname(neuroConfig.mainPath))): # external command
if neuroConfig.brainvisaSysEnv:
env.update(neuroConfig.brainvisaSysEnv.getVariables())
if os.spawnle( os.P_NOWAIT, textEditor, textEditor, source, env ) > 0:
return
#----------------------------------------------------------------------------
class SomaWorkflowMiniWidget(MiniComputingResourceWidget):
def __init__(self, model, sw_widget, parent=None):
super(SomaWorkflowMiniWidget, self).__init__(model,
sw_widget,
parent)
sw_widget.update_workflow_list_from_model = True
sw_widget.hide()
class SomaWorkflowWidget(ComputingResourceWidget):
# dict wf_id -> serialized_process
serialized_processes = None
brainvisa_code = "brainvisa_"
def __init__(self, model, computing_resource=None, parent=None):
super(SomaWorkflowWidget, self).__init__(model,
None,
False,
computing_resource,
parent,
0)
self.ui.list_widget_submitted_wfs.itemDoubleClicked.connect(self.workflow_double_clicked)
self.ui.resource_selection_frame.hide()
def workflow_filter(self, workflows):
new_workflows = {}
self.serialized_processes = {}
for wf_id, (name, date) in workflows.iteritems():
if name != None and \
len(name) > len(SomaWorkflowWidget.brainvisa_code)-1 and \
name[0:len(SomaWorkflowWidget.brainvisa_code)] == SomaWorkflowWidget.brainvisa_code:
new_workflows[wf_id] = (name[len(SomaWorkflowWidget.brainvisa_code):], date)
return new_workflows
@QtCore.Slot()
def workflow_double_clicked(self):
selected_items = self.ui.list_widget_submitted_wfs.selectedItems()
wf_id = int( selected_items[0].data(QtCore.Qt.UserRole) )
if wf_id not in self.serialized_processes:
workflow = self.model.current_workflow()#current_connection.workflow(wf_id)
if workflow == None:
QMessageBox.warning(self, "Workflow loading impossible", "The workflow does not exist.")
return
workflow = workflow.server_workflow
if workflow.user_storage != None and \
len(workflow.user_storage) == 2 and \
workflow.user_storage[0] == SomaWorkflowWidget.brainvisa_code:
self.serialized_processes[wf_id] = workflow.user_storage[1]
else:
QMessageBox.warning(self, "Workflow loading impossible", "The workflow was not created from a BrainVISA pipeline.")
return
serialized_process = self.serialized_processes[wf_id]
serialized_process = StringIO.StringIO(serialized_process)
try:
QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor))
view = SomaWorkflowProcessView(self.model,
wf_id,
self.model.current_resource_id,
serialized_process=serialized_process,
parent=_mainWindow)
view.setAttribute( QtCore.Qt.WA_DeleteOnClose )
QtGui.QApplication.restoreOverrideCursor()
except Exception, e:
QtGui.QApplication.restoreOverrideCursor()
raise e
view.show()
class WorkflowSubmissionDlg(QDialog):
def __init__(self, parent=None):
super(WorkflowSubmissionDlg, self).__init__(parent)
from brainvisa.workflow import ProcessToSomaWorkflow
loadUi(os.path.join(os.path.dirname(__file__), 'sw_submission_dlg.ui' ),
self)
#self.setupUi(self)
resource_list = _computing_resource_pool.resource_ids()
self.combo_resource.addItems(resource_list)
current_resource_index = resource_list.index(_workflow_application_model.current_resource_id)
self.combo_resource.setCurrentIndex(current_resource_index)
self.resource_changed(current_resource_index)
kind_of_file_processing = [ProcessToSomaWorkflow.NO_FILE_PROCESSING,
ProcessToSomaWorkflow.FILE_TRANSFER,
ProcessToSomaWorkflow.SHARED_RESOURCE_PATH]
self.combo_out_files.addItems(kind_of_file_processing)
kind_of_file_processing.append(ProcessToSomaWorkflow.BV_DB_SHARED_PATH)
self.combo_in_files.addItems(kind_of_file_processing)
self.lineedit_wf_name.setText("")
self.dateTimeEdit_expiration.setDateTime(datetime.now() + timedelta(days=5))
self.combo_resource.currentIndexChanged.connect(self.resource_changed)
@QtCore.Slot(int)
def resource_changed(self, resource_index):
resource_id = self.combo_resource.currentText()
queues = ["default queue"]
queues.extend(_computing_resource_pool.connection(resource_id).config.get_queues())
self.combo_queue.clear()
self.combo_queue.addItems(queues)
class SomaWorkflowProcessView(QMainWindow):
workflow_id = None
resource_id = None
model = None
process = None
serialized_process = None
ui = None
process_view = None
workflow_tree_view = None
workflow_item_view = None
workflow_plot_view = None
action_monitor_workflow = None
workflow_menu = None
workflow_tool_bar = None
def __init__(self,
model,
workflow_id,
resource_id,
process=None,
serialized_process=None,
parent=None):
super(SomaWorkflowProcessView, self).__init__(parent)
Ui_SWProcessView = loadUiType(os.path.join(os.path.dirname( __file__ ),
'sw_process_view.ui' ))[0]
self.ui = Ui_SWProcessView()
self.ui.setupUi(self)
self.model = model
self.serialized_process = serialized_process
self.process = process
self.workflow_id = workflow_id
self.resource_id = resource_id
self.connect(self.model, QtCore.SIGNAL('current_workflow_changed()'), self.current_workflow_changed)
self.setCorner(QtCore.Qt.TopLeftCorner, QtCore.Qt.LeftDockWidgetArea)
self.setCorner(QtCore.Qt.TopRightCorner, QtCore.Qt.RightDockWidgetArea)
self.setCorner(QtCore.Qt.BottomLeftCorner, QtCore.Qt.LeftDockWidgetArea)
self.setCorner(QtCore.Qt.BottomRightCorner, QtCore.Qt.RightDockWidgetArea)
self.action_monitor_workflow = QAction(_t_('Monitor execution'), self)
self.action_monitor_workflow.setCheckable(True)
self.action_monitor_workflow.setIcon(QIcon(os.path.join(os.path.dirname(soma_workflow.gui.__file__),"icon/monitor_wf.png")))
self.action_monitor_workflow.toggled.connect(self.enable_workflow_monitoring)
self.action_monitor_workflow.setChecked(True)
self.workflow_tree_view = soma_workflow.gui.workflowGui.WorkflowTree(
_workflow_application_model,
assigned_wf_id=self.workflow_id,
assigned_resource_id=self.resource_id,
parent=self)
self.workflow_item_view = soma_workflow.gui.workflowGui.WorkflowElementInfo(
model=_workflow_application_model,
proxy_model=self.workflow_tree_view.proxy_model,
parent=self)
self.workflow_plot_view = soma_workflow.gui.workflowGui.WorkflowPlot(
_workflow_application_model,
assigned_wf_id=self.workflow_id,
assigned_resource_id=self.resource_id,
parent=self)
if not soma_workflow.gui.workflowGui.MATPLOTLIB:
self.ui.dock_plot.hide()
self.ui.dock_plot.toggleViewAction().setVisible(False)
self.workflow_info_view = soma_workflow.gui.workflowGui.WorkflowInfoWidget(
_workflow_application_model,
assigned_wf_id=self.workflow_id,
assigned_resource_id=self.resource_id,
parent=self)
self.workflow_menu = self.ui.menubar.addMenu("&Workflow")
self.workflow_menu.addAction(_mainWindow.sw_widget.ui.action_stop_wf)
self.workflow_menu.addAction(_mainWindow.sw_widget.ui.action_restart)
_addSeparator( self.workflow_menu )
self.workflow_menu.addAction(_mainWindow.sw_widget.ui.action_transfer_infiles)
self.workflow_menu.addAction(_mainWindow.sw_widget.ui.action_transfer_outfiles)
_addSeparator( self.workflow_menu )
self.workflow_menu.addAction(_mainWindow.sw_widget.ui.action_delete_workflow)
self.workflow_menu.addAction(_mainWindow.sw_widget.ui.action_change_expiration_date)
view_menu = self.ui.menubar.addMenu("&View")
view_menu.addAction(self.ui.dock_bv_process.toggleViewAction())
view_menu.addAction(self.ui.dock_plot.toggleViewAction())
view_menu.addAction(close_viewers_action(self))
self.action_update_databases=QAction(self)
self.action_update_databases.setText(_t_( 'Check && update databases' ))
self.action_update_databases.triggered.connect(self.update_databases)
self.process_menu = self.ui.menubar.addMenu("&Process")
self.process_menu.addAction(self.action_update_databases)
self.workflow_tool_bar = QToolBar(self)
self.workflow_tool_bar.addWidget(self.workflow_info_view.ui.wf_status_icon)
_addSeparator( self.workflow_tool_bar )
self.workflow_tool_bar.addAction(_mainWindow.sw_widget.ui.action_stop_wf)
self.workflow_tool_bar.addAction(_mainWindow.sw_widget.ui.action_restart)
_addSeparator( self.workflow_tool_bar )
self.workflow_tool_bar.addAction(_mainWindow.sw_widget.ui.action_transfer_infiles)
self.workflow_tool_bar.addAction(_mainWindow.sw_widget.ui.action_transfer_outfiles)
self.ui.tool_bar.addWidget(self.workflow_tool_bar)
_addSeparator( self.ui.tool_bar )
self.ui.tool_bar.addAction(self.ui.dock_bv_process.toggleViewAction())
_addSeparator( self.ui.tool_bar )
self.ui.tool_bar.addAction(self.action_monitor_workflow)
tree_widget_layout = QtGui.QVBoxLayout()
tree_widget_layout.setContentsMargins(2,2,2,2)
tree_widget_layout.addWidget(self.workflow_tree_view)
self.ui.centralwidget.setLayout(tree_widget_layout)
self.process_layout = QtGui.QVBoxLayout()
self.process_layout.setContentsMargins(2,2,2,2)
self.ui.dock_bv_process_contents.setLayout(self.process_layout)
item_info_layout = QtGui.QVBoxLayout()
item_info_layout.setContentsMargins(2,2,2,2)
item_info_layout.addWidget(self.workflow_item_view)
self.ui.dock_item_info_contents.setLayout(item_info_layout)
wf_info_layout = QtGui.QVBoxLayout()
wf_info_layout.setContentsMargins(2,2,2,2)
wf_info_layout.addWidget(self.workflow_info_view)
self.ui.dock_workflow_info_contents.setLayout(wf_info_layout)
plot_layout = QtGui.QVBoxLayout()
plot_layout.setContentsMargins(2,2,2,2)
plot_layout.addWidget(self.workflow_plot_view)
self.ui.dock_plot_contents.setLayout(plot_layout)
self.connect(self.workflow_tree_view, QtCore.SIGNAL('selection_model_changed(QItemSelectionModel)'), self.workflow_item_view.setSelectionModel)
self.connect(self.workflow_item_view, QtCore.SIGNAL('connection_closed_error'), _mainWindow.sw_widget.reconnectAfterConnectionClosed)
self.workflow_tree_view.current_workflow_changed()
self.workflow_plot_view.current_workflow_changed()
self.workflow_info_view.current_workflow_changed()
self.ui.dock_bv_process.toggleViewAction().toggled.connect(self.show_process)
self.ui.dock_bv_process.toggleViewAction().setIcon(QIcon( os.path.join( neuroConfig.iconPath, 'icon.png')))
self.ui.dock_plot.close()
self.ui.dock_bv_process.close()
self.ui.dock_workflow_info.close()
wf_name = self.workflow_info_view.ui.wf_name.text()
if len(wf_name[len(SomaWorkflowWidget.brainvisa_code):]) == 0:
title = repr(self.workflow_id) + "@" + self.resource_id
else:
title = wf_name[len(SomaWorkflowWidget.brainvisa_code):] + "@" + self.resource_id
self.setWindowTitle(title)
# warning message in the status bar about the need to check and update databases after execution
warningMsg=QLabel("
Warning: After execution with Soma-Workflow, the databases may need to be updated.
Use \"Process -> Check & update databases\" menu to do it.
")
warningMsg.setWordWrap(True)
self.ui.statusbar.addPermanentWidget(warningMsg, 1)
def closeEvent( self, event ):
if self.process_view:
self.process_view.cleanup()
QMainWindow.closeEvent( self, event )
@QtCore.Slot(bool)
def enable_workflow_monitoring(self, enable):
if not enable:
if self.model.current_wf_id == self.workflow_id and \
self.model.current_resource_id == self.resource_id:
self.model.clear_current_workflow()
else:
if self.resource_id != self.model.current_resource_id:
if self.model.resource_pool.resource_exist(self.resource_id):
self.model.set_current_connection(self.resource_id)
else:
(resource_id,
new_connection) = _mainWindow.sw_widget.createConnection(self.resource_id,
editable_resource=False)
if new_connection:
self.model.add_connection(resource_id, new_connection)
else:
QMessageBox.warning(self, "Monitoring impossible", "The connection is not active.")
self.action_monitor_workflow.setChecked(False)
return
if self.model.is_loaded_workflow(self.workflow_id):
self.model.set_current_workflow(self.workflow_id)
else:
QMessageBox.warning(self, "Monitoring impossible", "The workflow was deleted.")
self.action_monitor_workflow.setChecked(False)
@QtCore.Slot(bool)
def show_process(self, checked):
if self.process == None and self.serialized_process == None:
return
if checked and self.process_view == None:
if self.process == None:
#print "before unserialize"
QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor))
#self.ui.statusbar.showMessage("Unserialize...")
try:
self.process = brainvisa.processes.getProcessInstance(self.serialized_process)
QtGui.QApplication.restoreOverrideCursor()
except Exception, e:
#self.ui.statusbar.clearMessage()
QtGui.QApplication.restoreOverrideCursor()
raise e
else:
#self.ui.statusbar.clearMessage()
QtGui.QApplication.restoreOverrideCursor()
#print "after unserialize"
#print "before process view creation"
QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor))
#self.ui.statusbar.showMessage("Building the process view...")
try:
self.process_view = ProcessView(self.process, parent=self, read_only=True)
QtGui.QApplication.restoreOverrideCursor()
except Exception, e:
#self.ui.statusbar.clearMessage()
QtGui.QApplication.restoreOverrideCursor()
raise e
else:
#self.ui.statusbar.clearMessage()
QtGui.QApplication.restoreOverrideCursor()
self.process_view.inlineGUI.hide()
self.process_view.info.hide()
self.process_view.eTreeWidget.setOrientation(Qt.Vertical)
self.process_layout.addWidget(self.process_view)
#print "After process view creation"
self.ui.dock_bv_process.toggleViewAction().toggled.disconnect(self.show_process)
process_button_layout = QtGui.QHBoxLayout()
process_button_layout.setContentsMargins(2,2,2,2)
self.process_layout.addLayout(process_button_layout)
self.process_view.action_clone_process.setText("Edit...")
self.process_view.action_iterate.setText("Iterate...")
btn_clone = QToolButton(self)
btn_clone.setDefaultAction(self.process_view.action_clone_process)
btn_clone.setMinimumWidth(90)
btn_clone.setSizePolicy( QSizePolicy( QSizePolicy.Fixed, QSizePolicy.Fixed ) )
process_button_layout.addWidget(btn_clone)
btn_iterate = QToolButton(self)
btn_iterate.setDefaultAction(self.process_view.action_iterate)
btn_iterate.setMinimumWidth(90)
btn_iterate.setSizePolicy( QSizePolicy( QSizePolicy.Fixed, QSizePolicy.Fixed ) )
process_button_layout.addWidget(btn_iterate)
btn_save = QToolButton(self)
btn_save.setDefaultAction(self.process_view.action_save_process)
btn_save.setMinimumWidth(90)
btn_save.setSizePolicy( QSizePolicy( QSizePolicy.Fixed, QSizePolicy.Fixed ) )
process_button_layout.addWidget(btn_save)
self.process_menu.addAction(self.process_view.action_save_process)
self.process_menu.addAction(self.process_view.action_clone_process)
self.process_menu.addAction(self.process_view.action_iterate)
@QtCore.Slot()
def update_databases(self):
QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor))
for dbSettings in neuroConfig.dataPath:
try:
if not dbSettings.builtin:
db=neuroHierarchy.databases.database(dbSettings.directory)
brainvisa.processes.defaultContext().write("Updating database "+db.name+"...")
# first update before checking for missing referentials
db.clear(context=brainvisa.processes.defaultContext())
db.update(context=brainvisa.processes.defaultContext())
# check referentials and transformations information, the database will be updated after
brainvisa.processes.defaultContext().write("Checking referentials information in database "+db.name+"...")
checker=BVChecker_3_1(db, brainvisa.processes.defaultContext())
checker.findActions()
checker.process()
except:
neuroException.showException(beforeError="Error while updating database "+dbSettings.directory)
QtGui.QApplication.restoreOverrideCursor()
@QtCore.Slot()
def current_workflow_changed(self):
if self.model.current_wf_id == self.workflow_id and \
self.model.current_resource_id == self.resource_id:
self.action_monitor_workflow.setChecked(True)
self.workflow_tool_bar.setEnabled(True)
self.workflow_menu.setEnabled(True)
else:
self.action_monitor_workflow.setChecked(False)
self.workflow_tool_bar.setEnabled(False)
self.workflow_menu.setEnabled(False)
_aboutWidget = None
#----------------------------------------------------------------------------
class AboutWidget( QWidget ):
def __init__( self, parent=None, name=None ):
QWidget.__init__( self, parent )
if name :
self.setObjectName( name )
layout=QVBoxLayout()
self.setLayout(layout)
self.setBackgroundRole( QPalette.Base )
self.setAutoFillBackground(True)
hb = QHBoxLayout( )
layout.addLayout(hb)
layout.setContentsMargins( 10, 10, 10, 10 )
self.setWindowTitle( _t_( 'About') )
if getattr( AboutWidget, 'pixIcon', None ) is None:
setattr( AboutWidget, 'pixIcon', QIcon( os.path.join( neuroConfig.iconPath, 'icon.png' ) ) )
self.setWindowIcon( self.pixIcon )
def buildImageWidget( parent, fileName, desiredHeight=0 ):
widget = QLabel( parent )
#widget.setBackgroundRole( QPalette.Base )
widget.setAlignment( Qt.AlignCenter )
pixmap = QPixmap( os.path.join( neuroConfig.iconPath, fileName ) )
if desiredHeight:
stretch = float( desiredHeight ) / pixmap.height()
matrix = QMatrix()
matrix.scale( stretch, stretch )
pixmap = pixmap.transformed( matrix )
widget.setPixmap( pixmap )
return widget
widget = buildImageWidget( None, 'brainvisa.png' )
hb.addWidget(widget)
label = QLabel( neuroConfig.versionText() )
hb.addWidget(label)
#label.setBackgroundRole( QPalette.Base )
label.setContentsMargins( 4, 4, 4, 4 )
font = QFont()
font.setPointSize( 30 )
label.setFont( font )
vb = QVBoxLayout( )
hb.addLayout(vb)
widget = buildImageWidget( None, 'ifr49.png', desiredHeight=60 )
vb.addWidget(widget)
widget.setContentsMargins( 5, 5, 5, 5 )
widget = buildImageWidget( None, 'neurospin.png', desiredHeight=40 )
vb.addWidget(widget)
widget.setContentsMargins( 5, 5, 5, 5 )
widget = buildImageWidget( None, 'shfj.png', desiredHeight=40 )
vb.addWidget(widget)
widget.setContentsMargins( 5, 5, 5, 5 )
widget = buildImageWidget( None, 'mircen.png', desiredHeight=40 )
vb.addWidget(widget)
widget.setContentsMargins( 5, 5, 5, 5 )
widget = buildImageWidget( None, 'inserm.png', desiredHeight=40 )
vb.addWidget(widget)
widget.setContentsMargins( 5, 5, 5, 5 )
widget = buildImageWidget( None, 'cnrs.png', desiredHeight=60 )
vb.addWidget(widget)
widget.setContentsMargins( 5, 5, 5, 5 )
widget = buildImageWidget( None, 'chups.png', desiredHeight=40 )
vb.addWidget(widget)
widget.setContentsMargins( 5, 5, 5, 5 )
widget = buildImageWidget( None, 'parietal.png', desiredHeight=40 )
vb.addWidget(widget)
widget.setContentsMargins( 5, 5, 5, 5 )
if parent is None:
px = ( neuroConfig.qtApplication.desktop().width() - self.sizeHint().width() ) / 2
py = ( neuroConfig.qtApplication.desktop().height() - self.sizeHint().height() ) / 2
self.setGeometry( px, py, self.sizeHint().width(), self.sizeHint().height() )
self.btnClose = QPushButton( _t_( 'Close' ) )
layout.addWidget(self.btnClose)
self.btnClose.setSizePolicy( QSizePolicy( QSizePolicy.Fixed, QSizePolicy.Fixed ) )
self.btnClose.setFocus()
QObject.connect( self.btnClose, SIGNAL( 'clicked()' ), self, SLOT( 'close()' ) )
def aboutRequest():
global _aboutWidget
if _aboutWidget is None:
_aboutWidget = AboutWidget()
_aboutWidget.show()
_aboutWidget.raise_()
#----------------------------------------------------------------------------
def logRequest():
neuroLogGUI.LogViewer( neuroConfig.logFileName ).show()
#----------------------------------------------------------------------------
def _addAction( parent, text=None, callback=None, shortcut=None ):
# this 'strange' function is only here to avoid a memory leak in PyQt:
# it apparently does the same as QMenu.addAction() / addSeparator(),
# but if the former are called, the returned QAction is created in the
# C++ layer, and has a strange ownership side effect: the python part
# of the QAction is never destroyed.
# Creating the QAction from Python side seems to work around the problem.
# Seen in PyQt 4.9.3
if text is None:
ac = QAction( parent )
ac.setSeparator( True )
else:
ac = QAction( text, parent )
if callback is not None:
ac.triggered.connect( callback )
if shortcut is not None:
ac.setShortcut( shortcut )
parent.addAction( ac )
return ac
def _addSeparator( menu ):
return _addAction( menu )
#----------------------------------------------------------------------------
def addBrainVISAMenu( widget, menuBar ):
bvMenu = QMenu( "&BrainVISA", menuBar ) # avoid creating the menu in addMenu
menuBar.addMenu( bvMenu ) # same problem as addAction()
_addAction( bvMenu, _t_( "&Help" ), helpRequest, Qt.CTRL + Qt.Key_H )
_addAction( bvMenu, _t_( "About" ), aboutRequest )
_addAction( bvMenu )
_addAction( bvMenu, _t_( "&Preferences" ),
neuroConfigGUI.editConfiguration, Qt.CTRL + Qt.Key_P )
_addAction( bvMenu, _t_( "Show &Log" ), logRequest, Qt.CTRL + Qt.Key_L )
_addAction( bvMenu, _t_( "&Open process..." ), ProcessView.open,
Qt.CTRL + Qt.Key_O )
_addAction( bvMenu, _t_( "Reload toolboxes" ), reloadToolboxesGUI )
_addAction( bvMenu, _t_( "Start &Shell" ), startShell, Qt.CTRL + Qt.Key_S )
_addAction( bvMenu )
if not isinstance( widget, ProcessSelectionWidget ):
_addAction( bvMenu, _t_( "Close" ), widget.close, Qt.CTRL + Qt.Key_W )
_addAction( bvMenu, _t_( "&Quit" ), quitRequest, Qt.CTRL + Qt.Key_Q )
return bvMenu
#----------------------------------------------------------------------------
class HTMLBrowser( QWidget ):
class BVTextBrowser( WebBrowserWithSearch ):
def __init__( self, parent, name=None ):
WebBrowserWithSearch.__init__( self, parent )
if name:
self.setObjectName(name)
#self.mimeSourceFactory().setExtensionType("py", "text/plain")
self.openWebAction = QAction( _t_( 'Open in a web browser' ), self )
self.openWebAction.setShortcut( Qt.CTRL + Qt.Key_W )
self.connect( self.openWebAction, SIGNAL( 'triggered(bool)' ),
self.openWeb )
def setSource( self, url ):
text=url.toString()
bvp = unicode( text )
if bvp.startswith( 'bvshowprocess://' ):
bvp = bvp[16:]
# remove tailing '/'
if bvp[ -1 ] == '/':
bvp = bvp[ : -1 ]
proc = brainvisa.processes.getProcess( bvp )
if proc is None:
print 'No process of name', bvp
else:
win = ProcessView( proc() )
win.show()
elif bvp.startswith( 'file://' ) and bvp.endswith( '.py' ):
WebBrowserWithSearch.setSource( self, url )
self.setHtml( '' + htmlEscape(open( url.toLocalFile() ).read()) + '
' )
sys.stdout.flush()
else:
WebBrowserWithSearch.setSource( self, url)
self.page().setLinkDelegationPolicy( QtWebKit.QWebPage.DelegateAllLinks )
def customMenu(self):
menu=WebBrowserWithSearch.customMenu(self)
menu.addAction( self.openWebAction )
return menu
def openWeb(self):
openWeb(self.url().toString())
def __init__( self, parent = None, name = None, fl = Qt.WindowFlags() ):
QWidget.__init__( self, parent, fl )
if name :
self.setObjectName( name )
vbox = QVBoxLayout( self )
self.setLayout(vbox)
vbox.setSpacing( 2 )
vbox.setContentsMargins( 3, 3, 3, 3 )
if getattr( HTMLBrowser, 'pixHome', None ) is None:
setattr( HTMLBrowser, 'pixIcon', QIcon( os.path.join( neuroConfig.iconPath, 'icon_help.png' ) ) )
setattr( HTMLBrowser, 'pixHome', QIcon( os.path.join( neuroConfig.iconPath, 'top.png' ) ) )
self.setWindowIcon( HTMLBrowser.pixIcon )
hbox = QHBoxLayout()
hbox.setSpacing(6)
hbox.setContentsMargins( 0, 0, 0, 0 )
self.homeAction = QAction( _t_( 'Home' ), self )
self.homeAction.setIcon( self.pixHome )
btnHome = QToolButton( )
btnHome.setSizePolicy( QSizePolicy( QSizePolicy.Minimum, QSizePolicy.Minimum ) )
hbox.addWidget( btnHome )
btnBackward = QToolButton( )
btnBackward.setSizePolicy( QSizePolicy( QSizePolicy.Minimum, QSizePolicy.Minimum ) )
hbox.addWidget( btnBackward )
btnForward = QToolButton( )
btnForward.setSizePolicy( QSizePolicy( QSizePolicy.Minimum, QSizePolicy.Minimum ) )
hbox.addWidget( btnForward )
btnReload = QToolButton( )
btnReload.setSizePolicy( QSizePolicy( QSizePolicy.Minimum, QSizePolicy.Minimum ) )
hbox.addWidget( btnReload )
vbox.addLayout( hbox )
browser = self.BVTextBrowser( self )
browser.setSizePolicy( QSizePolicy( QSizePolicy.Expanding, QSizePolicy.Expanding ) )
vbox.addWidget( browser )
self.connect( self.homeAction, SIGNAL('triggered(bool)'), self.home )
btnHome.setDefaultAction( self.homeAction )
btnForward.setDefaultAction( browser.pageAction( QtWebKit.QWebPage.Forward ) )
btnBackward.setDefaultAction( browser.pageAction( QtWebKit.QWebPage.Back ) )
a = browser.pageAction( QtWebKit.QWebPage.Reload )
a.setShortcut( QtGui.QKeySequence.Refresh )
btnReload.setDefaultAction( a )
self.connect( browser, SIGNAL('linkClicked(const QUrl &)' ),
browser.setSource )
self.browser = browser
neuroConfig.registerObject( self )
hbox.addWidget( QLabel( _t_( 'Search site:' ) ) )
self._siteSearch = QLineEdit()
hbox.addWidget( self._siteSearch )
self.connect( self._siteSearch, SIGNAL( 'returnPressed()' ),
self.siteSearch )
def setSource( self, source ):
if not isinstance( source, QUrl ):
if os.path.exists(source):
source = QUrl.fromLocalFile(source)
else:
source = QUrl(source)
self.browser.setSource( source )
def reload( self ):
self.browser.reload()
def setText( self, text ):
self.browser.setHtml( text )
def openWeb(self):
self.browser.openWeb()
def showCategoryDocumentation( self, category ):
"""
Searches for a documentation file associated to this category and opens it in this browser.
Documentation files are in docPath/processes/categories/category. Category is a relative path, so if no documentation is found with the entire path, it removes the first item and retries.
For example, if "t1 mri/viewers" doesn't exists, tries "viewers".
If there is no documentation file, the browser page is empty.
"""
categoryPath=category.lower().split("/")
found =False
while ((len(categoryPath) > 0) and not found):
html = neuroConfig.getDocFile(os.path.join(os.path.join( 'processes', 'categories', *categoryPath), 'category_documentation.html' ) )
if os.path.exists( html ):
self.setSource( html )
found=True
categoryPath=categoryPath[1:]
if not found:
self.browser.setHtml( '' )
def closeEvent( self, event ):
neuroConfig.unregisterObject( self )
QWidget.closeEvent( self, event )
def home( self, void=None ):
newver = checkbrainvisaupdates.checkUpdates()
if newver:
text = '''
A newer BrainVISA version is available
Version ''' + '.'.join( [ str(x) for x in newver[0] ] ) + ''' is available on the BrainVISA web site.
Download it on the BrainVISA download page.
'''
tmp = brainvisa.processes.defaultContext().temporary( 'HTML' )
open( tmp.fullPath(), 'w' ).write( text )
self.setSource( tmp.fullPath() )
else:
self.setSource( neuroConfig.getDocFile(os.path.join( 'help','index.html' ) ) )
def siteSearch( self ):
'''Search the brainvisa.info website using google search'''
if self._siteSearch.text():
if not hasattr( self, '_currentNonSearchPage' ):
self._currentNonSearchPage = self.browser.url()
url = 'http://www.google.com/cse?url=brainvisa.info&cref=http%3A%2F%2Fwww.google.com%2Fcse%2Ftools%2Fmakecse%3Furl%3Dbrainvisa.info&ie=&q=' + urllib.quote_plus( self._siteSearch.text() )
self.setSource( QUrl.fromEncoded( url ) )
else:
# get back to the previous non-search page
url = self._currentNonSearchPage
del self._currentNonSearchPage
self.setSource( url )
#----------------------------------------------------------------------------
class NamedPushButton( QPushButton ):
def __init__( self, parent, name ):
QPushButton.__init__( self, parent, name )
self.connect( self, SIGNAL( 'clicked()' ), self._pyClicked )
def _pyClicked( self ):
self.emit( SIGNAL( 'clicked' ), unicode( self.name()) )
#----------------------------------------------------------------------------
class WidgetScrollV( QScrollArea ):
#class VBox( QWidget ):
#def __init__( self, parent, ws ):
#QWidget.__init__( self, parent )
#layout=QVBoxLayout()
#self.setLayout(layout)
#layout.setSpacing( 5 )
#self.ws = ws
#def sizeHint( self ):
#return QSize( self.ws.visibleWidth(), QWidget.sizeHint( self ).height() )
def __init__( self, parent = None, name = None ):
#self.box = None
QScrollArea.__init__( self, parent)
if name:
self.setObjectName( name )
#self.setHorizontalScrollBarPolicy( Qt.ScrollBarAlwaysOff )
self.setFrameStyle( self.NoFrame )
#self.box = self.VBox( None, self )
#self.setWidget( self.box )
self.setSizePolicy( QSizePolicy( QSizePolicy.Expanding, QSizePolicy.Expanding ) )
self.setWidgetResizable(True)
#self.box.resize( self.visibleWidth(), self.box.height() )
#def show( self ):
#self.setMaximumSize( self.maximumWidth(), self.box.size().height() )
#return QScrollArea.show( self )
#def resizeEvent( self, e ):
#result = QScrollArea.resizeEvent( self, e )
#if self.widget():
#self.widget().resize( self.visibleWidth(), self.box.height() )
##self.updateGeometry()
#self.setMaximumSize( self.maximumWidth(), self.box.sizeHint().height() )
#if self.box.width() != self.visibleWidth():
#self.box.resize( self.visibleWidth(), self.box.height() )
#self.updateGeometry()
#return result
#def sizeHint( self ):
#if self.box:
#return QWidget.sizeHint( self.box )
#else:
#return QScrollArea.sizeHint( self )
#def visibleWidth(self):
#return self.viewport().width()
#----------------------------------------------------------------------------
class ExecutionContextGUI( brainvisa.processes.ExecutionContext):
def __init__( self ):
brainvisa.processes.ExecutionContext.__init__( self )
def ask( self, message, *buttons, **kwargs ):
modal=kwargs.get("modal", 1)
dlg = apply( self.dialog, (modal, message, None) + buttons )
return mainThreadActions().call( dlg.call )
def dialog( self, parentOrFirstArgument, *args, **kwargs ):
if isinstance( parentOrFirstArgument, QWidget ) or \
parentOrFirstArgument is None:
return self._dialog( parentOrFirstArgument, *args, **kwargs )
else:
return self._dialog( *((None,parentOrFirstArgument)+args), **kwargs )
def _dialog( self, parent, modal, message, signature, *buttons ):
return _mainThreadActions.call( UserDialog, parent, modal,
message, signature, buttons )
def mainThreadActions( self ):
return mainThreadActions()
def showProgress( self, value, maxval=None ):
def setProgress( self, value, maxval ):
if not maxval:
maxval = 100
if not hasattr( self, '_progressBar' ):
if not hasattr( self, 'inlineGUI' ):
# no GUI: fallback to text mode
brainvisa.processes.ExecutionContext.showProgress( self, value, maxval )
return
layout = self.inlineGUI.parentWidget().layout()
self._progressBar = QProgressBar( None )
layout.addWidget( self._progressBar )
self._progressBar.show()
if self._progressBar.maximum() != maxval:
self._progressBar.setRange( 0, maxval )
self._progressBar.setValue( int( round( value ) ) )
mainThreadActions().push( setProgress, self, value, maxval )
@staticmethod
def createContext():
return ExecutionContextGUI()
#----------------------------------------------------------------------------
class ExecutionNodeGUI(QWidget):
def __init__(self, parent, parameterized, read_only=False):
QWidget.__init__(self, parent)
layout = QVBoxLayout()
layout.setContentsMargins( 5, 5, 5, 5 )
layout.setSpacing( 4 )
self.setLayout(layout)
self.parameterizedWidget = ParameterizedWidget( parameterized, None )
if read_only:
self.parameterizedWidget.set_read_only(True)
layout.addWidget(self.parameterizedWidget)
def closeEvent(self, event):
self.parameterizedWidget.close()
QWidget.closeEvent(self, event)
def _checkReadable( self ):
if self.parameterizedWidget is not None:
self.parameterizedWidget.checkReadable()
#----------------------------------------------------------------------------
class VoidClass:
pass
#----------------------------------------------------------------------------
class RadioItem(QWidget):
"""An custom item to replace a QTreeWidgetItem for the representation of a SelectionExecutionNode item with a radio button.
QTreeWidgetItem enables only check box items."""
def __init__(self, text, group, parent=None):
QWidget.__init__(self, parent)
layout=QHBoxLayout()
layout.setContentsMargins( 0, 0, 0, 0 )
layout.setSpacing(0)
self.setLayout(layout)
self.radio=QRadioButton()
self.radio.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
layout.addWidget(self.radio)
group.addButton(self.radio)
self.icon=QLabel()
layout.addWidget(self.icon)
self.label=QLabel(text)
layout.addWidget(self.label)
layout.addStretch(1)
self.setAutoFillBackground(True)
# self.show()
def setChecked(self, checked):
self.radio.setChecked(checked)
def isChecked(self):
return self.radio.isChecked()
def setIcon(self, icon):
self.icon.setPixmap(icon.pixmap(*defaultIconSize))
#----------------------------------------------------------------------------
class NodeCheckListItem( QTreeWidgetItem ):
def __init__( self, node, parent, index=None, text=None, itemType=None, read_only=False ):
if not index is None :
QTreeWidgetItem.__init__( self )
parent.insertChild(index, self)
else :
QTreeWidgetItem.__init__( self, parent )
self._node = node
self.itemType=itemType
self.read_only = read_only
if itemType == "radio" and not self.read_only:
# if the item type is radio, create a custom item RadioItem to replace the current QTreeWidgetItem at display
# the radio button is included in a button group that is registred in the parent item
buttonGroup=getattr(self.parent(), "buttonGroup", None)
if not buttonGroup:
buttonGroup=QButtonGroup()
self.parent().buttonGroup=buttonGroup
self.widget=RadioItem(text, buttonGroup)
self.treeWidget().setItemWidget(self, 0, self.widget)
QWidget.connect(self.widget.radio, SIGNAL("clicked(bool)"), self.radioClicked)
QWidget.connect(self.widget.radio, SIGNAL("toggled(bool)"), self.radioToggled)
else:# not a radio button or read only, show text directly in the qtreeWidgetItem
if text:
self.setText(0, text)
self.setOn( node._selected )
node._selectionChange.add( self.nodeStateChanged )
def radioClicked(self, checked):
self.treeWidget().setCurrentItem(self)
def radioToggled(self, checked):
self.stateChange( checked )
def itemClicked(self):
if self.itemType == "check":
self.stateChange(self.isOn())
def setIcon(self, col, icon):
if self.itemType=="radio":
self.widget.setIcon(icon)
else:
QTreeWidgetItem.setIcon(self, col, icon)
def stateChange( self, selected ):
self._node.setSelected( selected )
def nodeStateChanged( self, node ):
self.setOn( node._selected )
def cleanup( self ):
self._node._selectionChange.remove( self.nodeStateChanged )
self._node = None
def setOn( self, b ):
if self.read_only:
if b:
self.setFlags(Qt.ItemIsSelectable | Qt.ItemIsDragEnabled | Qt.ItemIsEnabled)
else:
self.setFlags(Qt.ItemIsSelectable | Qt.ItemIsDragEnabled)
elif self.itemType=="radio":
self.widget.setChecked(b)
elif self.itemType=="check":
if b:
self.setCheckState( 0, Qt.Checked )
else:
self.setCheckState( 0, Qt.Unchecked )
def isOn( self ):
if self.read_only:
return int(Qt.ItemIsEnabled & self.flags())>0
elif self.itemType=="radio":
return self.widget.isChecked()
elif self.itemType=="check":
return self.checkState(0) == Qt.Checked
return True
def check(self, b):
"""
This method is used to check or uncheck a checkable item and warn the underlying model of the state change.
It is useful for the feature select/unselect before/after/all in pipelines and iterations.
"""
if self.itemType=="check" and not self.read_only:
if b:
self.setCheckState( 0, Qt.Checked )
else:
self.setCheckState( 0, Qt.Unchecked )
self.stateChange(b)
def currentItemChanged(self, current):
""" This function is called when the item gains or lose the status of current item of the tree widget.
In case the item is a radio button, its background and foreground colors are changed to follow the tree item widget policy.
:param current: indicates if the item is the current item or not. boolean.
"""
if self.itemType == "radio" and not self.read_only:
if current:
self.widget.setBackgroundRole(QPalette.Highlight)
self.widget.setForegroundRole(QPalette.HighlightedText)
else:
self.widget.setBackgroundRole(QPalette.Base)
self.widget.setForegroundRole(QPalette.Text)
#------------------------------------------------------------------------------
class ParameterLabel( QLabel ):
'''A QLabel that emits PYSIGNAL( 'contextMenuEvent' ) whenever a
contextMenuEvent occurs'''
def __init__( self, parameterName, mandatory, parent ):
if mandatory:
QLabel.__init__( self, '' + parameterName + ':',
parent )
else:
QLabel.__init__( self, parameterName + ':', parent )
#Save parameter name
self.parameterName = parameterName
# Create popup menu
self.contextMenu = QMenu( self )
#self.contextMenu.setCheckable( True )
self.default_id = _addAction( self.contextMenu, _t_( 'default value' ),
self.defaultChanged )
self.default_id.setCheckable(True)
self.default_id.setChecked( True )
self.addMenuLock()
self.setAutoFillBackground(True)
self.setBackgroundRole(QPalette.Window)
def set_read_only(self, read_only):
self.default_id.setEnabled(not read_only)
def contextMenuEvent( self, e ):
self.contextMenu.exec_( e.globalPos() )
e.accept()
def paramLabelText( self, val_default_id, val_lock_id ):
#CAS : val_default_id / val_lock_id
#val_default_id = 0 pas valeur par defaut donc icone
#val_default_id = 1 valeur par defaut donc pas icone
text = '' #cas 0 / 0
# if
# 1 / 0
# if 1 / 1
# else 0 / 1
if val_default_id:
if val_lock_id : text = text + '
'
else :
text = '
'
if val_lock_id : text = text + '
'
return(text)
def addMenuLock( self ):
self.lock_id = _addAction( self.contextMenu, _t_( 'lock' ),
self.lockChanged )
self.lock_id.setCheckable(False)
self.lock_id.setChecked( False )
def readText( self, txt ):
""" Function to return the value of text without value of tag for images """
if unicode( txt).startswith( '
' )
txt = txt[ x+3 : ]
return txt
return txt
def lockChanged( self, checked=False ):
""" This function is to lock or unlock data if a user click on the lock menu """
if checked:
self.emit( SIGNAL( 'lock_system' ), self.parameterName )
#warning the value of lock_id can be become false if we can't write the lock file.
#For example, if you try to lock a file which doesn't exist
else:
self.emit( SIGNAL( 'unlock_system' ), self.parameterName )
#txt = self.paramLabelText(self.default_id.isChecked(), False)
self.setlock(self.lock_id.isChecked()) #on remet a jour en fonction du resultat du unlock du fichier
txt = self.paramLabelText(self.default_id.isChecked(), self.lock_id.isChecked())
#print " ParameterLabel : lockChanged self.lock_id.isChecked() a false val de self.text() : " + self.text()
txt_value_parameter = self.readText(self.text())
while (self.readText(txt_value_parameter) != txt_value_parameter) :
txt_value_parameter = self.readText(txt_value_parameter)
txt = txt + txt_value_parameter
self.setText( unicode(txt) )
def setlock( self, default):
""" This function is to set lock or unlock data """
self.lock_id.setChecked(default)
txt = self.paramLabelText(self.default_id.isChecked(), default)
txt_value_parameter = self.readText(self.text())
while (self.readText(txt_value_parameter) != txt_value_parameter) :
txt_value_parameter = self.readText(txt_value_parameter)
txt = txt + txt_value_parameter
self.setText( unicode(txt) )
def defaultChanged( self, checked=False ):
# print "-- FUNCTION defaultChanged : neuroProcessesGUI / ParameterLabel --", checked
self.emit( SIGNAL( 'toggleDefault' ), self.parameterName )
txt = self.paramLabelText(self.default_id.isChecked(), self.lock_id.isChecked())
txt_value_parameter = self.readText(self.text())
while (self.readText(txt_value_parameter) != txt_value_parameter) :
txt_value_parameter = self.readText(txt_value_parameter)
txt = txt + txt_value_parameter
self.setText( unicode(txt) )
def setDefault( self, default ):
# print "-- FUNCTION setDefault : neuroProcessesGUI / ParameterLabel --"
self.default_id.setChecked( default )
#self.lockChanged()
txt = self.paramLabelText(self.default_id.isChecked(), self.lock_id.isChecked())
txt_value_parameter = self.readText(self.text())
while (self.readText(txt_value_parameter) != txt_value_parameter) :
txt_value_parameter = self.readText(txt_value_parameter)
txt = txt + txt_value_parameter
self.setText( unicode(txt))
#def defaultChanged( self, checked=False ):
##self.default_id.toggle()
#self.emit( SIGNAL( 'toggleDefault' ), self.parameterName )
#if self.default_id.isChecked():
#txt = unicode( self.text() )
#if txt.startswith( '
' )
#txt = txt[ x+3 : ]
#self.setText( txt )
#else:
#if not unicode( self.text() ).startswith( '
' + self.text() )
#def setDefault( self, default ):
#self.default_id.setChecked( default )
#if default:
#txt = unicode( self.text() )
#if txt.startswith( '
' )
#txt = txt[ x+3 : ]
#self.setText( txt )
#else:
#if not unicode( self.text() ).startswith( '
' + self.text() )
#------------------------------------------------------------------------------
class ParameterizedWidget( QWidget ):
def __init__( self, parameterized, parent ):
#lock# if getattr( ParameterizedWidget, 'pixDefault', None ) is None:
#lock# setattr( ParameterizedWidget, 'pixDefault', QPixmap( os.path.join( neuroConfig.iconPath, 'lock.png' ) ) )
#lock# setattr( ParameterizedWidget, 'pixCustom', QPixmap( os.path.join( neuroConfig.iconPath, 'unlock.png' ) ) )
QWidget.__init__( self, parent )
self.connect( self, SIGNAL( 'destroyed()' ), self.cleanup )
layout = QVBoxLayout( )
layout.setContentsMargins( 0, 0, 0, 0 )
layout.setSpacing(4)
self.setLayout(layout)
# the scroll widget will contain parameters widgets
self.scrollWidget = WidgetScrollV( )
layout.addWidget( self.scrollWidget )
#spacer = QSpacerItem( 0, 0, QSizePolicy.Minimum, QSizePolicy.Expanding )
#layout.addItem( spacer )
# Using weakref here because self.parameterized may contain a reference
# to self.parameterChanged (see below) which is a bound method that contains
# a reference to self. If nothing is done self is never destroyed.
self.parameterized = weakref.proxy( parameterized )
self.parameterized.deleteCallbacks.append( self.parameterizedDeleted )
self.labels={}
self.editors={}
#lock# self.btnLock={}
self._currentDirectory = None
self._doUpdateParameterValue = False
first = None
documentation = {}
id = getattr(parameterized, '_id', None)
if id is not None:
procdoc = brainvisa.processes.readProcdoc( id )
if procdoc:
documentation = procdoc.get( neuroConfig.language )
if documentation is None:
documentation = procdoc.get( 'en', {} )
# the widget that will contain parameters, it will be put in the scroll widget
parametersWidget=QWidget()
# QGridLayout : a label and an editor for each parameter, in 2 columns
parametersWidgetLayout=QGridLayout()
parametersWidgetLayout.setContentsMargins( 0, 0, 0, 0 )
if sys.platform == 'darwin' and QtCore.qVersion() == '4.6.2':
# is this layout problem a bug in qt/Mac 4.6.2 ?
parametersWidgetLayout.setSpacing(0)
else:
parametersWidgetLayout.setSpacing(2)
parametersWidget.setLayout(parametersWidgetLayout)
# create a widget for each parameter
line = 0
for k, p in self.parameterized.signature.items():
if neuroConfig.userLevel >= p.userLevel:
l = ParameterLabel( k, p.mandatory, None )
parametersWidgetLayout.addWidget(l, line, 0 )
l.setDefault(self.parameterized.isDefault( k ))
self.connect( l, SIGNAL( 'toggleDefault' ), self._toggleDefault )
if isinstance( p, ReadDiskItem ):
l.lock_id.setCheckable(True)
l.setlock(self._setlock_system(k)) #ini la valeur de lock du parametre
#self.connect( l, SIGNAL( 'setlock_system' ), self._setlock_system )
self.connect( l, SIGNAL( 'lock_system' ), self._lock_system )
self.connect( l, SIGNAL( 'unlock_system' ), self._unlock_system )
l.setSizePolicy( QSizePolicy.Fixed, QSizePolicy.Fixed )
self.labels[ k ] = l
e = p.editor( None, k, weakref.proxy( self ) )
parametersWidgetLayout.addWidget(e, line, 1)
self.parameterized.addParameterObserver( k, self.parameterChanged )
self.editors[ k ] = e
if first is None: first = e
v = getattr( self.parameterized, k, None )
if v is not None:
self.setValue( k, v, 1 )
e.valuePropertiesChanged( self.parameterized.isDefault( k ) )
e.connect( e, SIGNAL('noDefault'), self.removeDefault )
e.connect( e, SIGNAL('newValidValue'), self.updateParameterValue )
#lock# btn = NamedPushButton( hb, k )
#lock# btn.setPixmap( self.pixCustom )
#lock# btn.setFocusPolicy( QWidget.NoFocus )
#lock# btn.setToggleButton( 1 )
#lock# btn.hide()
#lock# self.connect( btn, PYSIGNAL( 'clicked' ), self._toggleDefault )
#lock# self.btnLock[ k ] = btn
if documentation is not None:
self.setParameterToolTip( k,
XHTML.html( documentation.get( 'parameters', {} ).get( k, '' ) ) \
+ '
: ' \
+ _t_( \
'value has been manually changed and is not modified by links anymore' ) \
+ '' )
line += 1
parametersWidget.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum))
self.scrollWidget.setWidget(parametersWidget)
if first: first.setFocus()
self._doUpdateParameterValue = True
#self.scrollWidget.widget().resize(600, 200)
# self.scrollWidget.show()
def set_read_only(self, read_only):
for x in self.editors.keys():
self.editors[x].set_read_only(read_only)
for x in self.labels.keys():
self.labels[x].set_read_only(read_only)
def parameterizedDeleted( self, parameterized ):
for k, p in parameterized.signature.items():
try:
parameterized.removeParameterObserver( k, self.parameterChanged )
except ValueError:
pass
self.parameterized = None
for x in self.editors.keys():
self.editors[x].releaseCallbacks()
def __del__( self ):
if self.parameterized is not None:
self.parameterizedDeleted( self.parameterized )
#QWidget.__del__( self )
def cleanup( self ):
for x in self.editors.keys():
self.editors[x].releaseCallbacks()
def closeEvent( self, event ):
for k, p in self.parameterized.signature.items():
try:
self.parameterized.removeParameterObserver( k, self.parameterChanged )
except ValueError:
pass
self.cleanup()
QWidget.closeEvent( self, event )
def setParameterToolTip( self, parameterName, text ):
self.labels[ parameterName ].setToolTip(self.parameterized.signature[ parameterName ].toolTipText( parameterName, text ))
def parameterChanged( self, parameterized, parameterName, value ):
"""This method is called when an attribute has changed in the model.
A parameter can change in the model because it is links to another parameter that has changed or because the user changed it in the GUI."""
# It is necessary to read user values before applying changes,
# otherwise selected data are reset
self.readUserValues()
self._doUpdateParameterValue = False
default=parameterized.isDefault( parameterName )
self.setValue( parameterName, value, default = default)
self.labels[ parameterName ].setDefault( default )
self.editors[ parameterName ].valuePropertiesChanged( default )
#lock system
self.labels[parameterName].setlock(self._setlock_system(parameterName))
self._doUpdateParameterValue = True
def updateParameterValue( self, name, value ):
#lock system
self.labels[name].setlock(self._setlock_system(name))
if self._doUpdateParameterValue:
setattr( self.parameterized, name, value )
def removeDefault( self, name ):
#lock system
self.labels[ name ].setlock(self._setlock_system(name))
self.parameterized.setDefault( name, False )
self.labels[ name ].setDefault( False )
self.editors[ name ].valuePropertiesChanged( False )
#lock# self.btnLock[ name ].setPixmap( self.pixDefault )
#lock# self.btnLock[ name ].setOn( 1 )
#lock# self.btnLock[ name ].show()
def _toggleDefault( self, name ):
isdefault = not self.parameterized.isDefault( name )
self.parameterized.setDefault( name, isdefault )
self.editors[ name ].valuePropertiesChanged( isdefault )
def _lock_system( self, name ):
"""function for lock system : lock a diskItem if the file exists"""
#print "-- FUNCTION _lock_system : neuroProcessesGUI / ParameterizedWidget-- "
#value = self.parameterized.__getattribute__(name)
value = getattr(self.parameterized, name, None)
if value is not None :
isLock = value.lockData()
if isLock : self.labels[name].lock_id.setChecked(True)
else : self.labels[name].lock_id.setChecked(False)
def _unlock_system( self, name ):
"""function for lock system : unlock a file"""
#print "-- FUNCTION _unlock_system : neuroProcessesGUI / ParameterizedWidget-- "
#value = self.parameterized.__getattribute__(name)
value = getattr(self.parameterized, name, None)
if value is not None :
value.unlockData()
def _setlock_system( self, name ):
"""function for lock system : lock a diskItem if the file exists"""
#from brainvisa.data.neuroDiskItems import DiskItem
#print "-- FUNCTION _setlock_system : neuroProcessesGUI / ParameterizedWidget-- "
#value = self.parameterized.__getattribute__(name)
value = getattr(self.parameterized, name, None)
if value is not None and isinstance( value, DiskItem ):
valueToSet = value.isLockData()
return valueToSet
else :
return (False)
def checkReadable( self ):
for ( n, p ) in self.parameterized.signature.items():
if p.checkReadable( getattr( self.parameterized, n, None ) ):
e = self.editors.get( n )
if e is not None: e.checkReadable()
def readUserValues( self ):
'Ensure that values typed by the user are taken into account'
for n in self.parameterized.signature.keys():
e = self.editors.get( n )
if e is not None:
e.checkValue()
def setValue( self, parameterName, value, default=0 ):
'Set the value of a parameter'
oldValue=self.editors[ parameterName ].getValue()
self.editors[ parameterName ].setValue( value, default = default )
# use signals-slots to update the parameter label gui if the lock status of the matching value changes.
# DiskItems now inherit from QObject and emit lockChanged signal when the lock changes
if isinstance(oldValue, QObject):
self.disconnect(oldValue, SIGNAL("lockChanged"), self.labels[parameterName].setlock)
self.disconnect(oldValue, SIGNAL("lockChanged"), self.editors[parameterName].lockChanged)
if isinstance(value, QObject):
self.connect(value, SIGNAL("lockChanged"), self.labels[parameterName].setlock )
self.connect(value, SIGNAL("lockChanged"), self.editors[parameterName].lockChanged)
#----------------------------------------------------------------------------
class BrainVISAAnimation( QLabel ):
def __init__( self, parent=None ):
QLabel.__init__( self, parent )
self.mmovie = QMovie( os.path.join( neuroConfig.iconPath, 'rotatingBrainVISA.gif' ) ) #, 1024*10 )
self.setMovie( self.mmovie )
#self.mmovie.setSpeed( 500 )
#qApp.processEvents()
self.mmovie.stop()
def start( self ):
self.mmovie.start()
def stop( self ):
self.mmovie.start()
qApp.processEvents()
self.mmovie.stop()
#----------------------------------------------------------------------------
class ProcessView( QWidget, ExecutionContextGUI ):
#actions:
action_save_process = None
action_clone_process = None
action_create_workflow = None
action_run = None
action_interupt = None
action_interupt_step = None
action_run_with_sw = None
action_iterate = None
eTreeWidget = None
menu = None
parameterizedWidget = None
read_only = None
def __init__( self,
processId,
parent=None,
externalInfo=None,
read_only=False):
ExecutionContextGUI.__init__( self )
QWidget.__init__( self, parent )
if getattr( ProcessView, 'pixIcon', None ) is None:
setattr( ProcessView, 'pixIcon', QIcon( os.path.join( neuroConfig.iconPath, 'icon_process.png' ) ) )
setattr( ProcessView, 'pixDefault', QIcon( os.path.join( neuroConfig.iconPath, 'lock.png' ) ) )
setattr( ProcessView, 'pixInProcess', QIcon( os.path.join( neuroConfig.iconPath, 'forward.png' ) ) )
setattr( ProcessView, 'pixProcessFinished', QIcon( os.path.join( neuroConfig.iconPath, 'ok.png' ) ) )
setattr( ProcessView, 'pixProcessError', QIcon( os.path.join( neuroConfig.iconPath, 'abort.png' ) ) )
setattr( ProcessView, 'pixNone', QIcon() )
self.read_only = read_only
# ProcessView cannot be a QMainWindow because it have to be included in a QStackedWidget in pipelines.
#centralWidget=QWidget()
#self.setCentralWidget(centralWidget)
centralWidgetLayout=QVBoxLayout()
self.setLayout(centralWidgetLayout)
centralWidgetLayout.setContentsMargins( 5, 5, 5, 5 )
centralWidgetLayout.setSpacing( 4 )
self.setWindowIcon( self.pixIcon )
self.workflowEnabled = False
self.action_save_process = QAction(_t_( '&Save...' ), self)
self.action_save_process.setShortcut( Qt.CTRL + Qt.Key_S )
self.action_save_process.triggered.connect(self.saveAs)
self.action_clone_process = QAction(_t_( '&Clone...' ), self)
self.action_clone_process.setShortcut(Qt.CTRL + Qt.Key_C)
self.action_clone_process.triggered.connect(self.clone)
self.action_create_workflow = QAction(_t_('Create &Workflow...'), self)
self.action_create_workflow.setShortcut(Qt.CTRL + Qt.Key_D)
self.action_create_workflow.triggered.connect(self.createWorkflow)
self.action_run = QAction(_t_('Run') , self)
self.action_run.triggered.connect(self._run)
self.action_run_with_sw = QAction(_t_('Run in parallel'), self)
self.action_run_with_sw.setToolTip('Run in parallel using Soma-workflow')
self.action_run_with_sw.triggered.connect(self._run_with_soma_workflow)
self.action_interupt = QAction(_t_('Interrupt'), self)
self.action_interupt.triggered.connect(self._interruptButton)
self.action_interupt.setVisible(False)
self.action_interupt_step = QAction(_t_('Interrupt current step'), self)
self.action_interupt_step.triggered.connect(self._interruptStepButton)
self.action_interupt_step.setVisible(False)
self.action_iterate = QAction(_t_('Iterate'), self)
self.action_iterate.triggered.connect(self._iterateButton)
self.action_lock_all = QAction(_t_('Lock all files'), self)
self.action_lock_all.triggered.connect(self.menuLockAllFiles)
self.action_unlock_all = QAction(_t_('Unlock all files'), self)
self.action_unlock_all.triggered.connect(self.menuUnlockAllfiles)
if parent is None:
neuroConfig.registerObject( self )
# menu bar
self.menu = QMenuBar( self )
addBrainVISAMenu( self, self.menu )
# warning: don't create the menu using addMenu() in PyQt
processMenu = QMenu( _t_( "&Process" ), self.menu )
self.menu.addMenu(processMenu)
processMenu.addAction(self.action_save_process)
processMenu.addAction(self.action_clone_process)
processMenu.addAction(self.action_iterate)
_addSeparator( processMenu )
processMenu.addAction(self.action_create_workflow)
_addSeparator( processMenu )
processMenu.addAction(self.action_run)
processMenu.addAction(self.action_interupt)
processMenu.addAction(self.action_interupt_step)
_addSeparator( processMenu )
processMenu.addAction(self.action_run_with_sw)
_addSeparator( processMenu )
processMenu.addAction(self.action_lock_all)
processMenu.addAction(self.action_unlock_all)
# warning: don't create the menu using addMenu() in PyQt
view_menu = QMenu( _t_( "&View" ), self.menu )
self.menu.addMenu(view_menu)
view_menu.addAction(close_viewers_action(self))
try:
import soma_workflow
self.workflowEnabled = True
except ImportError:
pass
if not self.workflowEnabled:
self.action_create_workflow.setEnabled(False)
centralWidgetLayout.addWidget(self.menu)
self.connect( self, SIGNAL( 'destroyed()' ), self.cleanup )
process = brainvisa.processes.getProcessInstance( processId )
if process is None:
raise RuntimeError( neuroException.HTMLMessage(_t_( 'Cannot open process %s' ) % ( str(processId), )) )
self.process = process
self.process.guiContext = weakref.proxy( self )
self._runningProcess = 0
self.process.signatureChangeNotifier.add( self.signatureChanged )
self.btnRun = None
self.btnRunSomaWorkflow = None
self.btnInterrupt = None
self.btnInterruptStep = None
self._running = False
if self.process.__class__ == brainvisa.processes.IterationProcess:
self.action_iterate.setVisible(False)
procdoc = brainvisa.processes.readProcdoc( process )
documentation = procdoc.get( neuroConfig.language )
if documentation is None:
documentation = procdoc.get( 'en', {} )
t = _t_(process.name) + ' ' + unicode( process.instance )
self.setWindowTitle( t )
if process.showMaximized:
self.showMaximized()
# title of the process : label + rotating icon when it's running
titleLayout = QHBoxLayout( )
centralWidgetLayout.addLayout(titleLayout)
if not parent:
self.labName = QLabel( t, self )
else:
self.labName = QLabel( _t_(process.name), self )
titleLayout.addWidget(self.labName)
self.labName.setFrameStyle( QFrame.Panel | QFrame.Raised )
self.labName.setLineWidth( 1 )
self.labName.setContentsMargins( 5, 5, 5, 5 )
self.labName.setAlignment( Qt.AlignCenter )
self.labName.setWordWrap( True )
font = self.labName.font()
font.setPointSize( QFontInfo( font ).pointSize() + 4 )
self.labName.setFont( font )
self.labName.setSizePolicy( QSizePolicy( QSizePolicy.Expanding, QSizePolicy.Preferred ) )
doc = XHTML.html( documentation.get( 'short', '' ) )
if doc:
self.labName.setToolTip('' + _t_(process.name) + '
' + _t_('Description') + ':
' + doc)
if externalInfo is None:
self.movie = BrainVISAAnimation( )
titleLayout.addWidget(self.movie)
titleLayout.setSpacing(3)
# vertical splitter : parameters, log text widget
splitter = QSplitter( Qt.Vertical )
centralWidgetLayout.addWidget(splitter)
# at the top of the splitter : the parameters
self.parametersWidget = QWidget(splitter)
parametersWidgetLayout = QVBoxLayout( )
parametersWidgetLayout.setContentsMargins( 0, 0, 0, 0 )
self.parametersWidget.setLayout(parametersWidgetLayout)
# at the bottom of the splitter : the text widget to log information about process execution
self.info = QTextEdit( splitter )
self.info.setReadOnly( True )
self.info.setAcceptRichText( True )
sizePolicy=QSizePolicy( QSizePolicy.Expanding, QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(50)
self.info.setSizePolicy( sizePolicy )
self.infoCounter = None
#splitter.setResizeMode( self.info, splitter.Stretch )
# splitter.setResizeMode( self.parametersWidget, QSplitter.FollowSizeHint )
#splitter.setResizeMode( self.parametersWidget, QSplitter.Auto )
container = self.parametersWidget
self.isMainWindow = True
else:
self.movie = None
splitter = None
self.parametersWidget = self
container = self
self.isMainWindow = False
self.info = externalInfo
self.splitter = splitter
self._widgetStack = None
eNode = getattr( process, '_executionNode', None )
self._executionNodeLVItems = {}
# process composed of several processes
if eNode is not None and self.isMainWindow:
self.parameterizedWidget = None
#vb = QVBoxLayout( )
#container.layout().addLayout(vb)
vb=container.layout()
# splitter that shows the composition of the process on the left and the parameters of each step on the right
self.eTreeWidget = QSplitter( Qt.Horizontal )
vb.addWidget(self.eTreeWidget)
# Run and iterate buttons
self.inlineGUI = self.process.inlineGUI( self.process, self, None,
externalRunButton = True )
if self.inlineGUI is None and externalInfo is None:
self.inlineGUI = self.defaultInlineGUI( None )
vb.addWidget(self.inlineGUI)
# composition of the pipeline
self.executionTree = QTreeWidget( self.eTreeWidget )
self.executionTree.setSizePolicy( QSizePolicy( QSizePolicy.Preferred, QSizePolicy.Preferred ) )
self.executionTree.setColumnCount(1)
self.executionTree.setHeaderLabels( ['Name'] )
self.executionTree.setAllColumnsShowFocus( 1 )
self.executionTree.setRootIsDecorated( 1 )
self.executionTree.setContextMenuPolicy(Qt.CustomContextMenu)
# Popup Menu for toolboxes
self.executionTreeMenu = QMenu( self )
_addAction( self.executionTreeMenu, _t_("Unselect before"), self.menuUnselectBefore)
_addAction( self.executionTreeMenu, _t_("Unselect after"), self.menuUnselectAfter)
#self.executionTreeMenu.addAction( _t_("Unselect all"), self.menuUnselectAll )
_addAction( self.executionTreeMenu, _t_("Select before"), self.menuSelectBefore)
_addAction( self.executionTreeMenu, _t_("Select after"), self.menuSelectAfter)
#self.executionTreeMenu.addAction( _t_("Select all"), self.menuSelectAll )
_addAction( self.executionTreeMenu )
_addAction( self.executionTreeMenu, _t_("Unselect steps writing locked files"), self.menuUnselectLocked )
_addAction( self.executionTreeMenu, _t_("Unselect steps upstream of locked files"), self.menuUnselectLockedUpstream )
_addAction( self.executionTreeMenu )
self.executionTreeMenu._opennodeaction = _addAction(
self.executionTreeMenu, _t_("Open this step separately"),
self.menuDetachExecutionNode )
self.executionTreeMenu._showdocaction \
= _addAction( self.executionTreeMenu, _t_("Show documentation"),
self.menuShowDocumentation )
_addAction( self.executionTreeMenu )
_addAction( self.executionTreeMenu, _t_("Lock files under this node"),
self.menuLockStep )
_addAction( self.executionTreeMenu, _t_("Unlock files under this node"),
self.menuUnlockStep )
self.executionTreeMenu._nodeactionseparator \
= _addAction( self.executionTreeMenu )
self.executionTreeMenu._addnodeaction \
= _addAction( self.executionTreeMenu, _t_("Add node"),
self.menuAddExecutionNode )
self.executionTreeMenu._removenodeaction \
= _addAction( self.executionTreeMenu, _t_("Remove node"),
self.menuRemoveExecutionNode )
self.connect(self.executionTree, SIGNAL( 'customContextMenuRequested ( const QPoint & )'), self.openContextMenu)
#self.executionTree.setSortingEnabled( -1 )
#self.eTreeWidget.setResizeMode( self.executionTree, QSplitter.KeepSize )
if self.read_only:
self.executionTreeMenu.setEnabled(False)
# parameters of a each step of the pipeline
self._widgetStack = QStackedWidget( self.eTreeWidget )
self._widgetStack.setSizePolicy( QSizePolicy( QSizePolicy.Preferred,
QSizePolicy.Preferred ) )
self._widgetStack._children = []
# set splitter sizes to avoid the widget stack to be hidden in case it is currently empty
self.eTreeWidget.setSizes( [150, 250] )
self._guiId = 0
self._executionNodeExpanded( self.executionTree, ( eNode, (eNode,) ) )
self.connect( self.executionTree,
SIGNAL( 'itemExpanded( QTreeWidgetItem * )' ),
self._executionNodeExpanded )
self.connect( self.executionTree,
SIGNAL( 'currentItemChanged( QTreeWidgetItem *, QTreeWidgetItem * )' ),
self.executionNodeSelected )
self.connect( self.executionTree,
SIGNAL( 'itemClicked( QTreeWidgetItem *, int )' ),
self.executionNodeClicked )
# Select and open the first item
item = self.executionTree.topLevelItem(0)
item.setExpanded( True )
self.executionTree.setCurrentItem( item )
# simple process : only a signature, no sub-processes
else:
self.executionTree = None
self.createSignatureWidgets( documentation )
self._iterationDialog = None
self._logDialog = None
# It is necessary to call show() before resize() to have a correct layout.
# Otherwize, vertical sliders may be superimposed to widgets. But some
# window managers compute the window location according to its size during
# show(). If the window become larger after show() call, part of it will be
# off screen. Therefore set the widget size before show() ensure that
# window location is correct and changing size after show() ensure that
# layout is correct.
if parent is None:
self.show()
self.resize( 600, 801 )
#qApp.processEvents()
initGUI = getattr( self.process, 'initializationGUI', None )
if initGUI is not None:
initGUI( self )
if self.read_only and self.parameterizedWidget != None:
self.parameterizedWidget.set_read_only(True)
def createSignatureWidgets( self, documentation=None ):
eNode = getattr( self.process, '_executionNode', None )
signatureWidget=None
# if the process has a signature, creates a widget for the parameters : ParameterizedWidget
if eNode and self.isMainWindow:
parent = self._widgetStack
if self.process.signature:
signatureWidget=eNode.gui(parent, processView=self)
else:
parent = self.parametersWidget.layout()
if self.process.signature:
signatureWidget = ParameterizedWidget( self.process, None )
parent.addWidget(signatureWidget)
self.parameterizedWidget=signatureWidget
if eNode is None or not self.isMainWindow:
self.inlineGUI = self.process.inlineGUI( self.process, self, None )
if self.inlineGUI is None and self.isMainWindow:
self.inlineGUI = self.defaultInlineGUI( None )
if self.inlineGUI is not None:
parent.addWidget(self.inlineGUI)
else:
self._widgetStack.removeWidget( self._widgetStack._children[ 0 ] )
self._widgetStack._children[ 0 ].close()
self._widgetStack._children[ 0 ].deleteLater()
if signatureWidget is not None:
self._widgetStack.insertWidget(0, signatureWidget )
self._widgetStack._children[ 0 ] = signatureWidget
self._widgetStack.setCurrentIndex( 0 )
# if self.parameterizedWidget is not None:
# if documentation is not None:
# for ( k, p ) in self.process.signature.items():
# if neuroConfig.userLevel >= p.userLevel:
# self.parameterizedWidget.setParameterToolTip( k,
# XHTML.html( documentation.get( 'parameters', {} ).get( k, '' ) ) \
# + '
: ' \
# + _t_( \
# 'value has been manually changed and is not linked anymore' ) \
# + '' )
# self.parameterizedWidget.show()
# if self.inlineGUI is not None:
# self.inlineGUI.show()
if self.parameterizedWidget != None:
self.parameterizedWidget.set_read_only(self.read_only)
def eraseSignatureWidgets( self ):
if self.parameterizedWidget is not None:
self.parameterizedWidget.close()
self.parameterizedWidget.deleteLater()
eNode = getattr( self.process, '_executionNode', None )
if eNode is None and self.inlineGUI is not None:
self.inlineGUI.close()
self.inlineGUI.deleteLater()
def signatureChanged( self, process ):
self.eraseSignatureWidgets()
self.createSignatureWidgets( None )
# Execution tree menu
def openContextMenu(self, point):
"""
Called on contextMenuRequested signal. It opens the popup menu at cursor position.
"""
item=self.executionTree.currentItem()
if item:
enode = item._executionNode
parent = item.parent()
if parent:
pnode = parent._executionNode
else:
pnode = None
if hasattr( enode, '_process' ):
self.executionTreeMenu._showdocaction.setEnabled( True )
else:
self.executionTreeMenu._showdocaction.setEnabled( False )
# Show/Hide node actions
if isinstance( enode, brainvisa.processes.ProcessExecutionNode ) :
enode = enode._executionNode
if isinstance( enode, brainvisa.processes.ParallelExecutionNode ) \
and enode.possibleChildrenProcesses :
self.executionTreeMenu._addnodeaction.setVisible( True )
else:
self.executionTreeMenu._addnodeaction.setVisible( False )
if isinstance( pnode, brainvisa.processes.ProcessExecutionNode ) :
pnode = pnode._executionNode
if isinstance( pnode, brainvisa.processes.ParallelExecutionNode ) \
and pnode.possibleChildrenProcesses :
self.executionTreeMenu._removenodeaction.setVisible( True )
else:
self.executionTreeMenu._removenodeaction.setVisible( False )
self.executionTreeMenu._nodeactionseparator.setVisible( \
self.executionTreeMenu._addnodeaction.isVisible() or \
self.executionTreeMenu._removenodeaction.isVisible() )
self.executionTreeMenu.exec_(QCursor.pos())
def changeItemSelection(self, select=True, all=True, before=False ):
item=self.executionTree.currentItem()
if item:
parent=item.parent()
if parent:
if all:
r=xrange(parent.childCount())
elif before:
r=xrange(parent.indexOfChild(item))
else:# after
r=xrange(parent.indexOfChild(item)+1, parent.childCount())
for i in r:
parent.child(i).check(select)
else:
parent=item.treeWidget()
if all:
r=xrange(parent.topLevelItemCount())
elif before:
r=xrange(parent.indexOfTopLevelItem(item))
else:# after
r=xrange(parent.indexOfTopLevelItem(item)+1, parent.topLevelItemCount())
for i in r:
parent.topLevelItem(i).check(select)
def menuUnselectBefore(self):
self.changeItemSelection(select=False, all=False, before=True)
def menuUnselectAfter(self):
self.changeItemSelection(select=False, all=False, before=False)
#def menuUnselectAll(self):
#self.changeItemSelection(select=False, all=True, before=False)
def menuSelectBefore(self):
self.changeItemSelection(select=True, all=False, before=True)
def menuSelectAfter(self):
self.changeItemSelection(select=True, all=False, before=False)
#def menuSelectAll(self):
#self.changeItemSelection(select=True, all=True, before=False)
def lockedSteps( self, useUnselected=False ):
items = [ self.process.executionNode() ]
locked = []
while items:
item = items.pop()
if not useUnselected and not item.isSelected():
continue
children = list( item.children() )
if len( children ) == 0: # terminal node
process = item._process
for n, p in process.signature.iteritems():
if isinstance( p, WriteDiskItem ):
v = getattr( item._process, n )
#test if data is locked
if v is not None and v.isLockData():
locked.append( item )
else:
if useUnselected:
items += list( item.children() )
else:
items += [ x for x in item.children() if x.isSelected() ]
return locked
def parentNodes( self, enode ):
items = [ [ self.process.executionNode() ] ]
chain = []
# walk the tree "vertically" getting deep in branches first
while items:
iteml = items[-1]
if len( iteml ) == 0:
items.pop()
chain.pop()
continue
item = iteml.pop()
if item is enode:
return chain
children = list( item.children() )
if len( children ) != 0:
chain.append( item )
items.append( list( item.children() ) )
def menuUnselectLocked( self ):
locked = self.lockedSteps()
for item in locked:
if item._optional:
item.setSelected( False )
else:
parents = [ x for x in self.parentNodes( item ) if x._optional ]
if parents:
parents[-1].setSelected( False )
def menuUnselectLockedUpstream( self ):
items = self.lockedSteps( useUnselected=True )
while items:
item = items.pop()
if item._optional:
item.setSelected( False )
else:
parents = [ x for x in self.parentNodes( item ) if x._optional ]
if parents:
parents[-1].setSelected( False )
items.append( parents[-1] )
continue
parents = self.parentNodes( item )
parents.reverse()
for p in parents:
if p.isSelected():
if p.__class__ is brainvisa.processes.SerialExecutionNode:
for sp in p.children():
if sp is item:
break
if sp._optional:
sp.setSelected( False )
else:
sparents = [ x for x in self.parentNodes( sp ) if x._optional ]
if sparents:
sparents[-1].setSelected( False )
item = p
def _changeAllLockedFiles( self, procOrNode, setLock ):
files = procOrNode.allParameterFiles()
# filter out non-existing files and already locked ones
if setLock:
files = [ f for f in files if f.isWriteable() and not f.isLockData() ]
else:
files = [ f for f in files if f.isWriteable() and f.isLockData() ]
# show and confirm
dialog = lockFilesGUI.LockedFilesListEditor( self, files, setLock )
if dialog.exec_():
files = dialog.selectedDiskItems()
if files:
if setLock:
print 'Locking...'
for f in files:
try:
f.lockData()
except IOError:
pass # probably not writeable
print 'done.'
else:
print 'Unlocking...'
for f in files:
try:
f.unlockData()
except IOError:
pass
print 'done.'
def menuLockAllFiles( self ):
self._changeAllLockedFiles( self.process, True )
def menuUnlockAllfiles( self ):
self._changeAllLockedFiles( self.process, False )
def menuDetachExecutionNode(self):
item=self.executionTree.currentItem()
if item:
proc = item._executionNode
self.readUserValues()
event = ProcessExecutionEvent()
event.setProcess( brainvisa.processes.getProcessInstance( proc ) )
clone = brainvisa.processes.getProcessInstanceFromProcessEvent( event )
return showProcess( clone )
def menuShowDocumentation(self):
global _mainWindow
item=self.executionTree.currentItem()
if item:
enode = item._executionNode
if hasattr( enode, '_process' ):
proc = enode._process
doc = brainvisa.processes.getHTMLFileName(proc)
if os.path.exists(doc):
_mainWindow.info.setSource(doc)
def menuLockStep(self):
global _mainWindow
item=self.executionTree.currentItem()
if item:
enode = item._executionNode
self._changeAllLockedFiles( enode, True )
def menuUnlockStep(self):
global _mainWindow
item=self.executionTree.currentItem()
if item:
enode = item._executionNode
self._changeAllLockedFiles( enode, False )
def menuAddExecutionNode(self):
item=self.executionTree.currentItem()
if item:
enode = item._executionNode
if isinstance( enode, brainvisa.processes.ProcessExecutionNode ) :
enode = enode._executionNode
if isinstance( enode, brainvisa.processes.SerialExecutionNode ) \
and enode.possibleChildrenProcesses :
defaultProcess = enode.possibleChildrenProcesses.keys()[0]
defaultProcessOptions = enode.possibleChildrenProcesses[defaultProcess]
child = brainvisa.processes.ProcessExecutionNode(
defaultProcess,
optional = defaultProcessOptions.get('optional', True),
selected = defaultProcessOptions.get('selected', True),
expandedInGui = defaultProcessOptions.get('expandedInGui', False)
)
enode.addChild(node = child)
def menuRemoveExecutionNode(self):
item = self.executionTree.currentItem()
parent = item.parent()
if parent:
pnode = parent._executionNode
if isinstance( pnode, brainvisa.processes.ProcessExecutionNode ) :
pnode = pnode._executionNode
if isinstance( pnode, brainvisa.processes.SerialExecutionNode ) \
and pnode.possibleChildrenProcesses :
n = pnode.childrenNames()
for k in n:
c = pnode._children[k]
if c and ((c is item._executionNode) \
or (weakref.proxy(c) is item._executionNode )) :
pnode.removeChild(k)
def defaultInlineGUI( self, parent, externalRunButton = False, container = None ):
if container is None:
container = QWidget( )
layout=QHBoxLayout()
container.setLayout(layout)
layout.setContentsMargins( 5, 5, 5, 5 )
else:
layout=container.layout()
if not externalRunButton:
self.btnRun = QToolButton(self)
self.btnRun.setDefaultAction(self.action_run)
self.btnRun.setToolButtonStyle(Qt.ToolButtonTextOnly)
layout.addWidget(self.btnRun)
self.btnRun.setMinimumWidth(90)
self.btnRun.setSizePolicy( QSizePolicy( QSizePolicy.Fixed, QSizePolicy.Fixed ) )
self.btnInterrupt = QToolButton(self)
self.btnInterrupt.setDefaultAction(self.action_interupt)
self.btnInterrupt.setToolButtonStyle(Qt.ToolButtonTextOnly)
layout.addWidget(self.btnInterrupt)
self.btnInterrupt.setMinimumWidth(90)
self.btnInterrupt.setSizePolicy( QSizePolicy( QSizePolicy.Fixed, QSizePolicy.Fixed ) )
self.btnInterrupt.setVisible(False)
self.btnInterruptStep = QToolButton(self)
self.btnInterruptStep.setDefaultAction(self.action_interupt_step)
self.btnInterruptStep.setToolButtonStyle(Qt.ToolButtonTextOnly)
layout.addWidget(self.btnInterruptStep)
self.btnInterruptStep.setMinimumWidth(90)
self.btnInterruptStep.setSizePolicy( QSizePolicy( QSizePolicy.Fixed, QSizePolicy.Fixed ) )
self.btnInterruptStep.setVisible(False)
self.btnIterate = QToolButton(self)
self.btnIterate.setDefaultAction(self.action_iterate)
self.btnIterate.setToolButtonStyle(Qt.ToolButtonTextOnly)
layout.addWidget(self.btnIterate)
self.btnIterate.setMinimumWidth(90)
self.btnIterate.setSizePolicy( QSizePolicy( QSizePolicy.Fixed, QSizePolicy.Fixed ) )
if self.process.__class__ == brainvisa.processes.IterationProcess:
self.btnIterate.setVisible(False)
if self.process.executionNode() != None and _workflow_application_model != None:
self.btnRunSomaWorkflow = QToolButton(self)
self.btnRunSomaWorkflow.setDefaultAction(self.action_run_with_sw)
self.btnRunSomaWorkflow.setToolButtonStyle(Qt.ToolButtonTextOnly)
layout.addWidget(self.btnRunSomaWorkflow)
self.btnRunSomaWorkflow.setMinimumWidth(90)
self.btnRunSomaWorkflow.setSizePolicy( QSizePolicy( QSizePolicy.Fixed, QSizePolicy.Fixed ) )
else:
self.action_run_with_sw.setVisible(False)
container.setSizePolicy( QSizePolicy( QSizePolicy.MinimumExpanding, QSizePolicy.Fixed ) )
container.setMaximumHeight( container.sizeHint().height() )
return container
def getEditor( self, parameterName ):
if self.parameterizedWidget is not None:
return self.parameterizedWidget.editors.get( parameterName )
return None
def closeEvent( self, event ):
self.cleanup()
QWidget.closeEvent( self, event )
def cleanup( self ):
self.process.cleanup()
if self.parameterizedWidget is not None:
self.parameterizedWidget.cleanup()
if self._widgetStack is not None:
for gui in self._widgetStack._children:
cleanup = getattr( gui, 'cleanup', None )
if cleanup is not None:
cleanup()
if gui is not None and sip is not None and not sip.isdeleted( gui ):
gui.deleteLater()
self._widgetStack = None
if self.executionTree is not None and QTreeWidgetItemIterator:
it = QTreeWidgetItemIterator( self.executionTree )
while it.value():
cleanup = getattr( it.value(), 'cleanup', None )
if cleanup is not None:
cleanup()
it+=1
self.executionTree = None
if neuroConfig:
neuroConfig.unregisterObject( self )
self.process.signatureChangeNotifier.remove( self.signatureChanged )
self._executionNodeLVItems.clear()
self.parametersWidget = None
self.info = None
self.process._lastResult = None
def _run(self):
self._runButton()
def _runButton( self, executionFunction=None ):
try:
try:
# disable run button when clicked to avoid several successive clicks
# it is enabled when the process starts, the label of the button switch to interrupt
self.action_run.setEnabled(False)
processView = self._checkReloadProcess()
if processView is None:
processView = self
processView.info.setText( '' )
else:
processView.info.setText( '' )
processView.warning( _t_('processes %s updated') % _t_(processView.process.name) )
processView._runningProcess = 0
processView._startCurrentProcess( executionFunction )
except:
neuroException.showException()
finally:
self.action_run.setEnabled(True)
def _run_with_soma_workflow( self, executionFunction=None ):
try:
from brainvisa.workflow import ProcessToSomaWorkflow
submission_dlg = WorkflowSubmissionDlg(self)
if submission_dlg.exec_() != QtGui.QDialog.Accepted:
return
resource_id = submission_dlg.combo_resource.currentText()
if resource_id != _workflow_application_model.current_resource_id:
_workflow_application_model.set_current_connection(resource_id)
qtdt = submission_dlg.dateTimeEdit_expiration.dateTime()
date = datetime(qtdt.date().year(), qtdt.date().month(), qtdt.date().day(),
qtdt.time().hour(), qtdt.time().minute(), qtdt.time().second())
queue = unicode(submission_dlg.combo_queue.currentText()).encode('utf-8')
if queue == "default queue": queue = None
input_file_processing = submission_dlg.combo_in_files.currentText()
output_file_processing = submission_dlg.combo_out_files.currentText()
brainvisa_cmd = [ 'python', '-m', 'brainvisa.axon.runprocess' ]
self.readUserValues()
builtin_db = []
if input_file_processing == ProcessToSomaWorkflow.BV_DB_SHARED_PATH:
for db_setting in neuroConfig.dataPath:
if db_setting.builtin:
uuid = db_setting.expert_settings.uuid
if uuid:
builtin_db.append(uuid)
else:
print "warning ! db " + repr(db_setting.directory) + " has no uuid."
ptowf = ProcessToSomaWorkflow(self.process,
input_file_processing=input_file_processing,
output_file_processing=output_file_processing,
brainvisa_cmd=brainvisa_cmd,
brainvisa_db=builtin_db)
workflow = ptowf.doIt()
name = unicode(submission_dlg.lineedit_wf_name.text())
if name == "":
if workflow.name != None:
name = SomaWorkflowWidget.brainvisa_code + workflow.name
else:
name = SomaWorkflowWidget.brainvisa_code
else:
name = SomaWorkflowWidget.brainvisa_code + name
#store the process in workflow.user_storage
serialized_process = StringIO.StringIO()
event = self.createProcessExecutionEvent()
event.save(serialized_process)
to_store = [SomaWorkflowWidget.brainvisa_code, serialized_process.getvalue()]
workflow.user_storage = to_store
serialized_process.close()
_workflow_application_model.add_workflow(
soma_workflow.gui.workflowGui.NOT_SUBMITTED_WF_ID,
datetime.now() + timedelta(days=5),
name,
soma_workflow.constants.WORKFLOW_NOT_STARTED,
workflow)
(wf_id, resource_id) = _mainWindow.sw_widget.submit_workflow(date,
name,
queue)
if wf_id == None:
return
view = SomaWorkflowProcessView(
_workflow_application_model,
wf_id,
resource_id,
process=self.process,
parent=_mainWindow)
view.setAttribute( QtCore.Qt.WA_DeleteOnClose )
view.show()
except:
neuroException.showException()
finally:
self.action_run_with_sw.setEnabled(True)
def _interruptButton(self):
if self._running:
try:
self._setInterruptionRequest( brainvisa.processes.ExecutionContext.UserInterruption() )
except:
neuroException.showException()
def _interruptStepButton( self, executionFunction=None ):
if self._running:
self._setInterruptionRequest( brainvisa.processes.ExecutionContext.UserInterruptionStep() )
def _checkReloadProcess( self ):
self.readUserValues()
reload = False
for p in self.process.allProcesses():
pp = brainvisa.processes.getProcess( p )
if pp is not p and pp is not p.__class__:
reload = True
break
result = None
if reload:
eNode = getattr( self.process, '_executionNode', None )
if eNode is None:
# Get current process arguments values
event = ProcessExecutionEvent()
event.setProcess( self.process )
# Forget about old process
self.process.signatureChangeNotifier.remove( self.signatureChanged )
self.eraseSignatureWidgets()
# Care about new process
self.process = brainvisa.processes.getProcessInstanceFromProcessEvent( event )
self.process.signatureChangeNotifier.add( self.signatureChanged )
procdoc = brainvisa.processes.readProcdoc( self.process )
documentation = procdoc.get( neuroConfig.language )
if documentation is None:
documentation = procdoc.get( 'en', {} )
self.createSignatureWidgets( documentation )
result = self
else:
result = self.clone()
self.deleteLater()
return result
def _startCurrentProcess( self, executionFunction ):
#Remove icon from all ListView items
for item in self._executionNodeLVItems.values():
item.setIcon( 0, self.pixNone )
self._lastProcessRaisedException = None
try:
self._startProcess( self.process, executionFunction )
self._running = True
except Exception, e:
self._lastProcessRaisedException = e
neuroException.showException()
def _write( self, html ):
_mainThreadActions.push( self._appendInfo, html )
def _appendInfo( self, msg ):
# the tags font are here just to avoid display problems in QTextEdit with non html content (color staying red after an error for example)
self.info.append( ""+msg+"" )
def _processStarted( self ):
if self._depth() == 1:
if self.movie is not None:
_mainThreadActions.push( self.movie.start )
_mainThreadActions.push(self.action_run.setEnabled, False)
_mainThreadActions.push(self.action_interupt.setVisible, True)
if self.process.__class__ == brainvisa.processes.IterationProcess:
_mainThreadActions.push(self.action_interupt_step.setVisible, True)
if self.btnRun != None:
_mainThreadActions.push(self.btnRun.setVisible, False)
_mainThreadActions.push(self.btnInterrupt.setVisible, True)
if self.process.__class__ == brainvisa.processes.IterationProcess:
_mainThreadActions.push(self.btnInterruptStep.setVisible, True)
#Adds an icon on the ListViewItem corresponding to the current process
# if any
p = self._currentProcess()
eNodeItem = self._executionNodeLVItems.get( p )
if eNodeItem is not None:
_mainThreadActions.push( eNodeItem.setIcon, 0, self.pixInProcess )
ExecutionContextGUI._processStarted( self )
def _processFinished( self, result ):
self.process._lastResult = result
ExecutionContextGUI._processFinished( self, result )
#Remove icon from the ListViewItem corresponding to the current process
# if any
p = self._currentProcess()
eNodeItem = self._executionNodeLVItems.get( p )
if eNodeItem is not None:
if self._lastProcessRaisedException:
_mainThreadActions.push( eNodeItem.setIcon, 0, self.pixProcessError )
else:
_mainThreadActions.push( eNodeItem.setIcon, 0,
self.pixProcessFinished )
if self._depth() == 1:
if self.movie is not None:
_mainThreadActions.push( self.movie.stop )
_mainThreadActions.push( self.action_run.setEnabled, True)
_mainThreadActions.push(self.action_interupt.setVisible, False)
if self.process.__class__ == brainvisa.processes.IterationProcess:
_mainThreadActions.push(self.action_interupt_step.setVisible, False)
if self.btnRun != None:
_mainThreadActions.push( self.btnRun.setVisible, True)
_mainThreadActions.push(self.btnInterrupt.setVisible, False)
if self.process.__class__ == brainvisa.processes.IterationProcess:
_mainThreadActions.push(self.btnInterruptStep.setVisible, False)
_mainThreadActions.push( self._checkReadable )
self._running = False
else:
_mainThreadActions.push( self._checkReadable )
def system( self, *args, **kwargs ):
ret = apply( ExecutionContextGUI.system, (self,) + args, kwargs )
_mainThreadActions.push( self._checkReadable )
return ret
def _checkReadable( self ):
if self.parameterizedWidget is not None:
self.parameterizedWidget.checkReadable()
if self.executionTree is not None:
for gui in self._widgetStack._children:
checkReadable = getattr( gui, '_checkReadable', None )
if checkReadable is not None:
checkReadable()
def dialog( self, modal, message, signature, *buttons ):
return _mainThreadActions.call( UserDialog, self, modal,
message, signature, buttons )
#def showCallScript( self ):
#text = "defaultContext().runProcess( '" + self.process.id() + "'"
#text2 = ''
#for n in self.process.signature.keys():
#if not self.process.isDefault( n ):
#text2 += ' ' + n + ' = ' + repr( self.editors[ n ].getValue() ) + ',\n'
#if text2:
#text += ',\n' + text2
#text += ')\n'
#txt = TextEditor( text )
#txt.resize( 800, 600 )
#txt.show()
def executionNodeRemoveChild( self, item, eNode, key = None, childNode = None ):
childItem = getattr(childNode, '_guiItem', None)
if childItem :
# Remove matching child item
for i in xrange(item.childCount()):
c = item.child(i)
if (childItem == c) or (childItem is weakref.proxy(c)):
item.takeChild(i)
break
func = getattr(item, 'hasChildren', None)
# Update children indicator for the current item
if func and func():
item.setChildIndicatorPolicy(item.DontShowIndicator)
def executionNodeAddChild( self, item, eNode, key = None, childNode = None, previous = None ):
newItem = None
if not getattr(childNode, '_guiItem', None) :
if isinstance( childNode, brainvisa.processes.ProcessExecutionNode ):
en = childNode._executionNode
if en is None:
en = childNode
else:
en = childNode
if eNode is not childNode \
and ( isinstance( eNode, brainvisa.processes.SelectionExecutionNode ) \
or ( isinstance( eNode, brainvisa.processes.ProcessExecutionNode ) \
and isinstance( eNode._executionNode, brainvisa.processes.SelectionExecutionNode ) ) ):
itemType = "radio"
elif childNode._optional:
itemType = "check"
else:
itemType = None
# Try to insert node at the matching index
if key :
index = eNode._children.index( key )
else :
index = None
name = childNode.name()
newItem = NodeCheckListItem( childNode, item, index, _t_( name ), itemType, read_only = self.read_only )
if isinstance( childNode, weakref.ProxyType ):
newItem._executionNode = childNode
else:
newItem._executionNode = weakref.proxy( childNode )
if isinstance( newItem, weakref.ProxyType ):
childNode._guiItem = newItem
else:
childNode._guiItem = weakref.proxy(newItem)
# Add callback to warn about child add and remove
beforeChildRemovedCallback = getattr(en, 'beforeChildRemoved', None)
if beforeChildRemovedCallback:
beforeChildRemovedCallback.add(
soma.functiontools.partial(
self.__class__.executionNodeRemoveChild, weakref.proxy( self ),
newItem ) )
afterChildAddedCallback = getattr(en, 'afterChildAdded', None)
if afterChildAddedCallback :
afterChildAddedCallback.add(
soma.functiontools.partial(
self.__class__.executionNodeAddChild, weakref.proxy( self ),
newItem) )
if en.hasChildren():
newItem.setChildIndicatorPolicy(newItem.ShowIndicator)
#newItem.setExpandable( en.hasChildren() )
if isinstance( childNode, brainvisa.processes.ProcessExecutionNode ):
self._executionNodeLVItems[ childNode._process ] = newItem
func = getattr(item, 'hasChildren', None)
# Update children indicator for the current item
if func and func():
item.setChildIndicatorPolicy(item.ShowIndicator)
if self._depth():
p = self._currentProcess()
eNodeItem = self._executionNodeLVItems.get( p )
if eNodeItem is not None:
eNodeItem.setIcon( 0, self.pixInProcess )
return newItem
def executionNodeSelected( self, item, previous ):
if item is not None:
if (getattr(item, "_guiId", None)) is not None:
self._widgetStack.setCurrentIndex( item._guiId )
else:
gui = item._executionNode.gui( self._widgetStack, processView=self )
if gui is not None:
item._guiId=self._widgetStack.addWidget( gui )
self._widgetStack._children.append( gui )
self._guiId += 1
else:
self._emptyWidget = QWidget( self._widgetStack )
item._guiId=self._widgetStack.addWidget( self._emptyWidget )
self._widgetStack.setCurrentIndex( item._guiId )
item.currentItemChanged(True)
if previous is not None:
previous.currentItemChanged(False)
# Trick to have correct slider
# size = self.size()
#self.resize( size.width()+1, size.height() )
#qApp.processEvents()
#self.resize( size )
def executionNodeClicked( self, item, column ):
item.itemClicked()
def _executionNodeExpanded( self, item, eNodeAndChildren=None ):
if item is not None and getattr( item, '_notExpandedYet', True ):
item._notExpandedYet = False
previous = None
if eNodeAndChildren is None:
eNode = item._executionNode
eNodeChildren = (eNode.child( k ) for k in eNode.childrenNames())
else:
eNode, eNodeChildren = eNodeAndChildren
for childNode in eNodeChildren:
previous = self.executionNodeAddChild( item, eNode,
childNode = childNode )
if childNode._expandedInGui:
previous.setExpanded( True )
def _executionNodeActivated(self, item):
if getattr(item, "activate", None):
item.activate()
def createWorkflow( self ):
from brainvisa.workflow import ProcessToSomaWorkflow
class Options( HasSignature ):
signature = SomaSignature(
'output',
SomaFileName,
dict( doc='Name of the output workflow file.' ),
'input_file_processing',
SomaChoice( ( _t_( ProcessToSomaWorkflow.NO_FILE_PROCESSING ), 0 ),
( _t_( ProcessToSomaWorkflow.FILE_TRANSFER ), 1 ),
( _t_( ProcessToSomaWorkflow.SHARED_RESOURCE_PATH ), 2 ),
( _t_( ProcessToSomaWorkflow.BV_DB_SHARED_PATH ), 3 )),
dict( defaultValue=0 ),
'output_file_processing',
SomaChoice( ( _t_( ProcessToSomaWorkflow.NO_FILE_PROCESSING ), 0 ),
( _t_( ProcessToSomaWorkflow.FILE_TRANSFER ), 1 ),
( _t_( ProcessToSomaWorkflow.SHARED_RESOURCE_PATH ), 2 )),
dict( defaultValue=0 )
)
options = Options()
if ApplicationQt4GUI().edit( options ):
input_file_processing = ProcessToSomaWorkflow.NO_FILE_PROCESSING
if options.input_file_processing == 1:
input_file_processing = ProcessToSomaWorkflow.FILE_TRANSFER
if options.input_file_processing == 2:
input_file_processing = ProcessToSomaWorkflow.SHARED_RESOURCE_PATH
if options.input_file_processing == 3:
input_file_processing = ProcessToSomaWorkflow.BV_DB_SHARED_PATH
output_file_processing = ProcessToSomaWorkflow.NO_FILE_PROCESSING
if options.output_file_processing == 1:
output_file_processing = ProcessToSomaWorkflow.FILE_TRANSFER
if options.output_file_processing == 2:
output_file_processing = ProcessToSomaWorkflow.SHARED_RESOURCE_PATH
builtin_db = []
if input_file_processing == ProcessToSomaWorkflow.BV_DB_SHARED_PATH:
for db_setting in neuroConfig.dataPath:
if db_setting.builtin:
uuid = db_setting.expert_settings.uuid
if uuid:
builtin_db.append(uuid)
else:
print "warning ! db " + repr(db_setting.directory) + " has no uuid."
ptowf = ProcessToSomaWorkflow(self.process,
options.output,
input_file_processing = input_file_processing,
output_file_processing = output_file_processing,
brainvisa_db=builtin_db)
ptowf.doIt()
def _iterateButton( self ):
self.readUserValues()
self._iterationDialog = IterationDialog( self, self.process, self )
self.connect( self._iterationDialog, SIGNAL( 'accept' ),
self._iterateAccept )
self._iterationDialog.show()
def _iterateAccept( self ):
try:
params = self._iterationDialog.getLists()
processes = self.process._iterate( **params )
iterationProcess = brainvisa.processes.IterationProcess( self.process.name+" iteration", processes )
showProcess( iterationProcess )
except:
neuroException.showException()
self._iterationDialog.show()
def setValue( self, name, value ):
setattr( self.process, name, value )
def readUserValues( self ):
if self.parameterizedWidget is not None:
self.parameterizedWidget.readUserValues()
if self._widgetStack is not None:
for pw in self._widgetStack._children:
ruv = getattr( pw, 'readUserValues', None )
if ruv is None:
ruv = getattr( getattr( pw, 'parameterizedWidget', None ), 'readUserValues', None )
if ruv is not None:
ruv()
def createProcessExecutionEvent( self ):
event = super( ProcessView, self ).createProcessExecutionEvent()
mainThreadActions().call( event.setWindow, self )
return event
def saveAs( self ):
minf = getattr( self.process, '_savedAs', '' )
# workaround a bug in PyQt ? Param 5 doesn't work; try to use kwargs
import sipconfig
if sipconfig.Configuration().sip_version >= 0x040a00:
minf = unicode( QFileDialog.getSaveFileName( None, 'Open a process file', minf, 'BrainVISA process (*.bvproc);;All files (*)', options=QFileDialog.DontUseNativeDialog ) )
else:
minf = unicode( QFileDialog.getSaveFileName( None, 'Open a process file', minf, 'BrainVISA process (*.bvproc);;All files (*)', None, QFileDialog.DontUseNativeDialog ) )
if minf:
if not minf.endswith( '.bvproc' ):
minf += '.bvproc'
self.readUserValues()
event = self.createProcessExecutionEvent()
self.process._savedAs = minf
event.save( minf )
def clone( self ):
self.readUserValues()
clone = brainvisa.processes.getProcessInstanceFromProcessEvent( self.createProcessExecutionEvent() )
return showProcess( clone )
@staticmethod
def open():
import sipconfig
if sipconfig.Configuration().sip_version >= 0x040a00:
minf = unicode( QFileDialog.getOpenFileName( None,
_t_( 'Open a process file' ), '', 'BrainVISA process (*.bvproc);;All files (*)', options=QFileDialog.DontUseNativeDialog ))
else:
minf = unicode( QFileDialog.getOpenFileName( None,
_t_( 'Open a process file' ), '', 'BrainVISA process (*.bvproc);;All files (*)', None, QFileDialog.DontUseNativeDialog ))
if minf:
showProcess( brainvisa.processes.getProcessInstance( minf ) )
#----------------------------------------------------------------------------
def showProcess( process, *args, **kwargs):
'''Opens a process window and set the corresponding arguments'''
global _mainWindow
view=None
try:
process = brainvisa.processes.getProcessInstance( process )
if process is None:
raise RuntimeError( neuroException.HTMLMessage(_t_( 'Invalid process %s' ) % ( str(process), )) )
for i in xrange( len( args ) ):
k, p = process.signature.items()[ i ]
process.setValue( k, args[ i ] )
for k, v in kwargs.items():
process.setValue( k, v )
gui = getattr( process, 'overrideGUI', None )
if gui is None:
view = ProcessView( process )
else:
view = gui()
windowGeometry = getattr( process, '_windowGeometry', None )
if windowGeometry is not None:
view.move( *windowGeometry[ 'position' ] )
view.resize( *windowGeometry[ 'size' ] )
view.show()
except: # an exception can occur if the process is reloaded and an error has been introduced in its code.
neuroException.showException()
return view
#----------------------------------------------------------------------------
class IterationDialog( QDialog ):
def __init__( self, parent, parameterized, context ):
QDialog.__init__( self, parent )
self.setModal(True)
layout = QVBoxLayout( )
self.setLayout(layout)
layout.setContentsMargins( 10, 10, 10, 10 )
self.setWindowTitle( _t_('%s iteration') % unicode( parent.windowTitle() ) )
params = []
for ( n, p ) in parameterized.signature.items():
if neuroConfig.userLevel >= p.userLevel:
params += [ n, neuroData.ListOf( p ) ]
self.parameterized = brainvisa.processes.Parameterized( neuroData.Signature( *params ) )
for n in self.parameterized.signature.keys():
setattr( self.parameterized, n, None )
self.parameterizedWidget = ParameterizedWidget( self.parameterized, None )
layout.addWidget(self.parameterizedWidget)
w=QWidget()
hb = QHBoxLayout( )
w.setLayout(hb)
layout.addWidget(w)
hb.setContentsMargins( 5, 5, 5, 5 )
w.setSizePolicy( QSizePolicy( QSizePolicy.Expanding, QSizePolicy.Fixed ) )
btn = QPushButton( _t_('Ok') )
hb.addWidget(btn)
btn.setSizePolicy( QSizePolicy( QSizePolicy.Fixed, QSizePolicy.Fixed ) )
self.connect( btn, SIGNAL( 'clicked()' ), self, SLOT( 'accept()' ) )
btn = QPushButton( _t_('Cancel') )
hb.addWidget(btn)
btn.setSizePolicy( QSizePolicy( QSizePolicy.Fixed, QSizePolicy.Fixed ) )
self.connect( btn, SIGNAL( 'clicked()' ), self, SLOT( 'reject()' ) )
def getLists( self ):
result = {}
for n in self.parameterized.signature.keys():
result[ n ] = getattr( self.parameterized, n, None )
return result
def accept( self ):
QDialog.accept( self )
self.parameterizedWidget.readUserValues()
self.emit( SIGNAL( 'accept' ) )
#----------------------------------------------------------------------------
class UserDialog( QDialog ):
def __init__( self, parent, modal, message, signature, buttons ):
flags = Qt.Window | Qt.Dialog
QDialog.__init__( self, parent, flags )
self.setWindowModality(Qt.WindowModal)
self.setAttribute(Qt.WA_DeleteOnClose, True)
layout = QVBoxLayout( )
self.setLayout(layout)
layout.setContentsMargins( 10, 10, 10, 10 )
layout.setSpacing( 5 )
self.condition = None
self.signature = signature
self._currentDirectory = None
if message is not None:
lab = QLabel( unicode(message) )
lab.setWordWrap( True )
layout.addWidget(lab)
lab.setSizePolicy( QSizePolicy( QSizePolicy.Preferred, QSizePolicy.Fixed ) )
self.editors = {}
if signature is not None:
sv = WidgetScrollV( )
layout.addWidget(sv)
first = None
svWidget=QWidget()
svWidgetLayout=QVBoxLayout()
svWidget.setLayout(svWidgetLayout)
for ( k, p ) in self.signature.items():
hb = QHBoxLayout( )
svWidgetLayout.addLayout(hb)
l=QLabel( k + ': ' )
hb.addWidget(l)
e = p.editor( None, k, self )
hb.addWidget(e)
self.editors[ k ] = e
if first is None: first = e
sv.setWidget(svWidget)
self.group1 = QButtonGroup( )
group1Widget=QWidget()
group1Layout=QHBoxLayout()
group1Widget.setLayout(group1Layout)
layout.addWidget(group1Widget)
self._actions = {}
self.group2 = QButtonGroup( )
group2Widget=QWidget()
group2Layout=QHBoxLayout()
group2Widget.setLayout(group2Layout)
layout.addWidget(group2Widget)
deleteGroup1 = 1
i=0
for b in buttons:
if type( b ) in ( types.TupleType, types.ListType ):
caption, action = b
btn = QPushButton( unicode( caption ) )
group1Layout.addWidget(btn)
self.group1.addButton(btn, i)
btn.setSizePolicy( QSizePolicy( QSizePolicy.Fixed, QSizePolicy.Fixed ) )
self._actions[ self.group1.id( btn ) ] = action
deleteGroup1 = 0
else:
btn = QPushButton( unicode( b ) )
group2Layout.addWidget(btn)
self.group2.addButton(btn, i)
btn.setSizePolicy( QSizePolicy( QSizePolicy.Fixed, QSizePolicy.Fixed ) )
i+=1
if deleteGroup1:
group1Widget.close( )
else:
group1Widget.setSizePolicy( QSizePolicy( QSizePolicy.Minimum, QSizePolicy.Fixed ) )
self.connect( self.group1, SIGNAL( 'buttonClicked(int)' ), self._doAction )
group2Widget.setSizePolicy( QSizePolicy( QSizePolicy.Minimum, QSizePolicy.Fixed ) )
self.connect( self.group2, SIGNAL( 'buttonClicked(int)' ), self.select )
def select( self, value ):
for e in self.editors.values():
e.checkValue()
self._result = value
self.done( 1 )
def setValue( self, name, value ):
mainThreadActions().push( self.editors[ name ].setValue, value )
def getValue( self, name ):
return mainThreadActions().call( self.editors[ name ].getValue )
def _doAction( self, index ):
self._actions[ index ]( self )
def call( self ):
if neuroConfig.gui:
self._result = None
mainThreadActions().call( self.show )
mainThreadActions().call( self.exec_ )
result = self._result
del self._result
return result
return -1
#----------------------------------------------------------------------------
class ProcessEdit( QDialog ):
def __init__( self, process ):
QDialog.__init__( self, None )
layout = QVBoxLayout( )
self.setLayout(layout)
layout.setContentsMargins( 10, 10, 10, 10 )
layout.setSpacing( 5 )
neuroConfig.registerObject( self )
self.process = process
t = _t_(process.name)
self.setWindowTitle( t )
spl = QSplitter( Qt.Vertical )
layout.addWidget(spl)
w=QWidget(spl)
vb = QVBoxLayout( )
w.setLayout(vb)
self.labName = QLabel( '' + t + '
', spl )
vb.addWidget(self.labName)
hb = QHBoxLayout( )
vb.addLayout(hb)
l=QLabel( _t_('HTML Path')+': ' )
hb.addWidget(l)
self.leHTMLPath = QLineEdit( )
hb.addWidget(self.leHTMLPath)
hb = QHBoxLayout( )
vb.addLayout(hb)
l=QLabel( _t_('Language')+': ' )
hb.addWidget(l)
self.cmbLanguage = QComboBox( )
self.cmbLanguage.setEditable(False)
hb.addWidget(self.cmbLanguage)
for i in neuroConfig._languages:
self.cmbLanguage.addItem( i )
if i == neuroConfig.language:
self.cmbLanguage.setCurrentIndex( self.cmbLanguage.count() - 1 )
self.connect( self.cmbLanguage, SIGNAL( 'activated(int)' ), self.changeLanguage )
l=QLabel( _t_('Short description') + ':' )
vb.addWidget(l)
self.mleShort = QTextEdit( )
self.mleShort.setAcceptRichText(False)
vb.addWidget(self.mleShort)
w=QWidget(spl)
vb = QVBoxLayout()
w.setLayout(vb)
#spl.setLayout(vb)
hb = QHBoxLayout( )
vb.addLayout(hb)
l=QLabel( _t_('Parameter')+': ' )
hb.addWidget(l)
self.cmbParameter = QComboBox( )
self.cmbParameter.setEditable(False)
hb.addWidget(self.cmbParameter)
stack = QStackedWidget( )
vb.addWidget(stack)
self.mleParameters = {}
for n in self.process.signature.keys():
mle = QTextEdit( )
mle.setAcceptRichText(False)
vb.addWidget(mle)
stack.addWidget( mle )
self.mleParameters[ self.cmbParameter.count() ] = mle
self.cmbParameter.addItem( n )
self.connect( self.cmbParameter, SIGNAL( 'activated(int)' ), stack, SLOT( 'setCurrentIndex(int)' ) )
stack.setCurrentIndex( 0 )
w=QWidget(spl)
vb = QVBoxLayout( )
w.setLayout(vb)
l=QLabel( _t_('Long description') + ':' )
vb.addWidget(l)
self.mleLong = QTextEdit( )
self.mleLong.setAcceptRichText(False)
vb.addWidget(self.mleLong)
self.readDocumentation()
self.setLanguage( unicode( self.cmbLanguage.currentText() ) )
w=QWidget()
hb = QHBoxLayout( )
w.setLayout(hb)
vb.addWidget(w)
hb.setContentsMargins( 5, 5, 5, 5 )
w.setSizePolicy( QSizePolicy( QSizePolicy.Expanding, QSizePolicy.Fixed ) )
btn = QPushButton( _t_('apply') )
hb.addWidget(btn)
btn.setSizePolicy( QSizePolicy( QSizePolicy.Fixed, QSizePolicy.Fixed ) )
self.connect( btn, SIGNAL( 'clicked()' ), self.applyChanges )
btn = QPushButton( _t_('Ok') )
hb.addWidget(btn)
btn.setSizePolicy( QSizePolicy( QSizePolicy.Fixed, QSizePolicy.Fixed ) )
self.connect( btn, SIGNAL( 'clicked()' ), self, SLOT( 'accept()' ) )
btn = QPushButton( _t_('Cancel') )
hb.addWidget(btn)
btn.setSizePolicy( QSizePolicy( QSizePolicy.Fixed, QSizePolicy.Fixed ) )
self.connect( btn, SIGNAL( 'clicked()' ), self, SLOT( 'reject()' ) )
self.resize( 800, 600 )
def closeEvent( self, event ):
neuroConfig.unregisterObject( self )
QWidget.closeEvent(self, event)
def readDocumentation( self ):
self.documentation = brainvisa.processes.readProcdoc( self.process )
def writeDocumentation( self ):
brainvisa.processes.procdocToXHTML( self.documentation )
self.setLanguage( self.language )
brainvisa.processes.writeProcdoc( self.process, self.documentation )
def setLanguage( self, lang ):
self.leHTMLPath.setText(XHTML.html(self.documentation.get( 'htmlPath', '' )) )
self.language = lang
d = self.documentation.get( lang, {} )
self.mleShort.setPlainText( XHTML.html( d.get( 'short', '' ) ) )
self.mleLong.setPlainText( XHTML.html( d.get( 'long', '' ) ) )
p = d.get( 'parameters', {} )
for i,j in self.mleParameters.items():
j.setPlainText( XHTML.html( p.get( unicode( self.cmbParameter.itemText( i ) ), '' ) ) )
def saveLanguage( self ):
d = {}
d[ 'short' ] = self.escapeXMLEntities( unicode( self.mleShort.toPlainText() ) )
d[ 'long' ] = self.escapeXMLEntities( unicode( self.mleLong.toPlainText() ) )
d[ 'parameters' ] = p = {}
for i,j in self.mleParameters.items():
p[ unicode( self.cmbParameter.itemText( i ) ) ] = self.escapeXMLEntities( unicode( j.toPlainText() ) )
self.documentation[ self.language ] = d
htmlPath = unicode( self.leHTMLPath.text() )
if htmlPath:
self.documentation[ 'htmlPath' ] = htmlPath
else:
try:
del self.documentation[ 'htmlPath' ]
except KeyError:
pass
@staticmethod
def escapeXMLEntities( s ):
return re.sub( r'&([a-z]+);', lambda m: '&'+m.group(1)+';', s )
def changeLanguage( self ):
self.saveLanguage()
self.setLanguage( unicode( self.cmbLanguage.currentText() ) )
def applyChanges( self ):
self.saveLanguage()
self.writeDocumentation()
brainvisa.processes.generateHTMLProcessesDocumentation( self.process )
mainWindow().info.reload()
def accept( self ):
self.applyChanges()
QDialog.accept( self )
#----------------------------------------------------------------------------
class ProcessSelectionWidget( QMainWindow ):
"""
This widget is the main window in brainvisa.
Provides navigation among processes.
"""
# Soma-Workflow widget for workflow execution on various computing resources
# SomaWorkflowWidget
sw_widget = None
sw_mini_widget = None
def __init__( self ):
QMainWindow.__init__( self )
if getattr( ProcessSelectionWidget, '_pixmapCache', None ) is None:
ProcessSelectionWidget._pixmapCache = {}
for file in ( 'icon_process_0.png', 'icon_process_1.png', 'icon_process_2.png', 'icon_process_3.png', 'folder.png' ):
fullPath = os.path.join( neuroConfig.iconPath, file )
ProcessSelectionWidget._pixmapCache[ fullPath ] = QIcon( fullPath )
centralWidget=QWidget()
self.setCentralWidget(centralWidget)
self.dock_doc = QDockWidget("Documentation", self)
self.dock_doc.setObjectName("documentation_dock")
self.dock_doc.toggleViewAction().setText(_t_("Documentation"))
self.dock_doc.setAllowedAreas(QtCore.Qt.LeftDockWidgetArea | QtCore.Qt.RightDockWidgetArea)
self.dock_doc.show()
self.addDockWidget(Qt.RightDockWidgetArea, self.dock_doc)
self.dock_sw = QDockWidget("Execution", self)
self.dock_sw.setObjectName("execution_dock")
self.dock_sw.toggleViewAction().setText(_t_("Workflow execution"))
self.dock_sw.setAllowedAreas(QtCore.Qt.BottomDockWidgetArea | QtCore.Qt.TopDockWidgetArea)
if _workflow_application_model != None:
self.sw_widget = SomaWorkflowWidget(_workflow_application_model,
computing_resource=socket.gethostname(),
parent=None)
self.sw_widget.setWindowTitle(_t_("Workflow execution"))
self.sw_mini_widget = SomaWorkflowMiniWidget(_workflow_application_model,
self.sw_widget,
self.dock_sw)
self.dock_sw.setWidget(self.sw_mini_widget)
self.dock_sw.hide()
self.addDockWidget(Qt.BottomDockWidgetArea, self.dock_sw)
else:
self.dock_sw.hide()
self.dock_sw.toggleViewAction().setVisible(False)
self.sw_widget = None
self.setCorner(QtCore.Qt.BottomRightCorner, QtCore.Qt.RightDockWidgetArea)
self.setCorner(QtCore.Qt.BottomLeftCorner, QtCore.Qt.LeftDockWidgetArea)
self.setCorner(QtCore.Qt.TopLeftCorner, QtCore.Qt.LeftDockWidgetArea)
self.setCorner(QtCore.Qt.TopRightCorner, QtCore.Qt.RightDockWidgetArea)
# Menu setup
menu = self.menuBar()
addBrainVISAMenu( self, menu )
neuroConfigGUI.addSupportMenu( self, menu )
view_menu = menu.addMenu(_t_("&View"))
view_menu.addAction(self.dock_doc.toggleViewAction())
view_menu.addAction(self.dock_sw.toggleViewAction())
view_menu.addAction(close_viewers_action(self))
# central widget layout
layout=QVBoxLayout()
centralWidget.setLayout(layout)
layout.setContentsMargins( 10, 10, 10, 10 )
# processTrees and the search box
w=QWidget(self)
layout.addWidget(w)
vb = QVBoxLayout()
vb.setContentsMargins( 0, 0, 0, 0 )
w.setLayout(vb)
self.currentProcessId = None
self.processTrees=ProcessTreesWidget()
self.processTrees.setSizePolicy( QSizePolicy( QSizePolicy.Preferred,
QSizePolicy.Preferred ) )
QObject.connect(self.processTrees, SIGNAL('selectionChanged'), self.itemSelected )
QObject.connect(self.processTrees, SIGNAL('doubleClicked'), self.openProcess )
QObject.connect(self.processTrees, SIGNAL('openProcess'), self.openProcess )
QObject.connect(self.processTrees, SIGNAL('editProcess'), self.editProcess )
QObject.connect(self.processTrees, SIGNAL('iterateProcess'), self.iterateProcess )
# the hacked search box
p = os.path.join( os.path.dirname( __file__ ), 'searchbox.ui' )
self.searchbox = QWidget() # for PySide/PyQt compat
self.searchbox = loadUi(p, self.searchbox)
self.searchboxSearchB = self.searchbox.BV_search
self.matchedProcs = []
self.searchboxResetSearchB = self.searchbox.BV_resetsearch
self.searchboxLineEdit = self.searchbox.BV_searchlineedit
self._continueSearching = 0
QObject.connect(self.searchboxSearchB, SIGNAL('clicked()'), self.buttonSearch)
QObject.connect(self.searchboxResetSearchB, SIGNAL('clicked()'), self.resetSearch )
vb.addWidget(self.processTrees)
vb.addWidget(self.searchbox)
# right dock : documentation panel
self.info = HTMLBrowser(self)
self.info.setSizePolicy(QSizePolicy(QSizePolicy.Expanding,
QSizePolicy.Expanding))
self.dock_doc.setWidget(self.info)
self.btnOpen = QPushButton( _t_('Open') )
self.btnEdit = None
self.updateList()
# try to start with a doc opened
self.info.home()
ds = qApp.desktop().size()
self.resize( min( 1200, ds.width() ), min( 800, ds.height() ) )
state_path = os.path.join(neuroConfig.homeBrainVISADir, "main_window_state.bin")
if os.path.exists(state_path):
state_file = QtCore.QFile(state_path)
state_file.open(QtCore.QIODevice.ReadOnly)
state = state_file.readAll()
state_file.close()
self.restoreState(state, 1)
def keyPressEvent(self, keyEvent):
if (keyEvent.matches(QKeySequence.Find) or keyEvent.matches(QKeySequence.FindNext) ):
if (self.searchboxLineEdit.text() == ""):
self.info.browser.keyPressEvent(keyEvent)
else:
self.buttonSearch()
elif ( (keyEvent.key() == Qt.Key_W) and (keyEvent.modifiers() == Qt.ControlModifier)):
self.info.openWeb()
else:
QWidget.keyPressEvent(self, keyEvent)
def buttonSearch(self):
"""
Called when user click on search / next button.
The text written in the search box is searched in tree leaves names (processes).
The first item found which name contains the searched string becomes selected. If the user click another time on the search / next button, next item is searched...
"""
# new search
if not self.matchedProcs :
# searched string
s = unicode(self.searchboxLineEdit.text()).lower()
if s == "":
self.matchedProcs = None
else:
# search for items which name contains the string -> generator
self.matchedProcs = self.processTrees.findItem(s)
# next search
if self.matchedProcs:
try:# an exception will occur when there is no more items
item=self.matchedProcs.next()
self.searchboxLineEdit.setEnabled(False)
self.searchboxSearchB.setText( 'next' )
# self.searchboxSearchB.setShortcut( QKeySequence.FindNext )
except:
self.resetSearch()
def resetSearch(self):
"""
Called at the end of a search or when the user click on reset button.
"""
self.matchedProcs = None
self.searchboxSearchB.setText('search')
# self.searchboxSearchB.setShortcut( QKeySequence.Find )
self.searchboxLineEdit.setEnabled(True)
self.searchboxLineEdit.setText("")
def itemSelected( self, item ):
"""
Called when a tree item becomes selected. currentProcessId is updated and associated documentation is shown.
:param item: the newly selected item :py:class:`ProcessTree.Item`
"""
if item:
if item.isLeaf():
processId = item.id
self.currentProcessId = processId
self.btnOpen.setEnabled( 1 )
documentation = brainvisa.processes.readProcdoc( self.currentProcessId )
source = brainvisa.processes.getHTMLFileName( self.currentProcessId )
if os.path.exists( source ):
self.info.setSource( source )
else:
self.info.setText( '' )
if self.btnEdit is not None: self.btnEdit.setEnabled( 1 )
else:
self.currentProcessId = None
self.btnOpen.setEnabled( 0 )
if self.btnEdit is not None: self.btnEdit.setEnabled( 0 )
# Construct categroy HTML documentation file name
self.info.showCategoryDocumentation( item.id )
else:
self.info.setText( '' )
def openProcess( self, item=None ):
"""
Called to open current process.
If the process is not given, selected process in current tree is opened.
:param item: the process to open. :py:class:`ProcessTree.Item`
"""
processId=None
if item is not None: # open given item
if item.isLeaf():
processId=item.id
showProcess(processId)
else: # if it is not given (open button), open selected item in current process tree
item=self.processTrees.treeStack.currentWidget().currentItem()
if item is not None:
item=item.model
if item.isLeaf():
processId=item.id
showProcess(processId)
if processId != self.currentProcessId:
self.itemSelected(item)
def editProcess( self, item=None ):
processId=None
if item is not None:
if item.isLeaf():
processId=item.id
else:
processId=self.currentProcessId
win = ProcessEdit( brainvisa.processes.getProcessInstance( processId ) )
win.show()
def iterateProcess( self, item=None ):
processId=None
if item is not None:
if item.isLeaf():
processId=item.id
else:
processId=self.currentProcessId
self.currentProcess=brainvisa.processes.getProcessInstance(processId)
#print "iterate process", processId
self._iterationDialog = IterationDialog( self, self.currentProcess, self )
self.connect( self._iterationDialog, SIGNAL( 'accept' ),
self._iterateAccept )
self._iterationDialog.show()
def _iterateAccept( self ):
"""
Call back when accepting iteration dialog. Iterates the selected process.
"""
try:
params = self._iterationDialog.getLists()
processes = self.currentProcess._iterate( **params )
iterationProcess = brainvisa.processes.IterationProcess( self.currentProcess.name+" iteration", processes )
showProcess( iterationProcess )
except:
neuroException.showException()
self._iterationDialog.show()
def updateList(self):
"""
Reloads the list of process trees.
"""
self.processTrees.setModel( brainvisa.processes.updatedMainProcessTree() )
def closeEvent ( self, event ):
state = self.saveState(1)
state_file = QtCore.QFile(os.path.join(neuroConfig.homeBrainVISADir, "main_window_state.bin"))
state_file.open(QtCore.QIODevice.WriteOnly)
state_file.write(state)
state_file.close()
quitRequest()
event.ignore()
#----------------------------------------------------------------------------
class ProcessTreesWidget(QSplitter):
"""
A widget that shows a list of :py:class:`ProcessTree`.
Each process tree presents a sub group of existing processes.
It's composed of two parts :
* the list of process trees (use profiles)
* a view of currently selected tree
Each process tree can be opened in another window in order to enable drag and drop from one tree to another.
.. py:attribute:: treeIndex
Widget containing items representing each process tree. TreeListWidget.
.. py:attribute:: treeStack
A stack of EditableTreeWidget, representing the content of each processTree. QWidgetStack.
.. py:attribute:: treeStackIdentifiers
dict associating a processTree to an unique integer identifier used with the widget stack. Only the selected processTree widget of the stack is visible.
.. py:attribute:: widgets
list of EditableTreeWidget currently in the stack. Useful because QWidgetStack doesn't provide iterator on its content.
.. py:attribute:: openedTreeWidget
Currently opened process tree. It is in a window independant from the main window. EditableTreeWidget
.. py:attribute:: model
list of ProcessTree which this widget represents. ProcessTrees
.. py:attribute:: popupMenu
QPopupMenu contextual menu associated to the list of process trees.
.. py:attribute:: savesTimer
QTimer started when the model has changed. When the timer times out, the model is saved. Used to delay model saves : it speeds up execution when there is several modification at the same time (drag&drop several elements).
"""
def __init__(self, processTrees=None, parent=None ):
"""
:param processTrees: ProcessTrees, the list of process trees which this widget represents
:param parent: QWidget, container of this widget
"""
QSplitter.__init__(self, parent)
self.treeIndex=TreeListWidget(None, self, iconSize=bigIconSize)
self.treeIndex.setSizePolicy( QSizePolicy( QSizePolicy.Preferred, QSizePolicy.Preferred ) )
# signals
self.connect(self.treeIndex, SIGNAL( 'currentItemChanged ( QTreeWidgetItem *, QTreeWidgetItem * )' ), self.setCurrentTree)
# on clicking on a tree, emit a pysignal for transmitting the signal to the parent. The shown documentation may need to be changed. (clicked instead of selectionChanged because the documentation may need to be changed event if the item was already selected)
self.connect(self.treeIndex, SIGNAL( 'itemClicked( QTreeWidgetItem *, int )' ), self.selectionChanged)
self.connect(self.treeIndex, SIGNAL( 'customContextMenuRequested ( const QPoint & )'), self.openContextMenu)
# help tooltip
self.treeIndex.setToolTip(_t_("Create your own lists of processes choosing new in contextual menu.
To add items in a list, open an existing list and move items in the new list.
If you set a list as default, it will selected the next time you run brainvisa.") )
self.treeStack=QStackedWidget(self)
self.treeStackIdentifiers = {}
self.treeStack.setSizePolicy( QSizePolicy( QSizePolicy.Preferred,
QSizePolicy.Preferred ) )
#self.setResizeMode( self.treeIndex, QSplitter.FollowSizeHint )
self.widgets=[]
self.openedTreeWidget=None
# Popup Menu for toolboxes
self.popupMenu = QMenu( self )
_addAction( self.popupMenu, _t_("New"), self.menuNewTabEvent )
_addAction( self.popupMenu, _t_("Delete"), self.menuDelTabEvent )
_addAction( self.popupMenu, _t_("Open"), self.menuOpenTabEvent)
_addAction( self.popupMenu, _t_("Set as default list"),
self.menuSetDefaultEvent)
# Popup Menu for processes
self.processMenu = QMenu( self )
_addAction( self.processMenu, _t_("Open"), self.menuOpenProcessEvent )
_addAction( self.processMenu, _t_("Edit documentation"), self.menuEditProcessEvent )
_addAction( self.processMenu, _t_("Iterate"), self.menuIterateProcessEvent)
self.setStretchFactor( 0, 2 )
self.setStretchFactor( 1, 3 )
if processTrees:
self.setModel(processTrees)
def setModel(self, processTrees):
"""
The widget is initialized with the given list of process tree.
For each process tree, an item is added in treeIndex. A widget is created to represent each process tree and added to treeStack.
:param processTrees: the list of process trees which this widget represents
"""
# clear widgets
self.treeIndex.clear()
for w in self.widgets:
self.treeStack.removeWidget(w)
self.treeStackIdentifiers = {}
self.widgets=[]
# register model and add listener
self.model=processTrees
self.model.addListener(self.updateContent)
# listens the change of selectedTree attribute in model
self.model.onAttributeChange("selectedTree", self.updateSelectedTree)
self.model.onAttributeChange("selectedTree", self.modelChanged)
self.model.addListener(self.modelChanged)
# for each processTree, create an EditableTreeWidget which is added to the widget stack
# and add an element to the list index of trees
for processTree in processTrees.values():
self.addProcessTree(processTree)
self.treeIndex.setModel(processTrees)
# if there's a selected tree by default, the corresponding item in widget is selected
if self.model.selectedTree != None:
found=False
i=0
while i - move items by drag and drop,
- delete item with del key,
- copy items by drag and drop and ctrl key,
- create new category with contextual menu."))
# signals
# selectionChanged doesn't work with multiple selection
# currentChanged isn't emited when click on an item that has already keyboeard focus and is not emited when click on an already selected item altought it may be necessary to update documentation because several items can be selected at the same time
# -> so use clicked signal instead
self.connect(treeWidget, SIGNAL( 'itemClicked( QTreeWidgetItem *, int )' ), self.selectionChanged)
self.connect(treeWidget, SIGNAL( 'itemDoubleClicked( QTreeWidgetItem *, int )' ), self.doubleClicked)
self.connect(treeWidget, SIGNAL( 'customContextMenuRequested ( const QPoint & )'), lambda p : self.openProcessMenu(treeWidget, p))
# the new widget representing the process tree is added in treeStack and in widgets
stackIdentifier = self.treeStack.addWidget( treeWidget )
self.treeStackIdentifiers.setdefault( object.__hash__( processTree ), stackIdentifier )
self.widgets.append(treeWidget)
# listens changes in the process tree (for saving each change in minf file)
processTree.addListenerRec(self.modelChanged)
processTree.onAttributeChangeRec("name", self.modelChanged)
def showTreeIndex(self):
if self.treeIndex.isHidden():
self.treeIndex.show()
else:
self.treeIndex.hide()
def openContextMenu(self, point):
"""
Called on contextMenuRequested signal. It opens the popup menu at cursor position.
"""
self.popupMenu.exec_(QCursor.pos())
def openProcessMenu(self, listView, point):
"""
Called on contextMenuRequested signal on the list of processes of a toolbox. It opens the process menu at cursor position if the current item represents a process.
"""
item=listView.itemAt(point)
if item and item.model and item.model.isLeaf():
self.processMenu.exec_(QCursor.pos())
else:
listView.openContextMenu(point)
def updateContent(self, action=None, items=None, position=None):
"""
Called on model change (list of process trees). The widget must update itself to reflect the change.
"""
# treeIndex is a TreeListWidget and has already a listener which update the view on model changes
# but some changes imply modification of treeStack -> add a widget in the stack or remove a widget
if action==ObservableList.INSERT_ACTION:# add a new process tree in the list
for processTree in items:
self.addProcessTree(processTree)
elif action==ObservableList.REMOVE_ACTION:# remove a process tree
for processTree in items:
w=self.treeStack.widget( self.treeStackIdentifiers.get( object.__hash__( processTree ) ) )
self.treeStack.removeWidget(w)
self.widgets.remove(w)
def updateSelectedTree(self, newSelection):
"""
Called when the selected tree changes.
"""
pass # maybe set a graphical element to show it is the default list...
def modelChanged(self, action=None, items=None, position=None):
"""
Method registred to be called when a process tree has changed or when the list of tree has changed.
New ProcessTree list must be saved in a minf file.
If change is insertion of a new item in a tree, registers listeners on this new item.
"""
#print "model changed", action, "write to minf file processTrees.minf"
if action==ObservableList.INSERT_ACTION:
for item in items:
# on insertion in a tree, listen changes of the new element
# on insertion of a tree in the tree list, nothing to do, listeners are already registred
if not issubclass(item.__class__, brainvisa.processes.ProcessTree):
if not item.isLeaf():
item.addListenerRec(self.modelChanged)
item.onAttributeChangeRec("name", self.modelChanged)
else:
item.onAttributeChange("name", self.modelChanged)
# instead of systematic model save for each change, start a timer wich will timeout when current event is finished
# So if there is several modification due to the same event (drag and drop several elements), the model will be saved only one time when all changes are done. (speedier)
if not self.savesTimer.isActive(): # if the timer is already started nothing to do, the change will be save anyway
self.savesTimer.start(0)
#self.model.save()
def selectionChanged(self, item, col=0):
"""
Called when selected item has changed in current process tree.
This method emits a signal that must be caught by parent widget.
"""
if item is not None:
self.emit(SIGNAL("selectionChanged"), item.model)
def doubleClicked(self, item, col):
"""
Called on double click on an item of current process tree.
This method emits a signal that must be caught by parent widget.
"""
self.emit(SIGNAL("doubleClicked"), item.model)
def setCurrentTree(self, item, previous=None):
"""
Changes the visible widget in the stack.
"""
if item:
self.treeStack.setCurrentIndex( self.treeStackIdentifiers.get( object.__hash__( item.model ) ) )
def findItem( self, name):
"""
Find items that contain the string given in parameters in their name. Each found item is selected and yield (and replace previous selection).
Wide search.
:param name: string searched in items names.
:rtype: generator
"""
for widget in self.widgets: # for all process trees widgets
it=QTreeWidgetItemIterator(widget)
lastSelection=None
while it.value():
item=it.value()
if not item.isHidden():
if item.model.isLeaf(): # for a leaf (process) search string in name
keep = False
if item.model.name.lower().find(name) > -1 \
or item.model.id.lower().find(name) > -1:
keep = True
else:
# also try to find the untranslated process name
try:
proc = brainvisa.processes.getProcess( item.model.id )
pname = proc.name
if pname.lower().find(name) > -1:
keep = True
except:
pass
if keep:
self.select(widget, item, lastSelection)
lastSelection=(widget, item)
yield item
it+=1
def select(self, widget, item, lastSelection):
"""
Select a process tree and an item in it. Undo last selection.
:param widget: EditableTreeWidget, the tree widget that contains the item to select.
:param item: EditableTreeItem, the item (process) to select
:param lastSelection: tuple(EditableTreeWidget, EditableTreeItem), previous selected item and its container, to be unselected.
"""
self.selectIndex(widget.model) # select in left panel (toolbox name)
self.setCurrentTree(widget) # raise widget of toolbox content
item.setHidden(False)
#widget.ensureItemVisible( item ) # show item (open parents...)
widget.setCurrentItem( item ) # select item
if lastSelection:# undo last selection
lastSelection[1].setSelected(False)
#lastSelection[0].setSelected(lastSelection[1], 0)
def selectIndex(self, model):
"""
Select a process tree in the left panel (toolboxes).
:param model: the process tree to select
"""
i=0
found=False
while i= 1:
_computing_resource_pool = ComputingResourcePool()
_computing_resource_pool.add_default_connection()
_workflow_application_model = WorkflowApplicationModel(_computing_resource_pool)
exec 'from brainvisa.processing.qt4gui.neuroProcessesGUI import *' in brainvisa.processes.__dict__
brainvisa.processes._defaultContext = ExecutionContextGUI()
else:
exec 'from brainvisa.processing.qt4gui.neuroProcessesGUI import mainThreadActions' in brainvisa.processes.__dict__