3364 lines
146 KiB
Python
3364 lines
146 KiB
Python
# -*- coding: utf-8 -*-
|
|
# import native python modules
|
|
import time
|
|
import math
|
|
import sys
|
|
import platform
|
|
import io
|
|
import os.path
|
|
import traceback
|
|
import tables
|
|
import xml.parsers.expat
|
|
import threading
|
|
import webbrowser
|
|
import xdg.BaseDirectory
|
|
import tempfile
|
|
|
|
import gi
|
|
gi.require_version('Gtk', '3.0')
|
|
gi.require_version('GtkSource', '3.0')
|
|
|
|
# import 3rd party modules
|
|
# gui graphics
|
|
from gi.repository import GObject as gobject
|
|
import sqlite3
|
|
import uuid
|
|
|
|
from gi.repository import Gtk as gtk
|
|
|
|
gtk_version_missmatch = gtk.check_version( 3, 0, 0 )
|
|
if gtk_version_missmatch:
|
|
raise Exception( "insufficient gtk version: " + gtk_version_missmatch )
|
|
|
|
from gi.repository import Gdk as gdk
|
|
|
|
from gi.repository import GtkSource as gtksourceview2
|
|
gobject.type_register(gtksourceview2.View)
|
|
|
|
from gi.repository import Pango as pango
|
|
|
|
# array math
|
|
import numpy
|
|
|
|
# math graphics
|
|
import matplotlib
|
|
# force noninteractive
|
|
matplotlib.rcParams[ "interactive" ] = "False"
|
|
matplotlib.rcParams[ "text.usetex" ] = "False"
|
|
matplotlib.rcParams[ "axes.formatter.limits" ] = "-3,3"
|
|
|
|
from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FigureCanvas
|
|
#from matplotlib.backends.backend_gtk3cairo import FigureCanvasGTK3Cairo as FigureCanvas
|
|
|
|
max_points_to_display = 0
|
|
|
|
import matplotlib.axes
|
|
import matplotlib.figure
|
|
|
|
# import our own stuff
|
|
from damaris.gui import ExperimentWriter, ExperimentHandling
|
|
from damaris.gui import ResultReader, ResultHandling
|
|
from damaris.gui import BackendDriver
|
|
#from damaris.gui.gtkcodebuffer import CodeBuffer, SyntaxLoader
|
|
#from damaris.data import Drawable # this is a base class, it should be used...
|
|
from damaris.data import DataPool, Accumulation, ADC_Result, MeasurementResult
|
|
|
|
# default, can be set to true from start script
|
|
debug = False
|
|
|
|
# version info
|
|
from damaris import __version__
|
|
|
|
|
|
class logstream:
|
|
gui_log = None
|
|
text_log = sys.__stdout__
|
|
|
|
def write( self, message ):
|
|
# default for stdout and stderr
|
|
if self.gui_log is not None:
|
|
self.gui_log( message )
|
|
if debug or self.gui_log is None:
|
|
self.text_log.write( message )
|
|
self.text_log.flush( )
|
|
def __call__( self, message ):
|
|
self.write( message )
|
|
def flush(self):
|
|
pass
|
|
def __del__( self ):
|
|
pass
|
|
|
|
|
|
global log
|
|
log = logstream( )
|
|
sys.stdout = log
|
|
sys.stderr = log
|
|
|
|
ExperimentHandling.log = log
|
|
ResultHandling.log = log
|
|
ResultReader.log = log
|
|
ExperimentWriter.log = log
|
|
BackendDriver.log = log
|
|
DataPool.log = log
|
|
|
|
|
|
class LockFile:
|
|
def __init__(self):
|
|
self.index = 0
|
|
self.file = os.path.expanduser("~/.damaris.lockdb")
|
|
self.conn = sqlite3.connect(self.file)
|
|
self.cursor = self.conn.cursor()
|
|
self.id = None
|
|
print("Connected to db..")
|
|
|
|
def add_experiment(self):
|
|
print("Add experiment..")
|
|
self.id = str(uuid.uuid4())
|
|
self.cursor.execute("INSERT INTO damaris VALUES (?, ?)",(self.id, "started"))
|
|
self.conn.commit()
|
|
return self.id
|
|
|
|
def del_experiment(self, id):
|
|
print("Delete experiment..")
|
|
self.cursor.execute('DELETE FROM damaris WHERE uuid=?', (self.id,))
|
|
self.conn.commit()
|
|
return True
|
|
|
|
def am_i_next(self):
|
|
first = self.cursor.execute("SELECT * FROM damaris ORDER BY ROWID ASC LIMIT 1")
|
|
current_running = first.fetchone()[0]
|
|
#print current_running,self.id
|
|
if current_running == self.id:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
class DamarisGUI:
|
|
ExpScript_Display = 0
|
|
ResScript_Display = 1
|
|
Monitor_Display = 2
|
|
Log_Display = 3
|
|
Config_Display = 4
|
|
|
|
Edit_State = 0
|
|
Run_State = 1
|
|
Pause_State = 2
|
|
Stop_State = 3
|
|
Quit_State = 4
|
|
|
|
def __init__(self, exp_script_filename=None, res_script_filename=None, start_immediately=False):
|
|
# state: edit, run, stop, quit
|
|
# state transitions:
|
|
# edit -> run|quit
|
|
# run -> pause|stop
|
|
# pause -> run|stop
|
|
# stop -> edit
|
|
self.state = DamarisGUI.Edit_State
|
|
# script execution engines and backend driver
|
|
self.si = None
|
|
# produced and displayed data
|
|
self.data = None
|
|
|
|
self.glade_layout_init( )
|
|
# my notebook
|
|
self.main_notebook = self.xml_gui.get_object( "main_notebook" )
|
|
|
|
self.log = LogWindow( self.xml_gui , self)
|
|
|
|
self.sw = ScriptWidgets( self.xml_gui )
|
|
|
|
self.toolbar_init( )
|
|
|
|
self.documentation_init( )
|
|
|
|
self.monitor = MonitorWidgets( self.xml_gui )
|
|
|
|
self.config = ConfigTab( self.xml_gui )
|
|
# lock file to prevent other DAMARIS to start immediatly
|
|
self.lockfile = LockFile() # = os.path.expanduser("~/.damaris.lock")
|
|
self.id = None
|
|
|
|
#to stop queued experiments
|
|
self.stop_experiment_flag = threading.Event()
|
|
|
|
exp_script = ""
|
|
if exp_script_filename is not None and exp_script_filename != "":
|
|
self.sw.exp_script_filename = exp_script_filename[ : ]
|
|
if os.path.isfile( exp_script_filename ) and os.access( exp_script_filename, os.R_OK ):
|
|
exp_script = self.sw.load_file_as_unicode( exp_script_filename )
|
|
|
|
res_script = ""
|
|
if res_script_filename is not None and res_script_filename != "":
|
|
self.sw.res_script_filename = res_script_filename[ : ]
|
|
if os.path.isfile( res_script_filename ) and os.access( res_script_filename, os.R_OK ):
|
|
res_script = self.sw.load_file_as_unicode( res_script_filename )
|
|
self.sw.set_scripts( exp_script, res_script )
|
|
|
|
if start_immediately:
|
|
if exp_script and res_script:
|
|
self.start_immediately = start_immediately
|
|
else:
|
|
raise "RuntimeError"
|
|
else:
|
|
self.start_immediately = False
|
|
self.statusbar_init( )
|
|
|
|
#connect event handlers
|
|
eventhandlers = {"on_toolbar_run_button_clicked": self.start_experiment,
|
|
"on_toolbar_pause_button_toggled": self.pause_experiment,
|
|
"on_toolbar_stop_button_clicked": self.stop_experiment,
|
|
"on_doc_menu_activate": self.show_doc_menu,
|
|
"on_toolbar_manual_button_clicked": self.show_manual,
|
|
"on_data_handling_column_textfield_value_changed": self.sw.column_line_widgets_changed_event,
|
|
"on_data_handling_line_textfield_value_changed": self.sw.column_line_widgets_changed_event,
|
|
"on_experiment_script_line_textfield_value_changed": self.sw.column_line_widgets_changed_event,
|
|
"on_experiment_script_column_textfield_value_changed": self.sw.column_line_widgets_changed_event,
|
|
"on_toolbar_open_file_button_clicked": self.sw.open_file,
|
|
"on_toolbar_new_button_clicked": self.sw.new_file,
|
|
"on_toolbar_save_as_button_clicked": self.sw.save_file_as,
|
|
"on_toolbar_save_file_button_clicked": self.sw.save_file,
|
|
"on_toolbar_save_all_button_clicked": self.sw.save_all_files,
|
|
"on_toolbar_check_scripts_button_clicked": self.sw.check_script,
|
|
"on_toolbar_undo_button_clicked": self.sw.undo,
|
|
"on_toolbar_redo_button_clicked": self.sw.redo,
|
|
"on_toolbar_search_button_clicked": self.sw.search,
|
|
"on_config_save_button_clicked": self.config.save_config_handler,
|
|
"on_config_load_button_clicked": self.config.load_config_handler,
|
|
"on_backend_executable_browse_button_clicked": self.config.browse_backend_executable_dialog,
|
|
"on_fontbutton_font_set": self.config.set_script_font_handler,
|
|
"on_display_autoscaling_checkbutton_toggled": self.monitor.display_autoscaling_toggled,
|
|
"on_display_statistics_checkbutton_toggled": self.monitor.display_statistics_toggled,
|
|
"on_display_x_scaling_combobox_changed": self.monitor.display_scaling_changed,
|
|
"on_display_y_scaling_combobox_changed": self.monitor.display_scaling_changed,
|
|
"on_display_save_data_as_text_button_clicked": self.monitor.save_display_data_as_text,
|
|
"on_display_copy_data_to_clipboard_button_clicked": self.monitor.copy_display_data_to_clipboard,
|
|
"on_data_handling_textview_event": self.sw.textviews_modified,
|
|
"on_experiment_script_textview_event": self.sw.textviews_modified,
|
|
"on_main_notebook_switch_page": self.nopevent,
|
|
"main_window_close": self.nopevent
|
|
}
|
|
self.xml_gui.connect_signals(eventhandlers)
|
|
|
|
self.main_window.maximize()
|
|
self.main_window.show_all( )
|
|
self.main_window.present( )
|
|
|
|
def glade_layout_init( self ):
|
|
glade_file = os.path.join( os.path.dirname( __file__ ), "damaris3.glade" )
|
|
self.xml_gui = gtk.Builder()
|
|
self.xml_gui.add_from_file(glade_file)
|
|
self.main_window = self.xml_gui.get_object( "main_window" )
|
|
self.main_window.connect( "delete-event", self.quit_event )
|
|
for icon_path in ["/usr/share/pixmaps/DAMARIS3.png",
|
|
os.path.join( os.path.dirname( __file__ ), "DAMARIS3.png" )]:
|
|
if os.path.exists(icon_path):
|
|
self.main_window.set_icon_from_file(icon_path)
|
|
break
|
|
self.main_window.set_title( "DAMARIS-%s" % __version__ )
|
|
|
|
|
|
def statusbar_init( self ):
|
|
"""
|
|
experiment and result thread status, backend state
|
|
"""
|
|
self.experiment_script_statusbar_label = self.xml_gui.get_object( "statusbar_experiment_script_label" )
|
|
self.data_handling_statusbar_label = self.xml_gui.get_object( "statusbar_data_handling_label" )
|
|
self.backend_statusbar_label = self.xml_gui.get_object( "statusbar_core_label" )
|
|
|
|
def toolbar_init( self ):
|
|
"""
|
|
buttons like save and run...
|
|
"""
|
|
self.toolbar_stop_button = self.xml_gui.get_object( "toolbar_stop_button" )
|
|
self.toolbar_run_button = self.xml_gui.get_object( "toolbar_run_button" )
|
|
self.toolbar_pause_button = self.xml_gui.get_object( "toolbar_pause_button" )
|
|
|
|
# prepare for edit state
|
|
self.toolbar_run_button.set_sensitive( True )
|
|
self.toolbar_stop_button.set_sensitive( False )
|
|
self.toolbar_pause_button.set_sensitive( False )
|
|
|
|
def run( self ):
|
|
# prolong lifetime of clipboard till the very end (avoid error message)
|
|
self.main_clipboard = self.sw.main_clipboard
|
|
gdk.threads_enter( )
|
|
|
|
if self.start_immediately:
|
|
self.toolbar_run_button.emit("clicked")
|
|
gtk.main( )
|
|
gdk.threads_leave( )
|
|
|
|
self.si = None
|
|
self.sw = None
|
|
self.config = None
|
|
self.xml_gui = None
|
|
|
|
# event handling: the real acitons in gui programming
|
|
|
|
# first global events
|
|
|
|
def nopevent(self, widget, data=None, otherargument=None):
|
|
#print(widget)
|
|
#print(data)
|
|
#print(otherargument)
|
|
pass
|
|
|
|
def quit_event( self, widget, data=None ):
|
|
"""
|
|
expecting quit event for main application
|
|
"""
|
|
if self.state in [ DamarisGUI.Edit_State, DamarisGUI.Quit_State ]:
|
|
self.state = DamarisGUI.Quit_State
|
|
# do a cleanup...
|
|
|
|
if self.sw.experiment_script_textbuffer.get_modified( ):
|
|
question = gtk.MessageDialog(parent=self.xml_gui.get_object( "main_window"),
|
|
type=gtk.MessageType.WARNING,
|
|
flags=gtk.DialogFlags.MODAL,
|
|
buttons=("_Cancel", gtk.ResponseType.CANCEL, "_No", gtk.ResponseType.NO, "_Yes", gtk.ResponseType.YES))
|
|
question.set_markup("The Experiment Script has been changed. Save changes?")
|
|
|
|
response = question.run()
|
|
question.destroy()
|
|
|
|
if response == gtk.ResponseType.CANCEL:
|
|
return True
|
|
elif response == gtk.ResponseType.YES:
|
|
current_page = self.main_notebook.get_current_page( )
|
|
self.main_notebook.set_current_page(0)
|
|
if self.sw.exp_script_filename is None:
|
|
self.sw.save_file_as()
|
|
else:
|
|
self.sw.save_file()
|
|
self.main_notebook.set_current_page(current_page)
|
|
|
|
if self.sw.data_handling_textbuffer.get_modified( ):
|
|
question = gtk.MessageDialog(parent=self.xml_gui.get_object( "main_window"),
|
|
type=gtk.MessageType.WARNING,
|
|
flags=gtk.DialogFlags.MODAL,
|
|
buttons=("_Cancel", gtk.ResponseType.CANCEL, "_No", gtk.ResponseType.NO, "_Yes", gtk.ResponseType.YES))
|
|
question.set_markup("The Result Script has been changed. Save changes?")
|
|
|
|
response = question.run()
|
|
question.destroy()
|
|
|
|
if response == gtk.ResponseType.CANCEL:
|
|
return True
|
|
elif response == gtk.ResponseType.YES:
|
|
current_page = self.main_notebook.get_current_page( )
|
|
self.main_notebook.set_current_page(1)
|
|
if self.sw.res_script_filename is None:
|
|
self.sw.save_file_as()
|
|
else:
|
|
self.sw.save_file()
|
|
self.main_notebook.set_current_page(current_page)
|
|
|
|
|
|
|
|
self.config = None
|
|
self.sw = None
|
|
self.monitor = None
|
|
self.log = None
|
|
# and quit
|
|
gtk.main_quit( )
|
|
return True
|
|
else:
|
|
question = gtk.MessageDialog(parent=self.xml_gui.get_object( "main_window"),
|
|
type=gtk.MessageType.ERROR,
|
|
flags=gtk.DialogFlags.MODAL,
|
|
buttons=("_Understood!", gtk.ResponseType.OK))
|
|
question.set_markup("Experiment still running, can not close DAMARIS!")
|
|
|
|
response = question.run()
|
|
question.destroy()
|
|
return True
|
|
|
|
# toolbar related events:
|
|
|
|
def start_experiment( self, widget, data=None ):
|
|
|
|
# something running?
|
|
if self.si is not None:
|
|
print("Last Experiment is not clearly stopped!")
|
|
self.si = None
|
|
|
|
# get config values:
|
|
actual_config = self.config.get( )
|
|
|
|
# get scripts and start script interface
|
|
self.sw.disable_editing( )
|
|
exp_script, res_script = self.sw.get_scripts( )
|
|
if not actual_config[ "start_result_script" ]:
|
|
res_script = ""
|
|
if not actual_config[ "start_experiment_script" ]:
|
|
exp_script = ""
|
|
backend = actual_config[ "backend_executable" ]
|
|
if not actual_config[ "start_backend" ]:
|
|
backend = ""
|
|
|
|
if (backend == "" and exp_script == "" and res_script == ""):
|
|
print("nothing to do...so doing nothing!")
|
|
self.sw.enable_editing( )
|
|
return
|
|
|
|
# check whether scripts are syntactically valid
|
|
# should be merged with check script function
|
|
exp_code = None
|
|
if exp_script != "":
|
|
try:
|
|
exp_code = compile( exp_script, "Experiment Script", "exec" )
|
|
except SyntaxError as e:
|
|
ln = e.lineno
|
|
lo = e.offset
|
|
if type( ln ) is not int:
|
|
ln = 0
|
|
if type( lo ) is not int:
|
|
lo = 0
|
|
print("Experiment Script: %s at line %d, col %d:" % (e.__class__.__name__, ln, lo))
|
|
if e.text != "":
|
|
print(e.text.rstrip())
|
|
print(" "*(e.offset-1)+"^")
|
|
print(e)
|
|
|
|
res_code = None
|
|
if res_script != "":
|
|
try:
|
|
res_code = compile( res_script, "Result Script", "exec" )
|
|
except SyntaxError as e:
|
|
ln = e.lineno
|
|
lo = e.offset
|
|
if type( ln ) is not int:
|
|
ln = 0
|
|
if type( lo ) is not int:
|
|
lo = 0
|
|
print("Result script: %s at line %d, col %d:" % (e.__class__.__name__, ln, lo))
|
|
if e.text != "":
|
|
print("\"%s\"" % e.text)
|
|
# print " "*(e.offset+1)+"^" # nice idea, but needs monospaced fonts
|
|
pass
|
|
print(e)
|
|
|
|
# detect error
|
|
if (exp_script != "" and exp_code is None) or \
|
|
(res_script != "" and res_code is None):
|
|
self.main_notebook.set_current_page( DamarisGUI.Log_Display )
|
|
self.sw.enable_editing( )
|
|
return
|
|
|
|
# prepare to run
|
|
self.state = DamarisGUI.Run_State
|
|
self.toolbar_run_button.set_sensitive( False )
|
|
self.toolbar_stop_button.set_sensitive( True )
|
|
self.toolbar_pause_button.set_sensitive( True )
|
|
self.toolbar_pause_button.set_active( False )
|
|
|
|
# delete old data
|
|
self.data = None
|
|
self.monitor.observe_data_pool( self.data )
|
|
|
|
# set the text mark for hdf logging
|
|
if self.log.textbuffer.get_mark( "lastdumped" ) is None:
|
|
self.log.textbuffer.create_mark( "lastdumped", self.log.textbuffer.get_end_iter( ), left_gravity=True )
|
|
|
|
loop_run=0
|
|
#user shorter interval because this blocks user events from the GUI
|
|
interval = 0.1
|
|
self.stop_experiment_flag.clear()
|
|
|
|
self.id = self.lockfile.add_experiment()
|
|
while not self.lockfile.am_i_next():
|
|
while gtk.events_pending():
|
|
gtk.main_iteration_do(False)
|
|
if self.stop_experiment_flag.wait(interval):
|
|
break
|
|
if loop_run > 1:
|
|
self.experiment_script_statusbar_label.set_text("Waiting for other experiment to finish")
|
|
loop_run = 0
|
|
loop_run += interval
|
|
|
|
if self.stop_experiment_flag.isSet():
|
|
#Experiment has been stopped, clean up and leave
|
|
self.experiment_script_statusbar_label.set_text("Experiment stopped")
|
|
self.state = DamarisGUI.Edit_State
|
|
self.sw.enable_editing( )
|
|
self.toolbar_run_button.set_sensitive( True )
|
|
self.toolbar_stop_button.set_sensitive( False )
|
|
self.toolbar_pause_button.set_sensitive( False )
|
|
self.toolbar_pause_button.set_active( False )
|
|
#delete experiment from queue so that next experiment may start
|
|
self.lockfile.del_experiment(self.id)
|
|
return
|
|
|
|
# start experiment
|
|
try:
|
|
self.spool_dir = os.path.abspath( actual_config[ "spool_dir" ] )
|
|
# setup script engines
|
|
self.si = ScriptInterface( exp_code,
|
|
res_code,
|
|
backend,
|
|
self.spool_dir,
|
|
clear_jobs=actual_config[ "del_jobs_after_execution" ],
|
|
clear_results=actual_config[ "del_results_after_processing" ] )
|
|
|
|
self.data = self.si.data
|
|
# run frontend and script engines
|
|
self.monitor.observe_data_pool( self.data )
|
|
self.si.runScripts( )
|
|
except Exception as e:
|
|
#print "ToDo evaluate exception",str(e), "at",traceback.extract_tb(sys.exc_info()[2])[-1][1:3]
|
|
#print "Full traceback:"
|
|
traceback_file = io.StringIO( )
|
|
traceback.print_tb( sys.exc_info( )[ 2 ], None, traceback_file )
|
|
self.main_notebook.set_current_page( DamarisGUI.Log_Display )
|
|
print("Error while executing scripts: %s\n" % str( e ) + traceback_file.getvalue( ))
|
|
traceback_file = None
|
|
|
|
self.data = None
|
|
if self.si is not None:
|
|
still_running = [_f for _f in [ self.si.exp_handling, self.si.res_handling, self.si.back_driver ] if _f]
|
|
for r in still_running:
|
|
r.quit_flag.set( )
|
|
print("waiting for threads stoping...", end=' ')
|
|
still_running = [x for x in [ self.si.exp_handling, self.si.res_handling, self.si.back_driver ] if x is not None and x.is_alive( )]
|
|
for t in still_running:
|
|
while t.is_alive():
|
|
while gtk.events_pending():
|
|
gtk.main_iteration_do(False)
|
|
t.join( 0.05 )
|
|
print("done")
|
|
|
|
# cleanup
|
|
self.si = None
|
|
self.state = DamarisGUI.Edit_State
|
|
self.sw.enable_editing( )
|
|
self.toolbar_run_button.set_sensitive( True )
|
|
self.toolbar_stop_button.set_sensitive( False )
|
|
self.toolbar_pause_button.set_sensitive( False )
|
|
self.toolbar_pause_button.set_active( False )
|
|
#delete Experiment from queue
|
|
self.lockfile.del_experiment(self.id)
|
|
return
|
|
|
|
# switch to grapics
|
|
self.main_notebook.set_current_page( DamarisGUI.Monitor_Display )
|
|
|
|
# set running
|
|
if self.si.exp_handling is not None:
|
|
self.experiment_script_statusbar_label.set_text( "Experiment Script Running (0)" )
|
|
else:
|
|
self.experiment_script_statusbar_label.set_text( "Experiment Script Idle" )
|
|
if self.si.res_handling is not None:
|
|
self.data_handling_statusbar_label.set_text( "Result Script Running (0)" )
|
|
else:
|
|
self.data_handling_statusbar_label.set_text( "Result Script Idle" )
|
|
|
|
if self.si.back_driver is not None:
|
|
self.backend_statusbar_label.set_text( "Backend Running" )
|
|
else:
|
|
self.backend_statusbar_label.set_text( "Backend Idle" )
|
|
|
|
# start data dump
|
|
self.dump_thread = None
|
|
self.save_thread = None
|
|
self.dump_filename = ""
|
|
if actual_config[ "data_pool_name" ] != "":
|
|
self.dump_states( init=True )
|
|
gobject.timeout_add( 200, self.observe_running_experiment )
|
|
|
|
def observe_running_experiment( self ):
|
|
"""
|
|
periodically look at running threads
|
|
"""
|
|
# look at components and update them
|
|
# test whether backend and scripts are done
|
|
r = self.si.data.get( "__recentresult", -1 ) + 1
|
|
b = self.si.data.get( "__resultsinadvance", -1 ) + 1
|
|
e = self.si.data.get( "__recentexperiment", -1 ) + 1
|
|
|
|
experimentstarttime = self.si.data.get( "__experimentstarted", 0 )
|
|
expectedexperimentruntime = self.si.data.get( "__totalexperimentlength", 0 )
|
|
experimentsfinishedtime = 0.0
|
|
|
|
for i in range(0, b):
|
|
experimentsfinishedtime += self.si.data.get("__experimentlengths", {}).get(i, 0.0)
|
|
|
|
experimentruntime = time.time() - experimentstarttime
|
|
|
|
experimenttimetext = ""
|
|
|
|
if experimentstarttime > 0 and expectedexperimentruntime > 0:
|
|
runtimemin, runtimesec = divmod(math.floor(experimentruntime), 60)
|
|
runtimehours, runtimemin = divmod(runtimemin, 60)
|
|
experimenttimetext += " ({:02d}:{:02d}:{:02d} / ".format(int(runtimehours), int(runtimemin), int(runtimesec))
|
|
expectedmin, expectedsec = divmod(math.ceil(expectedexperimentruntime), 60)
|
|
expectedhours, expectedmin = divmod(expectedmin, 60)
|
|
experimenttimetext += "{:02d}:{:02d}:{:02d})".format(int(expectedhours), int(expectedmin), int(expectedsec))
|
|
|
|
backendtimetext = ""
|
|
|
|
if experimentsfinishedtime > 0:
|
|
finexperimmin, finexperimsec = divmod(round(experimentsfinishedtime), 60)
|
|
finexperimhours, finexperimmin = divmod(finexperimmin, 60)
|
|
backendtimetext = " ({:02d}:{:02d}:{:02d})".format(int(finexperimhours), int(finexperimmin), int(finexperimsec))
|
|
|
|
e_text = None
|
|
r_text = None
|
|
b_text = None
|
|
if self.si.exp_handling is not None:
|
|
if not self.si.exp_handling.is_alive( ):
|
|
self.si.exp_handling.join( )
|
|
if self.si.exp_handling.raised_exception:
|
|
print("experiment script failed at line %d (function %s): %s" % (self.si.exp_handling.location[ 0 ],
|
|
self.si.exp_handling.location[ 1 ],
|
|
self.si.exp_handling.raised_exception))
|
|
print("Full traceback", self.si.exp_handling.traceback)
|
|
e_text = "Experiment Script Failed (%d)" % e
|
|
else:
|
|
e_text = "Experiment Script Finished (%d)" % e
|
|
print("experiment script finished")
|
|
self.si.exp_handling = None
|
|
else:
|
|
#print self.si.back_driver.get_messages()
|
|
e_text = "Experiment Script Running (%d)" % e
|
|
e_text += experimenttimetext
|
|
|
|
if self.si.res_handling is not None:
|
|
if not self.si.res_handling.is_alive( ):
|
|
self.si.res_handling.join( )
|
|
if self.si.res_handling.raised_exception:
|
|
print("result script failed at line %d (function %s): %s" % (self.si.res_handling.location[ 0 ],
|
|
self.si.res_handling.location[ 1 ],
|
|
self.si.res_handling.raised_exception))
|
|
print("Full traceback", self.si.res_handling.traceback)
|
|
r_text = "Result Script Failed (%d)" % r
|
|
else:
|
|
r_text = "Result Script Finished (%d)" % r
|
|
self.si.res_handling = None
|
|
else:
|
|
r_text = "Result Script Running (%d)" % r
|
|
|
|
if self.si.back_driver is not None:
|
|
if not self.si.back_driver.is_alive( ):
|
|
if self.si.back_driver.raised_exception:
|
|
b_text = "Backend Failed"
|
|
else:
|
|
b_text = "Backend Finished"
|
|
self.si.back_driver.join( )
|
|
self.si.back_driver = None
|
|
else:
|
|
b_text = "Backend Running"
|
|
if b != 0:
|
|
b_text += " (%d)" % b
|
|
b_text += backendtimetext
|
|
|
|
if self.dump_thread is not None:
|
|
if self.dump_thread.is_alive( ):
|
|
sys.stdout.write( "." )
|
|
self.dump_dots += 1
|
|
if self.dump_dots > 80:
|
|
print()
|
|
self.dump_dots = 0
|
|
else:
|
|
self.dump_thread.join( )
|
|
self.dump_thread = None
|
|
dump_size = os.stat( self.dump_filename ).st_size / 1e6
|
|
print("done (%.1f s, %.1f MB)" % (time.time( ) - self.dump_start_time, dump_size))
|
|
|
|
gdk.threads_enter( )
|
|
if e_text:
|
|
self.experiment_script_statusbar_label.set_text( e_text )
|
|
if r_text:
|
|
self.data_handling_statusbar_label.set_text( r_text )
|
|
if b_text:
|
|
self.backend_statusbar_label.set_text( b_text )
|
|
gdk.threads_leave( )
|
|
|
|
still_running = [_f for _f in [ self.si.exp_handling, self.si.res_handling, self.si.back_driver, self.dump_thread ] if _f]
|
|
if len( still_running ) == 0:
|
|
if self.save_thread is None and self.dump_filename != "":
|
|
print("all subprocesses ended, saving data pool")
|
|
# thread to save data...
|
|
self.save_thread = threading.Thread( target=self.dump_states, name="dump states" )
|
|
self.save_thread.start( )
|
|
self.dump_start_time = time.time( )
|
|
self.dump_dots = 0
|
|
self.state = DamarisGUI.Stop_State
|
|
|
|
if self.state == DamarisGUI.Stop_State:
|
|
gdk.threads_enter( )
|
|
self.toolbar_pause_button.set_sensitive( False )
|
|
self.toolbar_stop_button.set_sensitive( False )
|
|
gdk.threads_leave( )
|
|
if len( still_running ) != 0:
|
|
print("subprocess(es) still running: " + ', '.join( [s.getName( ) for s in still_running] ))
|
|
return True
|
|
else:
|
|
if self.save_thread is not None:
|
|
if self.save_thread.is_alive( ):
|
|
sys.stdout.write( "." )
|
|
self.dump_dots += 1
|
|
if self.dump_dots > 80:
|
|
print()
|
|
self.dump_dots = 0
|
|
return True
|
|
self.save_thread.join( )
|
|
self.save_thread = None
|
|
dump_size = os.stat( self.dump_filename ).st_size / 1e6
|
|
print("done (%.1f s, %.1f MB)" % (time.time( ) - self.dump_start_time, dump_size))
|
|
|
|
# now everything is stopped
|
|
self.state = DamarisGUI.Edit_State
|
|
gdk.threads_enter( )
|
|
self.sw.enable_editing( )
|
|
self.toolbar_run_button.set_sensitive( True )
|
|
self.toolbar_stop_button.set_sensitive( False )
|
|
self.toolbar_pause_button.set_sensitive( False )
|
|
gdk.threads_leave( )
|
|
|
|
# keep data to display but throw away everything else
|
|
self.si = None
|
|
# delete lock file so that other experiment can start
|
|
self.lockfile.del_experiment(self.id)
|
|
return False
|
|
|
|
# dump states?
|
|
if self.dump_thread is None and self.dump_filename != "" and \
|
|
self.dump_timeinterval != 0 and self.last_dumped + self.dump_timeinterval < time.time( ):
|
|
print("dumping data pool")
|
|
self.dump_start_time = time.time( )
|
|
self.dump_dots = 0
|
|
# thread to save data...
|
|
self.dump_thread = threading.Thread( target=self.dump_states, name="dump states" )
|
|
self.dump_thread.start( )
|
|
|
|
# or look at them again
|
|
return True
|
|
|
|
def dump_states( self, init=False ):
|
|
"""
|
|
init: constructs basic structure of this file
|
|
compress: optional argument for zlib compression 0-9
|
|
"""
|
|
|
|
if init:
|
|
# calculate new settings for hdf files
|
|
actual_config = self.config.get( )
|
|
# provide name tags for dump file name
|
|
extensions_dict = { "date": time.strftime( "%Y-%m-%d" ),
|
|
"datetime": time.strftime( "%Y-%m-%d_%H%M" )
|
|
}
|
|
if self.sw.exp_script_filename is None:
|
|
extensions_dict[ "exp" ] = "unnamed"
|
|
else:
|
|
extensions_dict[ "exp" ] = os.path.splitext( os.path.basename( self.sw.exp_script_filename ) )[ 0 ]
|
|
if self.sw.res_script_filename is None:
|
|
extensions_dict[ "res" ] = "unnamed"
|
|
else:
|
|
extensions_dict[ "res" ] = os.path.splitext( os.path.basename( self.sw.res_script_filename ) )[ 0 ]
|
|
# create new dump file name
|
|
try:
|
|
self.dump_filename = actual_config.get( "data_pool_name" ) % extensions_dict
|
|
except ValueError:
|
|
print("invalid formating character in '" + actual_config.get( "data_pool_name" ) + "'")
|
|
self.dump_filename = "DAMARIS_data_pool.h5"
|
|
print("reseting dumpfile to " + self.dump_filename)
|
|
del extensions_dict
|
|
|
|
if os.path.split( self.dump_filename )[ 1 ] == "" or os.path.isdir( self.dump_filename ):
|
|
print("The dump filename is a directory, using filename 'DAMARIS_data_pool.h5'")
|
|
self.dump_filename += os.sep + "DAMARIS_data_pool.h5"
|
|
|
|
# compression
|
|
self.dump_complib = actual_config.get( "data_pool_complib", None )
|
|
if self.dump_complib == "None":
|
|
self.dump_complib = None
|
|
if self.dump_complib is not None:
|
|
self.dump_complevel = int( actual_config.get( "data_pool_comprate", 0 ) )
|
|
else:
|
|
self.dump_complevel = 0
|
|
|
|
# time interval
|
|
self.dump_timeinterval = 60 * 60 * 10
|
|
try:
|
|
self.dump_timeinterval = int( 60 * float( actual_config[ "data_pool_write_interval" ] ) )
|
|
except ValueError as e:
|
|
print("configuration provides non-number for dump interval: " + str( e ))
|
|
|
|
# if existent, move away old files
|
|
if os.path.isfile( self.dump_filename ):
|
|
# create bakup name pattern
|
|
dump_filename_pattern = None
|
|
(filename, ext) = os.path.splitext( self.dump_filename )
|
|
if ext in [ ".h5", ".hdf", ".hdf5" ]:
|
|
dump_filename_pattern = filename.replace( "%", "%%" ) + "_%d" + ext
|
|
else:
|
|
dump_filename_pattern = self.dump_filename.replace( "%", "%%" ) + "_%d"
|
|
|
|
last_backup = 0
|
|
cummulated_size = os.stat( self.dump_filename ).st_size
|
|
while os.path.isfile( dump_filename_pattern % last_backup ):
|
|
cummulated_size += os.stat( dump_filename_pattern % last_backup ).st_size
|
|
last_backup += 1
|
|
while last_backup > 0:
|
|
os.rename( dump_filename_pattern % (last_backup - 1), dump_filename_pattern % last_backup )
|
|
last_backup -= 1
|
|
os.rename( self.dump_filename, dump_filename_pattern % 0 )
|
|
if cummulated_size > (1 << 30):
|
|
print("Warning: the cumulated backups size of '%s' is %d MByte" % (self.dump_filename,
|
|
cummulated_size / (1 << 20)))
|
|
# init is finnished now
|
|
|
|
# now it's time to create the hdf file
|
|
dump_file = None
|
|
if not os.path.isfile( self.dump_filename ):
|
|
if not init:
|
|
print("dump file \"%s\" vanished unexpectedly, creating new one" % self.dump_filename)
|
|
# have a look to the path and create necessary directories
|
|
dir_stack = [ ]
|
|
dir_trunk = os.path.dirname( os.path.abspath( self.dump_filename ) )
|
|
while dir_trunk != "" and not os.path.isdir( dir_trunk ):
|
|
dir_stack.append( os.path.basename( dir_trunk ) )
|
|
dir_trunk = os.path.dirname( dir_trunk )
|
|
try:
|
|
while len( dir_stack ):
|
|
dir_trunk = os.path.join( dir_trunk, dir_stack.pop( ) )
|
|
if os.path.isdir( dir_trunk ):
|
|
continue
|
|
os.mkdir( dir_trunk )
|
|
except OSError as e:
|
|
print(e)
|
|
print("could not create dump directory '%s', so hdf5 dumps disabled" % dir_trunk)
|
|
self.dump_filename = ""
|
|
self.dump_timeinterval = 0
|
|
return True
|
|
|
|
# create new dump file
|
|
dump_file = tables.open_file( self.dump_filename, mode="w", title="DAMARIS experiment data" )
|
|
# write scripts and other useful information
|
|
scriptgroup = dump_file.create_group( "/", "scripts", "Used Scripts" )
|
|
exp_text, res_text = self.sw.get_scripts( )
|
|
if self.si.exp_script:
|
|
dump_file.create_array( scriptgroup, "experiment_script", exp_text.encode('utf-8') )
|
|
if self.si.res_script:
|
|
dump_file.create_array( scriptgroup, "result_script", res_text.encode('utf-8') )
|
|
if self.si.backend_executable:
|
|
dump_file.create_array( scriptgroup, "backend_executable", self.si.backend_executable.encode('utf-8') )
|
|
if self.spool_dir:
|
|
dump_file.create_array( scriptgroup, "spool_directory", self.spool_dir.encode('utf-8') )
|
|
timeline_tablecols = numpy.recarray( 0, dtype=([ ("time", "S17"),
|
|
("experiments", "int64"),
|
|
("results", "int64") ]) )
|
|
timeline_table = dump_file.create_table( "/", "timeline", timeline_tablecols,
|
|
title="Timeline of Experiment" )
|
|
if tables.__version__[ 0 ] == "1":
|
|
logarray = dump_file.create_vlarray( where=dump_file.root,
|
|
name="log",
|
|
atom=tables.StringAtom( length=120 ),
|
|
title="log messages",
|
|
filters=tables.Filters( complevel=9, complib='zlib' ) )
|
|
else:
|
|
logarray = dump_file.create_earray( where=dump_file.root,
|
|
name="log",
|
|
atom=tables.StringAtom( itemsize=120 ),
|
|
shape=(0,),
|
|
title="log messages",
|
|
filters=tables.Filters( complevel=9, complib='zlib' ) )
|
|
|
|
if dump_file is None and os.path.isfile( self.dump_filename ) and tables.is_pytables_file( self.dump_filename ):
|
|
# take some data from dump file and repack
|
|
os.rename( self.dump_filename, self.dump_filename + ".bak" )
|
|
old_dump_file = tables.open_file( self.dump_filename + ".bak", mode="r+" )
|
|
if "data_pool" in old_dump_file.root:
|
|
old_dump_file.remove_node( where="/", name="data_pool", recursive=True )
|
|
old_dump_file.copy_file( self.dump_filename )
|
|
old_dump_file.close( )
|
|
del old_dump_file
|
|
os.remove( self.dump_filename + ".bak" )
|
|
# prepare for update
|
|
dump_file = tables.open_file( self.dump_filename, mode="r+" )
|
|
|
|
if dump_file is None:
|
|
# exit!
|
|
print("could not create dump directory '%s', so hdf5 dumps disabled" % dir_trunk)
|
|
self.dump_filename = ""
|
|
self.dump_timeinterval = 0
|
|
return True
|
|
|
|
# no undo please!
|
|
if dump_file.is_undo_enabled( ):
|
|
dump_file.disable_undo( )
|
|
|
|
# save the data!
|
|
self.data.write_hdf5( dump_file, where="/", name="data_pool",
|
|
complib=self.dump_complib, complevel=self.dump_complevel )
|
|
|
|
# now save additional information
|
|
e = self.si.data.get( "__recentexperiment", -1 ) + 1
|
|
r = self.si.data.get( "__recentresult", -1 ) + 1
|
|
timeline_table = dump_file.root.timeline
|
|
timeline_row = timeline_table.row
|
|
timeline_row[ "time" ] = time.strftime( "%Y%m%d %H:%M:%S" )
|
|
timeline_row[ "experiments" ] = e
|
|
timeline_row[ "results" ] = r
|
|
timeline_row.append( )
|
|
timeline_table.flush( )
|
|
|
|
# save log window information:
|
|
# also save backend's logfile information?
|
|
logtextbuffer = self.log.textbuffer
|
|
last_end = logtextbuffer.get_mark( "lastdumped" )
|
|
if last_end is None:
|
|
last_end = logtextbuffer.create_mark( "lastdumped", logtextbuffer.get_start_iter( ), left_gravity=True )
|
|
#logtext_start = logtextbuffer.get_iter_at_mark( last_end )
|
|
logtext_end = logtextbuffer.get_end_iter( )
|
|
logtextbuffer.move_mark( last_end, logtext_end )
|
|
# Create new iterators after the mark move
|
|
logtext_start = logtextbuffer.get_iter_at_mark( last_end )
|
|
logtext_end = logtextbuffer.get_end_iter( )
|
|
# recode from unicode
|
|
logtext = logtextbuffer.get_text( logtext_start, logtext_end, True )
|
|
# avoid circular references (seems to be necessary with gtk-2.12)
|
|
# del logtextbuffer, logtext_start, logtext_end, last_end
|
|
for l in logtext.splitlines( ):
|
|
dump_file.root.log.append( numpy.array( [ l ], dtype="S120" ) )
|
|
|
|
dump_file.flush( )
|
|
dump_file.close( )
|
|
self.last_dumped = time.time( )
|
|
del dump_file
|
|
|
|
return True
|
|
|
|
def pause_experiment( self, widget, data=None ):
|
|
"""
|
|
pause experiment execution (that means delay backend and let others run)
|
|
"""
|
|
if self.si is None:
|
|
return False
|
|
pause_state = self.toolbar_pause_button.get_active( )
|
|
if pause_state:
|
|
if self.state != DamarisGUI.Run_State:
|
|
return False
|
|
if self.spool_dir is None:
|
|
return False
|
|
no = self.si.data.get( "__recentresult", -1 ) + 1
|
|
result_pattern = os.path.join( self.spool_dir, "job.%09d.result" )
|
|
job_pattern = os.path.join( self.spool_dir, "job.%09d" )
|
|
while os.path.isfile( result_pattern % no ):
|
|
no += 1
|
|
i = 0
|
|
self.pause_files = [ ]
|
|
while i < 3 and os.path.isfile( job_pattern % (no + i) ):
|
|
pause_file = (job_pattern % (no + i)) + ".pause"
|
|
os.rename( job_pattern % (no + i), pause_file )
|
|
self.pause_files.append( pause_file )
|
|
i += 1
|
|
self.state = DamarisGUI.Pause_State
|
|
self.backend_statusbar_label.set_text( "Backend Paused" )
|
|
|
|
else:
|
|
if self.state != DamarisGUI.Pause_State:
|
|
return False
|
|
self.state = DamarisGUI.Run_State
|
|
for f in self.pause_files:
|
|
os.rename( f, f[ :-6 ] )
|
|
self.pause_files = None
|
|
self.backend_statusbar_label.set_text( "Backend Running" )
|
|
|
|
def stop_experiment( self, widget, data=None ):
|
|
if self.state in [ DamarisGUI.Run_State, DamarisGUI.Pause_State ]:
|
|
self.stop_experiment_flag.set()
|
|
if self.si is None:
|
|
return
|
|
still_running = [_f for _f in [ self.si.exp_handling, self.si.res_handling, self.si.back_driver ] if _f]
|
|
for r in still_running:
|
|
r.quit_flag.set( )
|
|
self.state = DamarisGUI.Stop_State
|
|
|
|
def documentation_init( self ):
|
|
|
|
self.doc_urls = {
|
|
"DAMARIS Manual": "file:///usr/share/doc/python3-damaris/doc/_build/html/index.html",
|
|
}
|
|
|
|
if os.path.isdir( "/usr/share/doc/python%d.%d-doc/html" % (sys.version_info[ :2 ]) ):
|
|
self.doc_urls[ "Python Docs" ] = "file:///usr/share/doc/python%d.%d-doc/html/index.html" % (
|
|
sys.version_info[ :2 ])
|
|
|
|
doc_index_url = None
|
|
self.doc_browser = None
|
|
|
|
def show_doc_menu( self, widget, data=None ):
|
|
"""
|
|
offer a wide variety of docs, prefer local installations
|
|
"""
|
|
if type( widget ) is gtk.MenuItem:
|
|
requested_doc = widget.get_child( ).get_text( )
|
|
else:
|
|
requested_doc = "Python DAMARIS"
|
|
|
|
if requested_doc in self.doc_urls and self.doc_urls[ requested_doc ] is not None:
|
|
if self.doc_browser is not None:
|
|
if not self.doc_browser.is_alive( ):
|
|
self.doc_browser.join( )
|
|
if self.doc_browser.my_webbrowser is not None:
|
|
print("new browser tab")
|
|
self.doc_browser.my_webbrowser.open_new_tab( self.doc_urls[ requested_doc ] )
|
|
else:
|
|
del self.doc_browser
|
|
self.doc_browser = start_browser( self.doc_urls[ requested_doc ] )
|
|
self.doc_browser.start( )
|
|
else:
|
|
self.doc_browser = start_browser( self.doc_urls[ requested_doc ] )
|
|
self.doc_browser.start( )
|
|
else:
|
|
print("missing docs for '%s'" % (requested_doc))
|
|
|
|
show_manual = show_doc_menu
|
|
|
|
|
|
class start_browser( threading.Thread ):
|
|
def __init__( self, url ):
|
|
threading.Thread.__init__( self, name="manual browser" )
|
|
self.my_webbrowser = None
|
|
self.my_webbrowser_process = None
|
|
self.start_url = url
|
|
|
|
def run( self ):
|
|
"""
|
|
start a webbrowser
|
|
"""
|
|
if sys.hexversion >= 0x02050000:
|
|
if sys.platform == "linux2" and self.my_webbrowser is None:
|
|
# try for debian linux
|
|
self.my_webbrowser = webbrowser.get( "x-www-browser" )
|
|
if self.my_webbrowser is None:
|
|
# this is what it should be everywhere!
|
|
self.my_webbrowser = webbrowser.get( )
|
|
if self.my_webbrowser is not None:
|
|
print("starting web browser (module webbrowser)")
|
|
self.my_webbrowser.open( self.start_url )
|
|
return True
|
|
# last resort
|
|
print("starting web browser (webbrowser.py)")
|
|
self.my_webbrowser_process = os.spawnl( os.P_NOWAIT,
|
|
sys.executable,
|
|
os.path.basename( sys.executable ),
|
|
"-c",
|
|
"import webbrowser\nwebbrowser.open('%s')" % self.start_url )
|
|
return True
|
|
|
|
|
|
class LogWindow:
|
|
"""
|
|
writes messages to the log window
|
|
"""
|
|
|
|
def __init__( self, xml_gui, damaris_gui ):
|
|
|
|
self.xml_gui = xml_gui
|
|
self.damaris_gui = damaris_gui
|
|
self.textview = self.xml_gui.get_object( "messages_textview" )
|
|
self.textview.connect( "key-press-event", self.textview_keypress)
|
|
font_desc = pango.FontDescription("Monospace 10")
|
|
self.textview.modify_font(font_desc)
|
|
self.textbuffer = self.textview.get_buffer( )
|
|
self.logstream = log
|
|
self.logstream.gui_log = self
|
|
self.last_timetag = None
|
|
self( "Started in directory %s\n" % os.getcwd( ) )
|
|
|
|
def __call__( self, message ):
|
|
timetag = time.time( )
|
|
gobject.idle_add( self.add_message_callback, timetag, message, priority=gobject.PRIORITY_LOW )
|
|
|
|
def add_message_callback( self, timetag, message ):
|
|
date_tag = ""
|
|
if self.last_timetag is None or (message != "\n" and self.last_timetag + 60 < timetag):
|
|
self.last_timetag = timetag
|
|
timetag_int = math.floor( timetag )
|
|
timetag_ms = int( (timetag - timetag_int) * 1000 )
|
|
date_tag = time.strftime( "%Y%m%d %H:%M:%S.%%03d:\n", time.localtime( timetag_int ) ) % timetag_ms
|
|
gdk.threads_enter( )
|
|
self.textbuffer.place_cursor( self.textbuffer.get_end_iter( ) )
|
|
self.textbuffer.insert_at_cursor( date_tag + str( message ) )
|
|
self.textview.scroll_to_mark( self.textbuffer.get_insert( ), 0.1 , False, 0.5, 0.5)
|
|
gdk.threads_leave( )
|
|
|
|
def textview_keypress( self, widget, event, data=None ):
|
|
if event.state & gdk.ModifierType.CONTROL_MASK != 0:
|
|
if event.keyval == gdk.keyval_from_name("f"):
|
|
self.damaris_gui.sw.search(None, None)
|
|
return True
|
|
|
|
return False
|
|
|
|
def __del__( self ):
|
|
self.logstream.gui_log = None
|
|
self.logstream = None
|
|
|
|
|
|
class ScriptWidgets:
|
|
def __init__( self, xml_gui ):
|
|
"""
|
|
initialize text widgets with text
|
|
"""
|
|
self.xml_gui = xml_gui
|
|
|
|
# my states
|
|
# editing enabled/disabled
|
|
self.editing_state = True
|
|
# keep in mind which filename was used for experiment script
|
|
self.exp_script_filename = None
|
|
# keep in mind which filename was used for result script
|
|
self.res_script_filename = None
|
|
|
|
# load syntax description for syntax highlighting (defined in python.xml)
|
|
#sl = SyntaxLoader( "python" )
|
|
# script buffers:
|
|
self.experiment_script_textview = self.xml_gui.get_object( "experiment_script_textview" )
|
|
self.data_handling_textview = self.xml_gui.get_object( "data_handling_textview" )
|
|
|
|
# create and set syntax-highlighting text-buffers as textview backends
|
|
self.experiment_script_textbuffer = gtksourceview2.Buffer()
|
|
self.data_handling_textbuffer = gtksourceview2.Buffer()
|
|
|
|
lm = gtksourceview2.LanguageManager()
|
|
langpython = lm.get_language("python")
|
|
|
|
self.experiment_script_textbuffer.set_language(langpython)
|
|
self.experiment_script_textbuffer.set_highlight_syntax(True)
|
|
self.data_handling_textbuffer.set_language(langpython)
|
|
self.data_handling_textbuffer.set_highlight_syntax(True)
|
|
|
|
fontdesc = pango.FontDescription("monospace")
|
|
|
|
self.experiment_script_textview.set_buffer( self.experiment_script_textbuffer )
|
|
self.experiment_script_textview.set_show_line_numbers(True)
|
|
self.experiment_script_textview.set_auto_indent(True)
|
|
self.experiment_script_textview.set_insert_spaces_instead_of_tabs(True)
|
|
self.experiment_script_textview.set_tab_width(4)
|
|
self.experiment_script_textview.set_smart_home_end(True)
|
|
self.experiment_script_textview.modify_font(fontdesc)
|
|
self.data_handling_textview.set_buffer( self.data_handling_textbuffer )
|
|
self.data_handling_textview.set_show_line_numbers(True)
|
|
self.data_handling_textview.set_auto_indent(True)
|
|
self.data_handling_textview.set_insert_spaces_instead_of_tabs(True)
|
|
self.data_handling_textview.set_tab_width(4)
|
|
self.data_handling_textview.set_smart_home_end(True)
|
|
self.data_handling_textview.modify_font(fontdesc)
|
|
|
|
keywords = """for if else elif in print try finally except global lambda not or pass def
|
|
class import from as return yield while continue break assert None True False AccumulatedValue
|
|
Accumulation MeasurementResult ADC_Result Experiment synchronize sleep result data
|
|
issubclass min max abs pow range xrange log_range lin_range staggered_range file
|
|
combine_ranges interleaved_range get_sampling_rate uses_statistics write_to_csv write_to_hdf
|
|
get_job_id get_description set_description get_xdata get_ydata set_xdata set_ydata
|
|
ttl_pulse ttls rf_pulse state_start state_end wait record loop_start loop_end set_frequency
|
|
set_pfg set_dac set_phase set_pts_local get_length set_title get_title add_lineplotdata
|
|
add_errorplotdata get_number_of_channels get_style set_style get_xlabel set_xlabel
|
|
get_ylabel set_ylabel"""
|
|
|
|
self.keywordsbuffer = gtksourceview2.Buffer()
|
|
self.keywordsbuffer.set_text(keywords)
|
|
|
|
compexp = self.experiment_script_textview.get_completion()
|
|
compres = self.data_handling_textview.get_completion()
|
|
|
|
compexp.set_property("show-headers", True)
|
|
compres.set_property("show-headers", True)
|
|
|
|
self.compwgen = gtksourceview2.CompletionWords.new("Keywords", None)
|
|
self.compwgen.register(self.keywordsbuffer)
|
|
self.compwgen.set_property("minimum-word-size", 1)
|
|
|
|
self.compwexp = gtksourceview2.CompletionWords.new("Words", None)
|
|
self.compwexp.register(self.experiment_script_textbuffer)
|
|
self.compwexp.set_property("minimum-word-size", 2)
|
|
|
|
self.compwres = gtksourceview2.CompletionWords.new("Words", None)
|
|
self.compwres.register(self.data_handling_textbuffer)
|
|
self.compwres.set_property("minimum-word-size", 2)
|
|
|
|
compexp.add_provider(self.compwgen)
|
|
compres.add_provider(self.compwgen)
|
|
|
|
compexp.add_provider(self.compwexp)
|
|
compres.add_provider(self.compwres)
|
|
|
|
# clipboard
|
|
self.main_clipboard = gtk.Clipboard( )
|
|
|
|
# statusbar
|
|
self.experiment_script_statusbar_label = self.xml_gui.get_object( "statusbar_experiment_script_label" )
|
|
self.data_handling_statusbar_label = self.xml_gui.get_object( "statusbar_data_handling_label" )
|
|
|
|
# footer with line and col indicators
|
|
self.experiment_script_line_indicator = self.xml_gui.get_object( "experiment_script_line_textfield" )
|
|
self.experiment_script_column_indicator = self.xml_gui.get_object( "experiment_script_column_textfield" )
|
|
self.data_handling_line_indicator = self.xml_gui.get_object( "data_handling_line_textfield" )
|
|
self.data_handling_column_indicator = self.xml_gui.get_object( "data_handling_column_textfield" )
|
|
|
|
# some event handlers
|
|
self.experiment_script_textbuffer.connect( "modified-changed", self.textviews_modified )
|
|
self.experiment_script_textview.connect_after( "move-cursor", self.textviews_moved )
|
|
self.experiment_script_textview.connect( "key-press-event", self.textviews_keypress )
|
|
self.experiment_script_textview.connect( "button-release-event", self.textviews_clicked )
|
|
self.data_handling_textbuffer.connect( "modified-changed", self.textviews_modified )
|
|
self.data_handling_textview.connect_after( "move-cursor", self.textviews_moved )
|
|
self.data_handling_textview.connect( "key-press-event", self.textviews_keypress )
|
|
self.data_handling_textview.connect( "button-release-event", self.textviews_clicked )
|
|
|
|
# and the editing toolbar buttons
|
|
self.toolbar_new_button = self.xml_gui.get_object( "toolbar_new_button" )
|
|
self.toolbar_open_button = self.xml_gui.get_object( "toolbar_open_file_button" )
|
|
self.toolbar_save_button = self.xml_gui.get_object( "toolbar_save_file_button" )
|
|
self.toolbar_save_as_button = self.xml_gui.get_object( "toolbar_save_as_button" )
|
|
self.toolbar_save_all_button = self.xml_gui.get_object( "toolbar_save_all_button" )
|
|
self.toolbar_check_scripts_button = self.xml_gui.get_object( "toolbar_check_scripts_button" )
|
|
self.toolbar_undo_button = self.xml_gui.get_object( "toolbar_undo_button" )
|
|
self.toolbar_redo_button = self.xml_gui.get_object( "toolbar_redo_button" )
|
|
self.toolbar_search_button = self.xml_gui.get_object("toolbar_search_button")
|
|
|
|
# main notebook
|
|
self.main_notebook = self.xml_gui.get_object( "main_notebook" )
|
|
|
|
# config toolbar
|
|
self.main_notebook.connect_after( "switch_page", self.notebook_page_switched )
|
|
|
|
# start with empty scripts
|
|
self.set_scripts( "", "" )
|
|
self.enable_editing( )
|
|
|
|
# public methods
|
|
|
|
def set_scripts( self, exp_script=None, res_script=None ):
|
|
# load buffers and set cursor to front
|
|
if exp_script is not None:
|
|
self.experiment_script_textbuffer.begin_not_undoable_action()
|
|
self.experiment_script_textbuffer.set_text( str( exp_script ) )
|
|
self.experiment_script_textbuffer.place_cursor( self.experiment_script_textbuffer.get_start_iter( ) )
|
|
self.experiment_script_textbuffer.set_modified( False )
|
|
self.experiment_script_textbuffer.end_not_undoable_action()
|
|
self.textviews_moved( self.experiment_script_textview )
|
|
if res_script is not None:
|
|
self.data_handling_textbuffer.begin_not_undoable_action()
|
|
self.data_handling_textbuffer.set_text( str( res_script ) )
|
|
self.data_handling_textbuffer.place_cursor( self.data_handling_textbuffer.get_start_iter( ) )
|
|
self.data_handling_textbuffer.set_modified( False )
|
|
self.data_handling_textbuffer.end_not_undoable_action()
|
|
self.textviews_moved( self.data_handling_textview )
|
|
self.set_toolbuttons_status( )
|
|
|
|
def get_scripts( self ):
|
|
"""
|
|
returns scripts
|
|
"""
|
|
exp_script = self.experiment_script_textbuffer.get_text( self.experiment_script_textbuffer.get_start_iter( ),
|
|
self.experiment_script_textbuffer.get_end_iter( ) , True).rstrip( )
|
|
res_script = self.data_handling_textbuffer.get_text( self.data_handling_textbuffer.get_start_iter( ),
|
|
self.data_handling_textbuffer.get_end_iter( ) , True).rstrip( )
|
|
return (exp_script, res_script)
|
|
|
|
def disable_editing( self ):
|
|
"""
|
|
disable editing (for running experiments)
|
|
"""
|
|
# disable buffers
|
|
self.editing_state = False
|
|
self.experiment_script_textview.set_sensitive( False )
|
|
self.data_handling_textview.set_sensitive( False )
|
|
self.set_toolbuttons_status( )
|
|
|
|
def enable_editing( self ):
|
|
"""
|
|
returns to editable state
|
|
"""
|
|
self.editing_state = True
|
|
self.experiment_script_textview.set_sensitive( True )
|
|
self.data_handling_textview.set_sensitive( True )
|
|
self.set_toolbuttons_status( )
|
|
|
|
# methods to update status and appearance
|
|
|
|
def set_toolbuttons_status( self ):
|
|
"""
|
|
|
|
ToDo: care about associated file names
|
|
"""
|
|
current_page = self.main_notebook.get_current_page( )
|
|
if self.editing_state and current_page in [ 0, 1 ]:
|
|
self.toolbar_new_button.set_sensitive( True )
|
|
self.toolbar_open_button.set_sensitive( True )
|
|
# find visible tab
|
|
exp_modified = self.experiment_script_textbuffer.get_modified( )
|
|
res_modified = self.data_handling_textbuffer.get_modified( )
|
|
enable_save = True
|
|
canundo = False
|
|
canredo = False
|
|
if current_page == 0:
|
|
enable_save = exp_modified and self.exp_script_filename is not None
|
|
canundo = self.experiment_script_textbuffer.can_undo()
|
|
canredo = self.experiment_script_textbuffer.can_redo()
|
|
elif current_page == 1:
|
|
enable_save = res_modified and self.res_script_filename is not None
|
|
canundo = self.data_handling_textbuffer.can_undo()
|
|
canredo = self.data_handling_textbuffer.can_redo()
|
|
self.toolbar_save_button.set_sensitive( enable_save )
|
|
self.toolbar_save_as_button.set_sensitive( True )
|
|
self.toolbar_save_all_button.set_sensitive( exp_modified or res_modified )
|
|
self.toolbar_check_scripts_button.set_sensitive( True )
|
|
self.toolbar_undo_button.set_sensitive(canundo)
|
|
self.toolbar_redo_button.set_sensitive(canredo)
|
|
self.toolbar_search_button.set_sensitive(True)
|
|
else:
|
|
# disable toolbar
|
|
self.toolbar_new_button.set_sensitive( False )
|
|
self.toolbar_open_button.set_sensitive( False )
|
|
self.toolbar_save_button.set_sensitive( False )
|
|
self.toolbar_save_as_button.set_sensitive( False )
|
|
self.toolbar_save_all_button.set_sensitive( False )
|
|
self.toolbar_check_scripts_button.set_sensitive( False )
|
|
self.toolbar_undo_button.set_sensitive(False)
|
|
self.toolbar_redo_button.set_sensitive(False)
|
|
self.toolbar_search_button.set_sensitive(current_page == 3)
|
|
|
|
if self.exp_script_filename:
|
|
exp_titlename = str( os.path.splitext( os.path.basename( self.exp_script_filename ) )[ 0 ] )
|
|
else:
|
|
exp_titlename = "unnamed"
|
|
if self.res_script_filename:
|
|
res_titlename = str( os.path.splitext( os.path.basename( self.res_script_filename ) )[ 0 ] )
|
|
else:
|
|
res_titlename = "unnamed"
|
|
window_title = "DAMARIS-%s %s,%s" % (__version__, exp_titlename, res_titlename)
|
|
self.xml_gui.get_object( "main_window" ).set_title( window_title )
|
|
|
|
# text widget related events
|
|
|
|
def check_script( self, widget, data=None ):
|
|
if not self.editing_state:
|
|
return 0
|
|
|
|
current_page = self.main_notebook.get_current_page( )
|
|
if current_page not in [ 0, 1 ]:
|
|
return 0
|
|
script = self.get_scripts( )[ current_page ]
|
|
try:
|
|
compile( script, ("Experiment Script", "Result Script")[ current_page ], "exec" )
|
|
except SyntaxError as se:
|
|
if current_page == 0:
|
|
tb = self.experiment_script_textbuffer
|
|
tv = self.experiment_script_textview
|
|
elif current_page == 1:
|
|
tb = self.data_handling_script_textbuffer
|
|
tv = self.data_handling_script_textview
|
|
|
|
ln = se.lineno
|
|
lo = se.offset
|
|
# reguard http://bugs.python.org/issue1778
|
|
if type( ln ) is not int:
|
|
ln = 0
|
|
if type( lo ) is not int:
|
|
lo = 0
|
|
print("Syntax Error:\n%s in %s at line %d, offset %d" % (
|
|
str( se ), se.filename, ln, lo) + "\n(ToDo: Dialog)")
|
|
if ln > 0 and ln <= tb.get_line_count( ):
|
|
new_place = tb.get_iter_at_line_offset( ln - 1, 0 )
|
|
if lo > 0 and lo <= new_place.get_chars_in_line( ):
|
|
new_place.set_line_offset( lo )
|
|
tb.place_cursor( new_place )
|
|
tv.scroll_to_iter( new_place, 0.2, False, 0, 0 )
|
|
except Exception as e:
|
|
print("Compilation Error:\n" + str( e ) + "\n(ToDo: Dialog)")
|
|
|
|
def notebook_page_switched( self, notebook, page, pagenumber ):
|
|
self.set_toolbuttons_status( )
|
|
|
|
def column_line_widgets_changed_event( self, data=None ):
|
|
page = self.main_notebook.get_current_page( )
|
|
if page == 0:
|
|
textview = self.experiment_script_textview
|
|
column_indicator = self.experiment_script_column_indicator
|
|
newline = self.experiment_script_line_indicator.get_value_as_int( ) - 1
|
|
newcol = column_indicator.get_value_as_int( ) - 1
|
|
elif page == 1:
|
|
textview = self.data_handling_textview
|
|
column_indicator = self.data_handling_column_indicator
|
|
newline = self.data_handling_line_indicator.get_value_as_int( ) - 1
|
|
newcol = column_indicator.get_value_as_int( ) - 1
|
|
else:
|
|
print("unknown line/column selector")
|
|
return False
|
|
|
|
textbuffer = textview.get_buffer( )
|
|
new_place = textbuffer.get_iter_at_line( newline )
|
|
if not newcol > new_place.get_chars_in_line( ):
|
|
new_place.set_line_offset( newcol )
|
|
else:
|
|
column_indicator.set_value( 1 )
|
|
if len( textbuffer.get_selection_bounds( ) ) != 0:
|
|
textbuffer.move_mark_by_name( "insert", new_place )
|
|
else:
|
|
textbuffer.place_cursor( new_place )
|
|
|
|
textview.scroll_mark_onscreen( textbuffer.get_insert( ) )
|
|
textview.grab_focus( )
|
|
return True
|
|
|
|
def textviews_modified( self, widget = None, event=None ):
|
|
# mix into toolbar affairs
|
|
if type(widget) is gtksourceview2.View:
|
|
return self.textviews_moved( widget )
|
|
else:
|
|
return False
|
|
|
|
|
|
def textviews_clicked( self, widget, event ):
|
|
return self.textviews_moved( widget )
|
|
|
|
def textviews_moved( self, widget, text=None, count=None, ext_selection=None, data=None ):
|
|
|
|
textbuffer = widget.get_buffer( )
|
|
cursor_mark = textbuffer.get_insert( )
|
|
cursor_iter = textbuffer.get_iter_at_mark( cursor_mark )
|
|
if textbuffer is self.experiment_script_textbuffer:
|
|
line_indicator = self.experiment_script_line_indicator
|
|
column_indicator = self.experiment_script_column_indicator
|
|
if textbuffer is self.data_handling_textbuffer:
|
|
line_indicator = self.data_handling_line_indicator
|
|
column_indicator = self.data_handling_column_indicator
|
|
|
|
# do only necessary updates!
|
|
li_range_new = textbuffer.get_end_iter( ).get_line( ) + 1
|
|
if line_indicator.get_range( )[ 1 ] != li_range_new:
|
|
line_indicator.set_range( 1, li_range_new )
|
|
ci_range_new = cursor_iter.get_chars_in_line( ) + 1
|
|
if column_indicator.get_range( )[ 1 ] != ci_range_new:
|
|
column_indicator.set_range( 1, ci_range_new )
|
|
cursor_line = cursor_iter.get_line( ) + 1
|
|
cursor_lineoffset = cursor_iter.get_line_offset( ) + 1
|
|
if line_indicator.get_value( ) != cursor_line:
|
|
line_indicator.set_value( cursor_line )
|
|
if column_indicator.get_value( ) != cursor_lineoffset:
|
|
column_indicator.set_value( cursor_lineoffset )
|
|
self.set_toolbuttons_status( )
|
|
return False
|
|
|
|
def textviews_keypress( self, widget, event, data=None ):
|
|
"""
|
|
helpful tab and return key functions
|
|
"""
|
|
if event.state & gdk.ModifierType.CONTROL_MASK != 0:
|
|
if event.keyval == gdk.keyval_from_name( "s" ):
|
|
# save buffer
|
|
page = self.main_notebook.get_current_page( )
|
|
if (self.exp_script_filename, self.res_script_filename)[ page ] is None:
|
|
self.save_file_as( )
|
|
else:
|
|
self.save_file( )
|
|
return True
|
|
elif event.keyval == gdk.keyval_from_name( "S" ):
|
|
# save both buffers
|
|
self.save_all_files( None, None )
|
|
return True
|
|
elif event.keyval == gdk.keyval_from_name("f"):
|
|
self.search(None, None)
|
|
return True
|
|
return False
|
|
|
|
#Handle Backspace (delete more than one space on line beginning)
|
|
if event.keyval == 0xFF08:
|
|
textbuffer = widget.get_buffer( )
|
|
|
|
if textbuffer.get_has_selection():
|
|
return False
|
|
|
|
cursor_mark = textbuffer.get_insert( )
|
|
cursor_iter = textbuffer.get_iter_at_mark( cursor_mark )
|
|
|
|
linestart_iter = cursor_iter.copy( )
|
|
linestart_iter.set_line_offset( 0 )
|
|
|
|
linebegin = textbuffer.get_text( linestart_iter, cursor_iter, True ).expandtabs(4)
|
|
|
|
if linebegin.isspace() and len(linebegin) > 0:
|
|
linebegin = ' ' * int((len( linebegin ) - 1) / 4) * 4
|
|
textbuffer.delete( linestart_iter, cursor_iter )
|
|
textbuffer.insert( linestart_iter, linebegin )
|
|
return True
|
|
|
|
elif event.keyval == 0xFF0D:
|
|
textbuffer = widget.get_buffer( )
|
|
|
|
if textbuffer.get_has_selection():
|
|
return False
|
|
|
|
cursor_mark = textbuffer.get_insert( )
|
|
cursor_iter = textbuffer.get_iter_at_mark( cursor_mark )
|
|
|
|
lastchar_iter = cursor_iter.copy()
|
|
lastchar = lastchar_iter.backward_char( )
|
|
|
|
if not lastchar_iter.get_char( ) == ":":
|
|
return False
|
|
|
|
linestart_iter = cursor_iter.copy( )
|
|
linestart_iter.set_line_offset( 0 )
|
|
spaceend_iter = linestart_iter.copy( )
|
|
while (not spaceend_iter.ends_line( ) and not spaceend_iter.is_end( ) and spaceend_iter.get_char( ).isspace( )):
|
|
spaceend_iter.forward_char( )
|
|
linebegin = textbuffer.get_text( linestart_iter, spaceend_iter, True ).expandtabs(4)
|
|
indent_length = int((int(len(linebegin)/4)+1)*4)
|
|
|
|
intext = "\n" + (" " * indent_length)
|
|
|
|
textbuffer.insert(cursor_iter, intext)
|
|
widget.scroll_to_mark( cursor_mark, 0.0, False, 0.5, 0.5 )
|
|
|
|
return True
|
|
|
|
|
|
#self.textviews_moved(widget)
|
|
return False
|
|
|
|
def load_file_as_unicode( self, script_filename ):
|
|
script_file = open( script_filename, "r", encoding="iso-8859-15" )
|
|
script_string = ""
|
|
for line in script_file:
|
|
script_string += line # str( line, encoding="iso-8859-15", errors="replace" )
|
|
script_file.close( )
|
|
return script_string
|
|
|
|
def open_file( self, widget, Data=None ):
|
|
"""
|
|
do the open file dialog, if necessary ask for save
|
|
"""
|
|
# ignore
|
|
if not self.editing_state:
|
|
return 0
|
|
|
|
# Determining the tab which is currently open
|
|
current_page = self.main_notebook.get_current_page( )
|
|
if current_page == 0:
|
|
open_dialog_title = "Open Experiment Script..."
|
|
modified = self.experiment_script_textbuffer.get_modified( )
|
|
elif current_page == 1:
|
|
open_dialog_title = "Open Result Script..."
|
|
modified = self.data_handling_textbuffer.get_modified( )
|
|
else:
|
|
return 0
|
|
|
|
if modified:
|
|
question = gtk.MessageDialog(parent=self.xml_gui.get_object( "main_window"),
|
|
type=gtk.MessageType.WARNING,
|
|
flags=gtk.DialogFlags.MODAL,
|
|
buttons=("_Yes", gtk.ResponseType.YES,
|
|
"_No", gtk.ResponseType.NO,
|
|
"_Cancel", gtk.ResponseType.CANCEL))
|
|
|
|
|
|
question.set_markup("The file has been changed. Save changes?")
|
|
|
|
response = question.run()
|
|
question.destroy()
|
|
|
|
if response == gtk.ResponseType.YES:
|
|
self.save_file()
|
|
elif response == gtk.ResponseType.CANCEL:
|
|
return 0
|
|
|
|
def response( self, response_id, script_widget ):
|
|
if response_id == gtk.ResponseType.OK:
|
|
file_name = dialog.get_filename( )
|
|
if file_name is None:
|
|
return
|
|
|
|
script_filename = os.path.abspath( file_name )
|
|
if not os.access( script_filename, os.R_OK ):
|
|
self.outer_space.show_error_dialog( "File I/O Error", "Cannot read from file %s" % script_filename )
|
|
return True
|
|
|
|
script_string = script_widget.load_file_as_unicode( script_filename )
|
|
|
|
if script_widget.main_notebook.get_current_page( ) == 0:
|
|
script_widget.exp_script_filename = script_filename
|
|
script_widget.set_scripts( script_string, None )
|
|
elif script_widget.main_notebook.get_current_page( ) == 1:
|
|
script_widget.res_script_filename = script_filename
|
|
script_widget.set_scripts( None, script_string )
|
|
|
|
return True
|
|
|
|
|
|
parent_window = self.xml_gui.get_object( "main_window" )
|
|
dialog = gtk.FileChooserDialog( title=open_dialog_title,
|
|
parent=parent_window,
|
|
action=gtk.FileChooserAction.OPEN,
|
|
buttons=("_Open", gtk.ResponseType.OK, "_Cancel", gtk.ResponseType.CANCEL)
|
|
)
|
|
dialog.set_default_response( gtk.ResponseType.OK )
|
|
dialog.set_select_multiple( False )
|
|
# Event-Handler for responce-signal (when one of the button is pressed)
|
|
dialog.connect( "response", response, self )
|
|
|
|
dialog.run( )
|
|
dialog.destroy( )
|
|
|
|
# update title and so on...
|
|
|
|
return True
|
|
|
|
def save_file( self, widget=None, Data=None ):
|
|
"""
|
|
save file to associated filename
|
|
"""
|
|
# ignore
|
|
if not self.editing_state:
|
|
return 0
|
|
|
|
# Determining the tab which is currently open
|
|
current_page = self.main_notebook.get_current_page( )
|
|
if current_page == 0:
|
|
filename = self.exp_script_filename
|
|
elif current_page == 1:
|
|
filename = self.res_script_filename
|
|
else:
|
|
return 0
|
|
|
|
if filename is None:
|
|
return 0
|
|
|
|
# save file
|
|
if current_page == 0:
|
|
script = self.get_scripts( )[ 0 ]
|
|
elif current_page == 1:
|
|
script = self.get_scripts( )[ 1 ]
|
|
else:
|
|
return 0
|
|
|
|
# encode from unicode to iso-8859-15
|
|
filecontents = script
|
|
openfile = open( filename, "w", encoding = "iso-8859-15" )
|
|
openfile.write( filecontents )
|
|
openfile.close()
|
|
|
|
if current_page == 0:
|
|
self.experiment_script_textbuffer.set_modified( False )
|
|
elif current_page == 1:
|
|
self.data_handling_textbuffer.set_modified( False )
|
|
self.set_toolbuttons_status( )
|
|
|
|
|
|
def save_file_as( self, widget=None, Data=None ):
|
|
|
|
def response( self, response_id, script_widget ):
|
|
if response_id == gtk.ResponseType.OK:
|
|
file_name = dialog.get_filename( )
|
|
if file_name is None:
|
|
return True
|
|
|
|
absfilename = os.path.abspath( file_name )
|
|
if os.access( file_name, os.F_OK ):
|
|
question = gtk.MessageDialog(parent=script_widget.xml_gui.get_object( "main_window"),
|
|
type=gtk.MessageType.WARNING,
|
|
flags=gtk.DialogFlags.MODAL,
|
|
buttons=("_Yes", gtk.ResponseType.YES, "_No", gtk.ResponseType.NO))
|
|
question.set_markup("The file already exists. Do you want to overwrite the existing file and delete its contents?")
|
|
|
|
response = question.run()
|
|
question.destroy()
|
|
|
|
if response != gtk.ResponseType.YES:
|
|
return True
|
|
|
|
current_page = script_widget.main_notebook.get_current_page( )
|
|
if current_page == 0:
|
|
script_widget.exp_script_filename = absfilename
|
|
elif current_page == 1:
|
|
script_widget.res_script_filename = absfilename
|
|
script_widget.save_file( )
|
|
|
|
return True
|
|
|
|
# Determining the tab which is currently open
|
|
|
|
current_page = self.main_notebook.get_current_page( )
|
|
if current_page == 0:
|
|
dialog_title = "Save Experiment Script As..."
|
|
elif current_page == 1:
|
|
dialog_title = "Save Result Script As..."
|
|
else:
|
|
return
|
|
|
|
parent_window = self.xml_gui.get_object( "main_window" )
|
|
dialog = gtk.FileChooserDialog( title=dialog_title,
|
|
parent=parent_window,
|
|
action=gtk.FileChooserAction.SAVE,
|
|
buttons=("_Save", gtk.ResponseType.OK, "_Cancel", gtk.ResponseType.CANCEL) )
|
|
|
|
dialog.set_default_response( gtk.ResponseType.OK )
|
|
dialog.set_select_multiple( False )
|
|
|
|
# Event-Handler for responce-signal (when one of the button is pressed)
|
|
dialog.connect( "response", response, self )
|
|
|
|
dialog.run( )
|
|
dialog.destroy( )
|
|
|
|
return True
|
|
|
|
def save_all_files( self, widget, Data=None ):
|
|
|
|
current_page = self.main_notebook.get_current_page( )
|
|
|
|
# change page and call save dialog
|
|
self.main_notebook.set_current_page( 0 )
|
|
if self.exp_script_filename is None:
|
|
self.save_file_as( )
|
|
else:
|
|
self.save_file( )
|
|
|
|
self.main_notebook.set_current_page( 1 )
|
|
if self.res_script_filename is None:
|
|
self.save_file_as( )
|
|
else:
|
|
self.save_file( )
|
|
|
|
self.main_notebook.set_current_page( current_page )
|
|
|
|
def undo(self, widget=None, Data=None ):
|
|
if not self.editing_state:
|
|
return 0
|
|
current_page = self.main_notebook.get_current_page( )
|
|
if current_page == 0:
|
|
self.experiment_script_textview.do_undo(self.experiment_script_textview)
|
|
elif current_page == 1:
|
|
self.data_handling_textview.do_undo(self.data_handling_textview)
|
|
self.set_toolbuttons_status()
|
|
|
|
def redo(self, widget=None, Data=None ):
|
|
if not self.editing_state:
|
|
return 0
|
|
current_page = self.main_notebook.get_current_page( )
|
|
if current_page == 0:
|
|
self.experiment_script_textview.do_redo(self.experiment_script_textview)
|
|
elif current_page == 1:
|
|
self.data_handling_textview.do_redo(self.data_handling_textview)
|
|
self.set_toolbuttons_status()
|
|
|
|
def search(self, widget=None, Data=None):
|
|
def response(script_widget, response_id, damsw, tb ):
|
|
if response_id != -1 and response_id != 1:
|
|
script_widget.destroy()
|
|
return True
|
|
|
|
current_page = damsw.main_notebook.get_current_page( )
|
|
|
|
startiter = None
|
|
enditer = None
|
|
logview = None
|
|
logbuffer = None
|
|
|
|
if current_page == 0:
|
|
if damsw.experiment_script_textbuffer.get_has_selection():
|
|
startiter, enditer = damsw.experiment_script_textbuffer.get_selection_bounds()
|
|
else:
|
|
mark = damsw.experiment_script_textbuffer.get_insert()
|
|
startiter = damsw.experiment_script_textbuffer.get_iter_at_mark(mark)
|
|
enditer = startiter
|
|
elif current_page == 1:
|
|
if damsw.data_handling_textbuffer.get_has_selection():
|
|
startiter, enditer = damsw.data_handling_textbuffer.get_selection_bounds()
|
|
else:
|
|
mark = damsw.data_handling_textbuffer.get_insert()
|
|
startiter = damsw.data_handling_textbuffer.get_iter_at_mark(mark)
|
|
enditer = startiter
|
|
elif current_page == 3:
|
|
logview = damsw.xml_gui.get_object( "messages_textview" )
|
|
logbuffer = logview.get_buffer()
|
|
if logbuffer.get_has_selection():
|
|
startiter, enditer = logbuffer.get_selection_bounds()
|
|
else:
|
|
mark = logbuffer.get_insert()
|
|
startiter = logbuffer.get_iter_at_mark(mark)
|
|
enditer = startiter
|
|
else:
|
|
script_widget.destroy()
|
|
return True
|
|
|
|
searchstring = tb.get_text()
|
|
|
|
if len(searchstring) > 0:
|
|
|
|
match_start = None
|
|
match_end = None
|
|
|
|
try:
|
|
if response_id == -1:
|
|
match_start, match_end = startiter.backward_search(searchstring, gtk.TextSearchFlags.TEXT_ONLY)
|
|
elif response_id == 1:
|
|
match_start, match_end = enditer.forward_search(searchstring, gtk.TextSearchFlags.TEXT_ONLY)
|
|
except:
|
|
pass
|
|
|
|
if match_start is not None and match_end is not None:
|
|
if current_page == 0:
|
|
damsw.experiment_script_textbuffer.select_range(match_start, match_end)
|
|
damsw.experiment_script_textview.scroll_to_iter(match_start, 0.2)
|
|
damsw.textviews_moved(damsw.experiment_script_textview)
|
|
elif current_page == 1:
|
|
damsw.data_handling_textbuffer.select_range(match_start, match_end)
|
|
damsw.data_handling_textview.scroll_to_iter(match_start, 0.2)
|
|
damsw.textviews_moved(damsw.data_handling_textview)
|
|
elif current_page == 3:
|
|
logbuffer.select_range(match_start, match_end)
|
|
logview.scroll_to_iter(match_start, 0.2)
|
|
|
|
|
|
|
|
|
|
dialog = gtk.Dialog(title="Search", parent=self.xml_gui.get_object("main_window"), flags=0,
|
|
buttons=("_Close", gtk.ResponseType.CLOSE, "Find last", -1, "Find next", 1))
|
|
cont = dialog.get_content_area()
|
|
|
|
markedstring = ""
|
|
current_page = self.main_notebook.get_current_page( )
|
|
|
|
startit = None
|
|
endit = None
|
|
logbuffer = None
|
|
|
|
if current_page == 0:
|
|
if self.experiment_script_textbuffer.get_has_selection():
|
|
startit, endit = self.experiment_script_textbuffer.get_selection_bounds()
|
|
markedstring = self.experiment_script_textbuffer.get_text(startit, endit, True)
|
|
elif current_page == 1:
|
|
if self.data_handling_textbuffer.get_has_selection():
|
|
startit, endit = self.data_handling_textbuffer.get_selection_bounds()
|
|
markedstring = self.data_handling_textbuffer.get_text(startit, endit, True)
|
|
elif current_page == 3:
|
|
logview = self.xml_gui.get_object( "messages_textview" )
|
|
logbuffer = logview.get_buffer()
|
|
if logbuffer.get_has_selection():
|
|
startit, endit = logbuffer.get_selection_bounds()
|
|
markedstring = logbuffer.get_text(startit, endit, True)
|
|
|
|
markedstring = markedstring.strip()
|
|
|
|
textbox = gtk.Entry()
|
|
textbox.set_text(markedstring)
|
|
|
|
cont.add(textbox)
|
|
|
|
dialog.set_default_response(1)
|
|
dialog.connect("response", response, self, textbox)
|
|
dialog.set_resizable(False)
|
|
|
|
dialog.show_all()
|
|
|
|
if startit is not None and endit is not None:
|
|
if current_page == 0:
|
|
self.experiment_script_textbuffer.select_range(startit, endit)
|
|
elif current_page == 1:
|
|
self.data_handling_textbuffer.select_range(startit, endit)
|
|
elif current_page == 3:
|
|
logbuffer.select_range(startit, endit)
|
|
|
|
|
|
|
|
|
|
def new_file( self, widget, Data=None ):
|
|
|
|
if not self.editing_state:
|
|
return 0
|
|
current_page = self.main_notebook.get_current_page( )
|
|
if current_page == 0:
|
|
if self.experiment_script_textbuffer.get_modified( ):
|
|
question = gtk.MessageDialog(parent=self.xml_gui.get_object( "main_window"),
|
|
type=gtk.MessageType.WARNING,
|
|
flags=gtk.DialogFlags.MODAL,
|
|
buttons=("_Yes", gtk.ResponseType.YES, "_No", gtk.ResponseType.NO, "_Cancel", gtk.ResponseType.CANCEL))
|
|
question.set_markup("The file has been changed. Save changes?")
|
|
|
|
response = question.run()
|
|
question.destroy()
|
|
|
|
if response == gtk.ResponseType.YES:
|
|
self.save_file()
|
|
elif response == gtk.ResponseType.CANCEL:
|
|
return 0
|
|
|
|
self.set_scripts( "", None )
|
|
self.exp_script_filename = None
|
|
elif current_page == 1:
|
|
if self.data_handling_textbuffer.get_modified( ):
|
|
question = gtk.MessageDialog(parent=self.xml_gui.get_object( "main_window"),
|
|
type=gtk.MessageType.WARNING,
|
|
flags=gtk.DialogFlags.MODAL,
|
|
buttons=("_Yes", gtk.ResponseType.YES, "_No", gtk.ResponseType.NO, "_Cancel", gtk.ResponseType.CANCEL))
|
|
question.set_markup("The file has been changed. Save changes?")
|
|
|
|
response = question.run()
|
|
question.destroy()
|
|
|
|
if response == gtk.ResponseType.YES:
|
|
self.save_file()
|
|
elif response == gtk.ResponseType.CANCEL:
|
|
return 0
|
|
|
|
self.set_scripts( None, "" )
|
|
self.res_script_filename = None
|
|
self.set_toolbuttons_status( )
|
|
|
|
|
|
class ConfigTab:
|
|
"""
|
|
by now all values are saved in the GUI widgets
|
|
"""
|
|
|
|
def __init__( self, xml_gui ):
|
|
self.xml_gui = xml_gui
|
|
|
|
self.configname = "damaris/python-damaris.xml"
|
|
self.system_default_filename = None
|
|
self.system_backend_folder = "/usr/lib/damaris/backends/"
|
|
if sys.platform[ :5 ] == "linux" or "darwin":
|
|
xdg_dirs = xdg.BaseDirectory.xdg_config_dirs
|
|
xdg_dirs.remove( xdg.BaseDirectory.xdg_config_home )
|
|
self.user_default_filename = os.path.join( xdg.BaseDirectory.xdg_config_home, self.configname )
|
|
self.system_default_filenames = [ os.path.join( syscfg, self.configname ) for syscfg in xdg_dirs ]
|
|
|
|
self.config_start_backend_checkbutton = self.xml_gui.get_object( "start_backend_checkbutton" )
|
|
self.config_backend_executable_entry = self.xml_gui.get_object( "backend_executable_entry" )
|
|
self.config_spool_dir_entry = self.xml_gui.get_object( "spool_dir_entry" )
|
|
self.config_start_experiment_script_checkbutton = self.xml_gui.get_object(
|
|
"start_experiment_script_checkbutton" )
|
|
self.config_start_result_script_checkbutton = self.xml_gui.get_object( "start_result_script_checkbutton" )
|
|
self.config_del_results_after_processing_checkbutton = self.xml_gui.get_object(
|
|
"del_results_after_processing_checkbutton" )
|
|
self.config_del_jobs_after_execution_checkbutton = self.xml_gui.get_object(
|
|
"del_jobs_after_execution_checkbutton" )
|
|
self.config_data_pool_name_entry = self.xml_gui.get_object( "data_pool_name_entry" )
|
|
self.config_data_pool_name_entry.set_tooltip_text("Use %(date)s for ISO date or %(datetime)s for ISO date+time")
|
|
self.config_data_pool_write_interval_entry = self.xml_gui.get_object( "data_pool_write_interval_entry" )
|
|
self.config_data_pool_complib = self.xml_gui.get_object( "CompLibs" )
|
|
self.config_data_pool_complib.set_tooltip_text("Use zlib for best compatibility with other tools")
|
|
self.config_data_pool_comprate = self.xml_gui.get_object( "CompRatio" )
|
|
self.config_data_pool_comprate.set_tooltip_text("Use 1 for best compression, 9 for best speed; 3 is very usable")
|
|
self.config_info_textview = self.xml_gui.get_object( "info_textview" )
|
|
self.config_script_font_button = self.xml_gui.get_object( "script_fontbutton" )
|
|
|
|
# insert version informations
|
|
components_text = """
|
|
operating system %(os)s
|
|
gtk version %(gtk)s
|
|
glib version %(glib)s
|
|
python version %(python)s
|
|
matplotlib version %(matplotlib)s, %(matplotlib_backend)s
|
|
numpy version %(numpy)s
|
|
pytables version %(pytables)s, using: %(pytables_libs)s
|
|
pygobject version %(pygobject)s
|
|
"""
|
|
if hasattr( gobject, "glib_version" ):
|
|
glib_version = "%d.%d.%d" % gobject.glib_version
|
|
else:
|
|
glib_version = "? (no pygobject module)"
|
|
if hasattr( gobject, "pygobject_version" ):
|
|
pygobject_version = "%d.%d.%d" % gobject.pygobject_version
|
|
else:
|
|
pygobject_version = "? (no gobject version number)"
|
|
|
|
numpy_version = "none"
|
|
try:
|
|
import numpy
|
|
except ImportError:
|
|
pass
|
|
else:
|
|
numpy_version = numpy.__version__
|
|
|
|
components_versions = {
|
|
"os": platform.platform( ),
|
|
"gtk": "{}.{}.{}".format(gtk.MAJOR_VERSION, gtk.MINOR_VERSION, gtk.MICRO_VERSION),
|
|
"glib": glib_version,
|
|
"python": sys.version,
|
|
"matplotlib": matplotlib.__version__,
|
|
"matplotlib_backend": FigureCanvas.__name__[ 12: ],
|
|
"numpy": numpy_version,
|
|
"pytables": tables.__version__,
|
|
"pytables_libs": "",
|
|
"pygobject": pygobject_version
|
|
}
|
|
|
|
# pytables modules:
|
|
# find compression extensions for combo box and write version numbers
|
|
# list is taken from ValueError output of tables.which_lib_version("")
|
|
model = self.config_data_pool_complib.get_model( )
|
|
for libname in ('hdf5', 'zlib', 'lzo', 'blosc', 'bzip2', 'szip'):
|
|
version_info = None
|
|
try:
|
|
version_info = tables.which_lib_version( libname )
|
|
except ValueError:
|
|
continue
|
|
if version_info:
|
|
components_versions[ "pytables_libs" ] += "\n * %s: %s" % (libname, str( version_info[1] ))
|
|
if libname != "hdf5":
|
|
# a compression library, add it to combo box
|
|
if isinstance( model, gtk.ListStore ):
|
|
model.append( [ libname ] )
|
|
elif isinstance( model, gtk.TreeStore ):
|
|
model.append( None, [ libname ] )
|
|
else:
|
|
print("cannot append compression lib name to %s" % model.__class__.__name__)
|
|
|
|
|
|
# debug message
|
|
if debug:
|
|
print("DAMARIS", __version__)
|
|
print(components_text % components_versions)
|
|
|
|
# set no compression as default...
|
|
self.config_data_pool_complib.set_active( 0 )
|
|
|
|
info_textbuffer = self.config_info_textview.get_buffer( )
|
|
info_text = info_textbuffer.get_text( info_textbuffer.get_start_iter( ), info_textbuffer.get_end_iter( ), True )
|
|
info_text %= { "moduleversions": components_text % components_versions, "damarisversion": __version__ }
|
|
info_textbuffer.set_text( info_text )
|
|
del info_textbuffer, info_text, components_text, components_versions
|
|
|
|
for self.system_default_filename in self.system_default_filenames:
|
|
if self.system_default_filename:
|
|
if os.access( self.system_default_filename, os.R_OK ):
|
|
self.load_config( self.system_default_filename )
|
|
else:
|
|
print("can not read system defaults from %s, ask your instrument responsible if required" % self.system_default_filename)
|
|
|
|
self.config_from_system = self.get( )
|
|
self.load_config( )
|
|
|
|
def get( self ):
|
|
"""
|
|
returns a dictionary of actual config values
|
|
"""
|
|
complib_iter = self.config_data_pool_complib.get_active_iter( )
|
|
complib = self.config_data_pool_complib.get_model( ).get_value( complib_iter, 0 )
|
|
return {
|
|
"start_backend": self.config_start_backend_checkbutton.get_active( ),
|
|
"start_result_script": self.config_start_result_script_checkbutton.get_active( ),
|
|
"start_experiment_script": self.config_start_experiment_script_checkbutton.get_active( ),
|
|
"spool_dir": self.config_spool_dir_entry.get_text( ),
|
|
"backend_executable": self.config_backend_executable_entry.get_text( ),
|
|
"data_pool_name": self.config_data_pool_name_entry.get_text( ),
|
|
"del_results_after_processing": self.config_del_results_after_processing_checkbutton.get_active( ),
|
|
"del_jobs_after_execution": self.config_del_jobs_after_execution_checkbutton.get_active( ),
|
|
"data_pool_write_interval": self.config_data_pool_write_interval_entry.get_text( ),
|
|
"data_pool_complib": complib,
|
|
"data_pool_comprate": self.config_data_pool_comprate.get_value_as_int( ),
|
|
"script_font": self.config_script_font_button.get_font_name( ),
|
|
"adc_bit_depth": ADC_Result.default_bit_depth
|
|
}
|
|
|
|
def set( self, config ):
|
|
if "start_backend" in config:
|
|
self.config_start_backend_checkbutton.set_active( config[ "start_backend" ] )
|
|
if "start_experiment_script" in config:
|
|
self.config_start_experiment_script_checkbutton.set_active( config[ "start_experiment_script" ] )
|
|
if "start_result_script" in config:
|
|
self.config_start_result_script_checkbutton.set_active( config[ "start_result_script" ] )
|
|
if "spool_dir" in config:
|
|
self.config_spool_dir_entry.set_text( config[ "spool_dir" ] )
|
|
if "backend_executable" in config:
|
|
self.config_backend_executable_entry.set_text( config[ "backend_executable" ] )
|
|
if "del_results_after_processing" in config:
|
|
self.config_del_results_after_processing_checkbutton.set_active( config[ "del_results_after_processing" ] )
|
|
if "del_jobs_after_execution" in config:
|
|
self.config_del_jobs_after_execution_checkbutton.set_active( config[ "del_jobs_after_execution" ] )
|
|
if "data_pool_write_interval" in config:
|
|
self.config_data_pool_write_interval_entry.set_text( config[ "data_pool_write_interval" ] )
|
|
if "data_pool_name" in config:
|
|
self.config_data_pool_name_entry.set_text( config[ "data_pool_name" ] )
|
|
if "data_pool_comprate" in config:
|
|
self.config_data_pool_comprate.set_value( float( config[ "data_pool_comprate" ] ) )
|
|
if "script_font" in config:
|
|
self.config_script_font_button.set_font_name( config[ "script_font" ] )
|
|
self.set_script_font_handler( None )
|
|
if "adc_bit_depth" in config:
|
|
try:
|
|
ADC_Result.default_bit_depth = int(config["adc_bit_depth"])
|
|
except (ValueError, TypeError):
|
|
pass
|
|
if "data_pool_complib" in config:
|
|
# find combo-box entry and make it active...
|
|
model = self.config_data_pool_complib.get_model( )
|
|
iter = model.get_iter_first( )
|
|
while iter is not None:
|
|
if model.get( iter, 0 )[ 0 ] == config[ "data_pool_complib" ]:
|
|
self.config_data_pool_complib.set_active_iter( iter )
|
|
break
|
|
iter = model.iter_next( iter )
|
|
# if this compression method is not supported, warn and do nothing
|
|
if iter is None:
|
|
print("compression method %s is not supported" % config[ "data_pool_complib" ])
|
|
|
|
def load_config_handler( self, widget ):
|
|
if self.system_default_filename:
|
|
self.load_config( self.system_default_filename )
|
|
self.load_config( )
|
|
|
|
def save_config_handler( self, widget ):
|
|
self.save_config( )
|
|
|
|
def set_script_font_handler( self, widget ):
|
|
"""
|
|
handles changes in font name
|
|
also sets the fonts to the text views (fast implementation: breaking encapsulation)
|
|
"""
|
|
font = self.config_script_font_button.get_font_name( )
|
|
experiment_script_textview = self.xml_gui.get_object( "experiment_script_textview" )
|
|
if experiment_script_textview:
|
|
experiment_script_textview.modify_font( pango.FontDescription( font ) )
|
|
data_handling_textview = self.xml_gui.get_object( "data_handling_textview" )
|
|
if data_handling_textview:
|
|
data_handling_textview.modify_font( pango.FontDescription( font ) )
|
|
|
|
def browse_backend_executable_dialog( self, widget ):
|
|
"""
|
|
do the open file dialog
|
|
"""
|
|
backend_filename_dialog_title = "find backend"
|
|
|
|
def response( self, response_id, script_widget ):
|
|
if response_id == gtk.ResponseType.OK:
|
|
file_name = self.get_filename( )
|
|
if file_name is None:
|
|
return
|
|
script_widget.config_backend_executable_entry.set_text( file_name )
|
|
return True
|
|
|
|
parent_window = self.xml_gui.get_object( "main_window" )
|
|
dialog = gtk.FileChooserDialog( title=backend_filename_dialog_title,
|
|
parent=parent_window,
|
|
action=gtk.FileChooserAction.OPEN,
|
|
buttons=("_Open",
|
|
gtk.ResponseType.OK,
|
|
"_Cancel",
|
|
gtk.ResponseType.CANCEL) )
|
|
dialog.set_default_response( gtk.ResponseType.OK )
|
|
dialog.set_select_multiple( False )
|
|
dialog.set_filename( os.path.abspath( self.config_backend_executable_entry.get_text( ) ) )
|
|
if os.path.isdir( self.system_backend_folder ) \
|
|
and os.access( self.system_backend_folder, os.R_OK ):
|
|
dialog.add_shortcut_folder( self.system_backend_folder )
|
|
# Event-Handler for responce-signal (when one of the button is pressed)
|
|
dialog.connect( "response", response, self )
|
|
#f = gtk.FileFilter( )
|
|
#f.add_custom( gtk.FileFilterFlags.FILENAME, lambda x: os.access( x[ 0 ], os.X_OK ) )
|
|
#dialog.set_filter( f )
|
|
|
|
dialog.run( )
|
|
dialog.destroy( )
|
|
|
|
return True
|
|
|
|
def load_config( self, filename=None ):
|
|
"""
|
|
set config from an xml file
|
|
"""
|
|
if filename is None:
|
|
filename = self.user_default_filename
|
|
try:
|
|
readfile = open( filename, "rb" )
|
|
except Exception as e:
|
|
if debug:
|
|
print("Could not open %s: %s" % (filename, str( e )))
|
|
return
|
|
|
|
# parser functions
|
|
def start_element( name, attrs, config ):
|
|
if name == "config" and "key" in attrs:
|
|
config[ "__this_key__" ] = attrs[ "key" ]
|
|
if "type" in attrs:
|
|
config[ "__this_type__" ] = attrs[ "type" ]
|
|
config[ attrs[ "key" ] ] = ""
|
|
|
|
def end_element( name, config ):
|
|
if "__this_type__" in config and "__this_key__" in config:
|
|
if config[ "__this_type__" ] == "Boolean":
|
|
if config[ config[ "__this_key__" ] ] == "True":
|
|
config[ config[ "__this_key__" ] ] = True
|
|
else:
|
|
config[ config[ "__this_key__" ] ] = False
|
|
elif config[ "__this_type__" ] == "Integer":
|
|
config[ config[ "__this_key__" ] ] = int( config[ config[ "__this_key__" ] ] )
|
|
|
|
if "__this_type__" in config:
|
|
del config[ "__this_type__" ]
|
|
if "__this_key__" in config:
|
|
del config[ "__this_key__" ]
|
|
|
|
|
|
def char_data( data, config ):
|
|
if "__this_key__" in config:
|
|
config[ config[ "__this_key__" ] ] += data
|
|
|
|
# parse file contents to dictionary
|
|
config = { }
|
|
p = xml.parsers.expat.ParserCreate( )
|
|
p.StartElementHandler = lambda n, a: start_element( n, a, config )
|
|
p.EndElementHandler = lambda n: end_element( n, config )
|
|
p.CharacterDataHandler = lambda d: char_data( d, config )
|
|
p.ParseFile( readfile )
|
|
|
|
self.set( config )
|
|
|
|
def save_config( self, filename=None ):
|
|
"""
|
|
write config as an xml file
|
|
"""
|
|
config = self.get( )
|
|
if filename is None:
|
|
filename = self.user_default_filename
|
|
dirs = os.path.dirname( filename )
|
|
if not os.path.isdir( dirs ):
|
|
os.makedirs( dirs )
|
|
|
|
print("save config to: "+filename)
|
|
|
|
configfile = open( filename, "w" )
|
|
configfile.write( "<?xml version='1.0'?>\n" )
|
|
configfile.write( "<damaris>\n" )
|
|
for k, v in config.items( ):
|
|
if k in self.config_from_system \
|
|
and self.config_from_system[ k ] == v:
|
|
if debug:
|
|
print("Ignoring for write, because system value for %r is %r equal to %r" % \
|
|
(k, self.config_from_system[ k ], v))
|
|
continue
|
|
print(k,v, type(v))
|
|
val = ""
|
|
typename = ""
|
|
if type( v ) is bool:
|
|
typename = "Boolean"
|
|
if v:
|
|
val = "True"
|
|
else:
|
|
val = "False"
|
|
elif type( v ) is bytes:
|
|
typename = "Bytes"
|
|
val = v
|
|
elif type( v ) is str:
|
|
typename = "String"
|
|
val = v
|
|
elif type( v ) is int:
|
|
typename = "Integer"
|
|
val = str( v )
|
|
configfile.write( " <config key='%s' type='%s'>%s</config>\n" % (k, typename, val) )
|
|
configfile.write( "</damaris>\n" )
|
|
|
|
|
|
class MonitorWidgets:
|
|
def __init__( self, xml_gui ):
|
|
"""
|
|
initialize matplotlib widgets and stuff around
|
|
"""
|
|
|
|
self.xml_gui = xml_gui
|
|
self.main_window = self.xml_gui.get_object( "main_window" )
|
|
self.display_settings_frame = self.xml_gui.get_object( "display_settings_frame" )
|
|
|
|
# Display footer:
|
|
self.display_x_scaling_combobox = self.xml_gui.get_object( "display_x_scaling_combobox" )
|
|
self.display_x_scaling_combobox.set_sensitive( False )
|
|
self.display_y_scaling_combobox = self.xml_gui.get_object( "display_y_scaling_combobox" )
|
|
self.display_y_scaling_combobox.set_sensitive( False )
|
|
self.display_autoscaling_checkbutton = self.xml_gui.get_object( "display_autoscaling_checkbutton" )
|
|
self.display_statistics_checkbutton = self.xml_gui.get_object( "display_statistics_checkbutton" )
|
|
|
|
self.display_descriptions_checkbutton = gtk.CheckButton(label="Show Desc.")
|
|
self.display_descriptions_checkbutton.set_active(True)
|
|
self.display_descriptions_checkbutton.show()
|
|
self.display_settings_frame.set_property("n-columns", 5)
|
|
self.display_settings_frame.attach(self.display_descriptions_checkbutton, 4, 5, 0, 1, gtk.AttachOptions.FILL, 0, 0, 0)
|
|
self.display_descriptions_checkbutton.connect("toggled", self.display_descriptions_toggled)
|
|
|
|
# Matplot (Display_Table, 1st Row) --------------------------------------------------------
|
|
|
|
# create new plot
|
|
self.matplot_figure = matplotlib.figure.Figure( )
|
|
|
|
# the plot area surrounded by axes
|
|
self.matplot_axes = self.matplot_figure.add_axes( [ 0.1, 0.15, 0.8, 0.7 ] )
|
|
|
|
self.matplot_axes.grid( True )
|
|
|
|
self.matplot_axes.set_ylim( [ 0.0, 1.0 ] )
|
|
self.matplot_axes.set_xlim( [ 0.0, 1.0 ] )
|
|
self.matplot_axes.set_autoscale_on( self.display_autoscaling_checkbutton.get_active( ) )
|
|
|
|
# linear y/x scaling
|
|
self.matplot_axes.set_yscale( "linear" )
|
|
self.matplot_axes.set_xscale( "linear" )
|
|
|
|
# add figure to canvas
|
|
self.matplot_canvas = FigureCanvas( self.matplot_figure )
|
|
|
|
# add ctrl + right-click context menu to canvas
|
|
self.matplot_canvas.connect("button-press-event", self.on_canvas_button_press)
|
|
|
|
self.display_table = self.xml_gui.get_object( "display_table" )
|
|
self.display_table.attach( self.matplot_canvas, 0, 6, 0, 1, gtk.AttachOptions.EXPAND | gtk.AttachOptions.FILL, gtk.AttachOptions.EXPAND | gtk.AttachOptions.FILL, 0, 0 )
|
|
self.matplot_canvas.show( )
|
|
|
|
# add matplotlib toolbar (display_table, 2nd row)
|
|
self.matplot_toolbar = matplotlib.backends.backend_gtk3.NavigationToolbar2GTK3( self.matplot_canvas )
|
|
# remove subplots:
|
|
#self.matplot_toolbar.DeleteToolByPos(1)
|
|
#self.matplot_toolbar.remove(self.matplot_toolbar.get_nth_item(3))
|
|
self.matplot_toolbar.remove(self.matplot_toolbar.get_nth_item(6))
|
|
# now let's add a button to the toolbar
|
|
self.matplot_cursor_button = gtk.ToggleButton(label='')
|
|
try:
|
|
image = gtk.Image.new_from_file("/usr/share/icons/breeze/actions/24/crosshairs-symbolic.svg")
|
|
self.matplot_cursor_button.set_image(image)
|
|
except:
|
|
pass
|
|
self.matplot_cursor_button.set_tooltip_text('Enable Crosshairs')
|
|
self.matplot_cursor_button.show()
|
|
self.matplot_cursor_button.connect('toggled', self.toggle_cursor)
|
|
toolitem = gtk.ToolItem()
|
|
toolitem.add(self.matplot_cursor_button)
|
|
self.matplot_toolbar.insert(toolitem, 5)
|
|
|
|
# Make cursor position text bigger and bold - access message label directly
|
|
try:
|
|
# The coordinate label is stored in the toolbar's message attribute
|
|
if hasattr(self.matplot_toolbar, 'message'):
|
|
self.matplot_toolbar.message.modify_font(pango.FontDescription("sans bold 16"))
|
|
except:
|
|
pass
|
|
|
|
self.display_table.attach( self.matplot_toolbar, 0, 1, 1, 2, gtk.AttachOptions.FILL | gtk.AttachOptions.EXPAND, 0, 0, 0 )
|
|
self.matplot_toolbar.show( )
|
|
|
|
# select display source
|
|
self.display_source_combobox = self.xml_gui.get_object( "display_source_combobox" )
|
|
self.display_source_treestore = gtk.TreeStore( gobject.TYPE_STRING )
|
|
self.display_source_combobox.set_model( self.display_source_treestore )
|
|
display_source_cell = gtk.CellRendererText( )
|
|
self.display_source_combobox.pack_start( display_source_cell, True )
|
|
self.display_source_combobox.add_attribute( display_source_cell, 'text', 0 )
|
|
# GTK3 fix: Prevent blank space from child tree nodes in popup
|
|
self.display_source_combobox.set_wrap_width(1)
|
|
self.source_list_reset( )
|
|
self.display_source_path_label = self.xml_gui.get_object( "display_source_path_label" )
|
|
|
|
# enable display scaling
|
|
self.display_x_scaling_combobox.set_active( 0 )
|
|
self.display_y_scaling_combobox.set_active( 0 )
|
|
for i in ("lin", "log10"):
|
|
self.display_x_scaling_combobox.append_text(i)
|
|
self.display_y_scaling_combobox.append_text(i)
|
|
self.display_x_scaling_combobox.append_text("ppm")
|
|
self.display_x_scaling_combobox.set_active(0)
|
|
self.display_y_scaling_combobox.set_active(0)
|
|
self.display_x_scaling_combobox.set_sensitive( True )
|
|
self.display_y_scaling_combobox.set_sensitive( True )
|
|
|
|
# and events...
|
|
self.display_source_combobox.connect( "changed", self.display_source_changed_event )
|
|
|
|
# data to observe
|
|
self.data_pool = None
|
|
# name of displayed data and reference to data
|
|
self.displayed_data = [ None, None ]
|
|
self.__rescale = True
|
|
self.update_counter = 0
|
|
self.update_counter_lock = threading.Lock( )
|
|
self.description_text = None
|
|
self.clipping_marker = None
|
|
|
|
def source_list_reset( self ):
|
|
self.display_source_treestore.clear( )
|
|
self.source_list_add( 'None' )
|
|
none_iter = self.source_list_find( [ 'None' ] )
|
|
if none_iter is not None:
|
|
self.display_source_combobox.set_active_iter( none_iter )
|
|
|
|
def source_list_find_one( self, model, iter, what ):
|
|
"""find node in subcategory"""
|
|
while iter is not None:
|
|
if model.get( iter, 0 )[ 0 ] == what:
|
|
return iter
|
|
iter = model.iter_next( iter )
|
|
return iter
|
|
|
|
def source_list_find( self, namelist ):
|
|
"""
|
|
namelist sequence of names, e.g. ["a", "b", "c" ] for "a/b/c"
|
|
"""
|
|
model = self.display_source_treestore
|
|
retval = None
|
|
#iter = model.get_iter_root( )
|
|
iter = model.get_iter_first( )
|
|
while iter is not None and len( namelist ) > 0:
|
|
name = namelist[ 0 ]
|
|
iter = self.source_list_find_one( model, iter, name )
|
|
if iter is not None:
|
|
retval = iter
|
|
namelist.pop( 0 )
|
|
iter = model.iter_children( retval )
|
|
return retval
|
|
|
|
def source_list_add( self, source_name, parent=None ):
|
|
namelist = source_name.split( "/" )
|
|
found = self.source_list_find( namelist )
|
|
if parent is None:
|
|
parent = found
|
|
for rest_name in namelist:
|
|
# append() returns iter for the new row
|
|
parent = self.display_source_treestore.append( parent, [ rest_name ] )
|
|
|
|
def source_list_remove( self, source_name ):
|
|
namelist = source_name.split( "/" )
|
|
pwd = namelist[ : ]
|
|
iter = self.source_list_find( namelist )
|
|
if iter is None or len( namelist ) > 0:
|
|
print("source_list_remove: WARNING: Not found")
|
|
return
|
|
model = self.display_source_treestore
|
|
if model.iter_has_child( iter ):
|
|
print("source_list_remove: WARNING: Request to delete a tree")
|
|
return
|
|
while True:
|
|
parent = model.iter_parent( iter )
|
|
model.remove( iter )
|
|
pwd.pop( )
|
|
# We now test, if we want to remove parent too
|
|
if parent is None:
|
|
break
|
|
if model.iter_has_child( parent ):
|
|
# The parent has other children
|
|
break
|
|
if "/".join( pwd ) in self.data_pool:
|
|
# The parent has data connected to it
|
|
break
|
|
iter = parent
|
|
|
|
def source_list_current( self ):
|
|
ai = self.display_source_combobox.get_active_iter( )
|
|
namelist = [ ]
|
|
while ai is not None:
|
|
namelist.insert( 0, str( self.display_source_treestore.get( ai, 0 )[ 0 ] ) )
|
|
ai = self.display_source_treestore.iter_parent( ai )
|
|
cur_source_name = "/".join( namelist )
|
|
return cur_source_name
|
|
|
|
def observe_data_pool( self, data_pool ):
|
|
"""
|
|
register a listener and save reference to data
|
|
assume to be in gtk/gdk lock
|
|
"""
|
|
if self.data_pool is not None:
|
|
# maybe some extra cleanup needed
|
|
print("ToDo: cleanup widgets")
|
|
if self.displayed_data[ 1 ] is not None and hasattr( self.displayed_data[ 1 ], "unregister_listener" ):
|
|
self.displayed_data[ 1 ].unregister_listener( self.datastructures_listener )
|
|
self.displayed_data[ 1 ] = None
|
|
self.displayed_data = [ None, None ]
|
|
self.data_pool.unregister_listener( self.datapool_listener )
|
|
self.data_pool = None
|
|
|
|
self.source_list_reset( )
|
|
self.update_counter_lock.acquire( )
|
|
self.update_counter = 0
|
|
self.update_counter_lock.release( )
|
|
|
|
# display states
|
|
self.__rescale = True
|
|
self.displayed_data = [ None, None ]
|
|
self.display_source_path_label.set_label( "" )
|
|
self.clear_display( )
|
|
|
|
if data_pool is not None:
|
|
# keep track of data
|
|
self.data_pool = data_pool
|
|
self.data_pool.register_listener( self.datapool_listener )
|
|
|
|
#################### observing data structures and produce idle events
|
|
|
|
def datapool_listener( self, event ):
|
|
"""
|
|
sort data as fast as possible and get rid of non-interesting data
|
|
"""
|
|
if event.subject.startswith( "__" ):
|
|
return
|
|
if debug and self.update_counter < 0:
|
|
print("negative event count!", self.update_counter)
|
|
if self.update_counter > 5:
|
|
if debug:
|
|
print("sleeping to find time for grapics updates")
|
|
threading.Event( ).wait( 0.05 )
|
|
while self.update_counter > 15:
|
|
threading.Event( ).wait( 0.05 )
|
|
if event.what == DataPool.Event.updated_value:
|
|
if self.displayed_data[ 0 ] is None or event.subject != self.displayed_data[ 0 ]:
|
|
# do nothing, forget it
|
|
return
|
|
|
|
displayed_object = self.displayed_data[ 1 ]
|
|
object_to_display = self.data_pool.get( event.subject )
|
|
if displayed_object is None or object_to_display is None:
|
|
self.update_counter_lock.acquire( )
|
|
self.update_counter += 1
|
|
self.update_counter_lock.release( )
|
|
gobject.idle_add( self.datapool_idle_listener, event, priority=gobject.PRIORITY_DEFAULT_IDLE )
|
|
else:
|
|
if event.what == DataPool.Event.updated_value and \
|
|
(
|
|
displayed_object is object_to_display or displayed_object.__class__ is object_to_display.__class__):
|
|
# oh, another category
|
|
self.update_counter_lock.acquire()
|
|
self.update_counter += 1
|
|
self.update_counter_lock.release()
|
|
gobject.idle_add( self.update_display_idle_event, self.displayed_data[ 0 ][ : ],
|
|
priority=gobject.PRIORITY_DEFAULT_IDLE )
|
|
else:
|
|
self.update_counter_lock.acquire( )
|
|
self.update_counter += 1
|
|
self.update_counter_lock.release( )
|
|
gobject.idle_add( self.datapool_idle_listener, event, priority=gobject.PRIORITY_DEFAULT_IDLE )
|
|
|
|
if event.what in [ DataPool.Event.updated_value, DataPool.Event.new_key ]:
|
|
#print "Update hdf5 file ..."
|
|
# TODO: incremental hdf5 update
|
|
pass
|
|
|
|
del displayed_object
|
|
del object_to_display
|
|
|
|
def datastructures_listener( self, event ):
|
|
"""
|
|
do fast work selecting important events
|
|
"""
|
|
if debug and self.update_counter < 0:
|
|
print("negative event count!", self.update_counter)
|
|
if self.update_counter > 5:
|
|
if debug:
|
|
print("sleeping to find time for graphics updates")
|
|
threading.Event( ).wait( 0.05 )
|
|
while self.update_counter > 15:
|
|
threading.Event( ).wait( 0.05 )
|
|
if event.origin is not self.displayed_data[ 1 ]:
|
|
return
|
|
self.update_counter_lock.acquire( )
|
|
self.update_counter += 1
|
|
self.update_counter_lock.release( )
|
|
gobject.idle_add( self.update_display_idle_event, self.displayed_data[ 0 ][ : ],
|
|
priority=gobject.PRIORITY_DEFAULT_IDLE )
|
|
|
|
################### consume idle events
|
|
|
|
def datapool_idle_listener( self, event ):
|
|
"""
|
|
here dictionary changes are done
|
|
"""
|
|
self.update_counter_lock.acquire( )
|
|
self.update_counter -= 1
|
|
self.update_counter_lock.release( )
|
|
|
|
if event.what == DataPool.Event.updated_value:
|
|
if (self.displayed_data[ 0 ] is not None and
|
|
self.displayed_data[ 0 ] == event.subject):
|
|
new_data_struct = self.data_pool[ self.displayed_data[ 0 ] ]
|
|
if self.displayed_data[ 1 ] is new_data_struct:
|
|
# update display only
|
|
if self.update_counter > 10:
|
|
print("update queue too long (%d>10): skipping one update" % self.update_counter)
|
|
else:
|
|
gdk.threads_enter( )
|
|
try:
|
|
self.update_display( )
|
|
finally:
|
|
gdk.threads_leave( )
|
|
else:
|
|
# unregister old one
|
|
if self.displayed_data[ 1 ] is not None and hasattr( self.displayed_data[ 1 ],
|
|
"unregister_listener" ):
|
|
self.displayed_data[ 1 ].unregister_listener( self.datastructures_listener )
|
|
self.displayed_data[ 1 ] = None
|
|
# register new one
|
|
if hasattr( new_data_struct, "register_listener" ):
|
|
new_data_struct.register_listener( self.datastructures_listener )
|
|
self.displayed_data[ 1 ] = new_data_struct
|
|
if self.update_counter > 10:
|
|
print("update queue too long (%d>10): skipping one update" % self.update_counter)
|
|
else:
|
|
gdk.threads_enter( )
|
|
try:
|
|
self.renew_display( )
|
|
finally:
|
|
gdk.threads_leave( )
|
|
new_data_struct = None
|
|
elif event.what == DataPool.Event.new_key:
|
|
# update combo-box by inserting and rely on consistent information
|
|
gdk.threads_enter( )
|
|
self.source_list_add( event.subject )
|
|
gdk.threads_leave( )
|
|
elif event.what == DataPool.Event.deleted_key:
|
|
# update combo-box by removing and rely on consistent information
|
|
gdk.threads_enter( )
|
|
if (self.displayed_data[ 0 ] is not None and
|
|
self.displayed_data[ 0 ] == event.subject):
|
|
self.displayed_data = [ None, None ]
|
|
none_iter = self.source_list_find( [ 'None' ] )
|
|
if none_iter is not None:
|
|
self.display_source_combobox.set_active_iter( none_iter )
|
|
# not necessary, because event will be submitted
|
|
#self.clear_display()
|
|
self.source_list_remove( event.subject )
|
|
gdk.threads_leave( )
|
|
elif event.what == DataPool.Event.destroy:
|
|
gdk.threads_enter( )
|
|
self.source_list_reset( 'None' )
|
|
self.displayed_data = [ None, None ]
|
|
self.clear_display( )
|
|
gdk.threads_leave( )
|
|
return
|
|
|
|
def update_display_idle_event( self, subject=None ):
|
|
|
|
self.update_counter_lock.acquire( )
|
|
self.update_counter -= 1
|
|
self.update_counter_lock.release( )
|
|
# print "update display", self.update_counter
|
|
if self.update_counter > 10:
|
|
print("update queue too long (%d>10): skipping one update" % self.update_counter)
|
|
return
|
|
if self.displayed_data[ 0 ] is None or subject != self.displayed_data[ 0 ]:
|
|
return
|
|
gdk.threads_enter( )
|
|
try:
|
|
self.update_display( )
|
|
finally:
|
|
gdk.threads_leave( )
|
|
|
|
|
|
######################## events from buttons
|
|
|
|
def display_source_changed_event( self, widget, data=None ):
|
|
|
|
new_data_name = self.source_list_current( )
|
|
|
|
if (self.displayed_data[ 0 ] is None and new_data_name == "None"):
|
|
return
|
|
if (self.displayed_data[ 0 ] == new_data_name):
|
|
return
|
|
if self.displayed_data[ 1 ] is not None and hasattr( self.displayed_data[ 1 ], "unregister_listener" ):
|
|
self.displayed_data[ 1 ].unregister_listener( self.datastructures_listener )
|
|
self.displayed_data[ 1 ] = None
|
|
# register new one
|
|
if new_data_name == "None":
|
|
self.display_source_path_label.set_label( "" )
|
|
self.displayed_data = [ None, None ]
|
|
self.clear_display( )
|
|
elif self.data_pool is None or new_data_name not in self.data_pool:
|
|
none_iter = self.source_list_find( [ 'None' ] )
|
|
if none_iter is not None:
|
|
self.display_source_combobox.set_active_iter( none_iter )
|
|
self.display_source_path_label.set_label( "" )
|
|
else:
|
|
new_data_struct = self.data_pool[ new_data_name ]
|
|
if hasattr( new_data_struct, "register_listener" ):
|
|
new_data_struct.register_listener( self.datastructures_listener )
|
|
self.displayed_data = [ new_data_name, new_data_struct ]
|
|
dirpart = new_data_name.rfind( "/" )
|
|
if dirpart >= 0:
|
|
self.display_source_path_label.set_label( "in " + new_data_name[ :dirpart ] )
|
|
else:
|
|
self.display_source_path_label.set_label( "" )
|
|
self.clear_display( )
|
|
# renew display via idle event
|
|
self.update_counter_lock.acquire( )
|
|
self.update_counter += 1
|
|
self.update_counter_lock.release( )
|
|
event = DataPool.Event( DataPool.Event.updated_value, new_data_name )
|
|
gobject.idle_add( self.datapool_idle_listener, event, priority=gobject.PRIORITY_DEFAULT_IDLE )
|
|
|
|
def display_autoscaling_toggled( self, widget, data=None ):
|
|
self.matplot_axes.set_autoscale_on( self.display_autoscaling_checkbutton.get_active( ) )
|
|
if self.displayed_data[ 0 ] is not None:
|
|
self.update_display( self.displayed_data[ 0 ][ : ] )
|
|
|
|
def display_scaling_changed( self, widget, data=None ):
|
|
self.__rescale = True
|
|
if self.displayed_data[ 0 ] is not None:
|
|
self.update_display( self.displayed_data[ 0 ][ : ] )
|
|
|
|
def display_statistics_toggled( self, widget, data=None ):
|
|
if self.displayed_data[ 0 ] is not None:
|
|
self.update_display( self.displayed_data[ 0 ][ : ] )
|
|
|
|
def display_descriptions_toggled( self, widget, data=None ):
|
|
if self.displayed_data[ 0 ] is not None:
|
|
self.update_display( self.displayed_data[ 0 ][ : ] )
|
|
|
|
def save_display_data_as_text( self, widget, data=None ):
|
|
"""
|
|
copy data to tmp file and show save dialog
|
|
"""
|
|
data_to_save = self.displayed_data[ : ]
|
|
if self.displayed_data[ 1 ] is None:
|
|
# nothing to save
|
|
return
|
|
if not hasattr( data_to_save[ 1 ], "write_to_csv" ):
|
|
log( "do not know how to save %s of class/type %s" % (data_to_save[ 0 ], type( data_to_save[ 1 ] )) )
|
|
return
|
|
|
|
# save them to a temporary file (in memory)
|
|
tmpdata = tempfile.TemporaryFile(mode="w+" )
|
|
tmpdata.write("# saved from monitor as %s\n" % data_to_save[ 0 ] )
|
|
data_to_save[ 1 ].write_to_csv( tmpdata )
|
|
|
|
parentfordialog = self.main_window
|
|
|
|
# show save dialog
|
|
def response( self, response_id, tmpfile ):
|
|
if response_id == gtk.ResponseType.OK:
|
|
file_name = dialog.get_filename( )
|
|
if file_name is None:
|
|
return True
|
|
|
|
absfilename = os.path.abspath( file_name )
|
|
if os.access( file_name, os.F_OK ):
|
|
question = gtk.MessageDialog(parent=parentfordialog,
|
|
type=gtk.MessageType.WARNING,
|
|
flags=gtk.DialogFlags.MODAL,
|
|
buttons=("_Yes", gtk.ResponseType.YES, "_No", gtk.ResponseType.NO))
|
|
question.set_markup("The file already exists. Do you want to overwrite the existing file and delete its contents?")
|
|
|
|
response = question.run()
|
|
question.destroy()
|
|
|
|
if response != gtk.ResponseType.YES:
|
|
return True
|
|
|
|
textfile = open( absfilename, "w" )
|
|
tmpfile.seek( 0 )
|
|
for l in tmpfile:
|
|
textfile.write( l )
|
|
textfile.close( )
|
|
textfile = None
|
|
tmpfile = None
|
|
return True
|
|
|
|
# Determining the tab which is currently open
|
|
dialog_title = "Save %s in file" % data_to_save[ 0 ]
|
|
|
|
dialog = gtk.FileChooserDialog( title=dialog_title,
|
|
parent=self.main_window,
|
|
action=gtk.FileChooserAction.SAVE,
|
|
buttons=("_Save", gtk.ResponseType.OK, "_Cancel", gtk.ResponseType.CANCEL) )
|
|
|
|
dialog.set_default_response( gtk.ResponseType.OK )
|
|
dialog.set_current_name( data_to_save[ 0 ] )
|
|
dialog.set_select_multiple( False )
|
|
|
|
# Event-Handler for responce-signal (when one of the button is pressed)
|
|
dialog.connect( "response", response, tmpdata )
|
|
del tmpdata, data_to_save
|
|
dialog.run( )
|
|
dialog.destroy( )
|
|
|
|
return True
|
|
|
|
def toggle_cursor(self, widget):
|
|
"""Toggle matplotlib crosshair cursor"""
|
|
if widget.get_active():
|
|
# Activate cursor
|
|
if not hasattr(self, 'matplot_cursor') or self.matplot_cursor is None:
|
|
self.matplot_cursor = matplotlib.widgets.Cursor(self.matplot_axes,
|
|
useblit=False, color='red', linewidth=1)
|
|
self.matplot_canvas.draw()
|
|
|
|
else:
|
|
# Deactivate cursor
|
|
if hasattr(self, 'matplot_cursor') and self.matplot_cursor is not None:
|
|
self.matplot_cursor.disconnect_events()
|
|
# Remove the lines from the plot
|
|
if hasattr(self.matplot_cursor, 'lineh'):
|
|
self.matplot_cursor.lineh.remove()
|
|
if hasattr(self.matplot_cursor, 'linev'):
|
|
self.matplot_cursor.linev.remove()
|
|
self.matplot_cursor = None
|
|
self.matplot_canvas.draw()
|
|
|
|
def on_canvas_button_press(self, widget, event):
|
|
"""Handle button press events on matplotlib canvas"""
|
|
if event.button == 2: # middle click
|
|
self.show_canvas_context_menu(event)
|
|
return True
|
|
return False
|
|
|
|
def show_canvas_context_menu(self, event):
|
|
"""Show context menu for matplotlib canvas"""
|
|
menu = gtk.Menu()
|
|
|
|
# toggle cursor
|
|
toggle_cursor = gtk.CheckMenuItem(label="Crosshair")
|
|
# Set the current state based on whether cursor exists
|
|
if hasattr(self, 'matplot_cursor') and self.matplot_cursor is not None:
|
|
toggle_cursor.set_active(True)
|
|
else:
|
|
toggle_cursor.set_active(False)
|
|
toggle_cursor.connect("toggled", lambda w: self.matplot_cursor_button.set_active(w.get_active()))
|
|
menu.append(toggle_cursor)
|
|
|
|
# Copy as PNG
|
|
item_copy_png = gtk.MenuItem(label="Copy as PNG")
|
|
item_copy_png.connect("activate", self.copy_canvas_as_png)
|
|
menu.append(item_copy_png)
|
|
|
|
# Copy as SVG
|
|
item_copy_svg = gtk.MenuItem(label="Copy as SVG")
|
|
item_copy_svg.connect("activate", self.copy_canvas_as_svg)
|
|
menu.append(item_copy_svg)
|
|
|
|
menu.show_all()
|
|
menu.popup(None, None, None, None, event.button, event.time)
|
|
|
|
def copy_canvas_as_png(self, widget):
|
|
"""Copy canvas to clipboard as PNG"""
|
|
self.copy_display_data_to_clipboard(widget)
|
|
|
|
def copy_canvas_as_svg(self, widget):
|
|
"""Copy canvas as SVG to clipboard"""
|
|
if self.matplot_figure is None:
|
|
return
|
|
|
|
try:
|
|
# Save the original figure size
|
|
original_size = self.matplot_figure.get_size_inches()
|
|
# Save figure to a buffer as SVG
|
|
import io as iomodule
|
|
buf = iomodule.StringIO()
|
|
self.matplot_figure.set_size_inches(6, 4)
|
|
self.matplot_figure.savefig(buf, format='svg', bbox_inches='tight')
|
|
buf.seek(0)
|
|
|
|
# Copy SVG text to clipboard
|
|
clipboard = gtk.Clipboard.get(gdk.SELECTION_CLIPBOARD)
|
|
clipboard.set_text(buf.read(), -1)
|
|
clipboard.store()
|
|
self.matplot_figure.set_size_inches(original_size)
|
|
|
|
log("Figure copied to clipboard as SVG")
|
|
|
|
except Exception as e:
|
|
log(f"Error copying figure as SVG: {e}")
|
|
|
|
def copy_display_data_to_clipboard( self, widget, data=None ):
|
|
"""
|
|
Copy matplotlib figure as PNG image to clipboard
|
|
Also keep text data copy functionality as fallback
|
|
"""
|
|
# First, try to copy the matplotlib figure as image
|
|
# Save the original figure size
|
|
original_size = self.matplot_figure.get_size_inches()
|
|
if self.matplot_figure is not None and self.matplot_canvas is not None:
|
|
try:
|
|
# Save figure to a temporary buffer as PNG
|
|
import io as iomodule
|
|
buf = iomodule.BytesIO()
|
|
self.matplot_figure.set_size_inches(6, 4)
|
|
self.matplot_figure.savefig(buf, format='png', dpi=300, bbox_inches='tight')
|
|
buf.seek(0)
|
|
|
|
# Load the PNG data into a GdkPixbuf
|
|
from gi.repository import GdkPixbuf
|
|
loader = GdkPixbuf.PixbufLoader.new_with_type('png')
|
|
loader.write(buf.read())
|
|
loader.close()
|
|
pixbuf = loader.get_pixbuf()
|
|
|
|
# Copy to clipboard
|
|
clipboard = gtk.Clipboard.get(gdk.SELECTION_CLIPBOARD)
|
|
clipboard.set_image(pixbuf)
|
|
clipboard.store()
|
|
# Restore original figure size
|
|
self.matplot_figure.set_size_inches(original_size)
|
|
# Redraw the canvas to ensure it stays visible
|
|
self.matplot_canvas.draw()
|
|
|
|
log("Figure copied to clipboard as PNG image")
|
|
return
|
|
|
|
except Exception as e:
|
|
log("Error copying figure to clipboard: %s" % str(e))
|
|
|
|
##################### functions to feed display
|
|
def clear_display( self ):
|
|
"""
|
|
unconditionally throw away everything
|
|
we are inside gtk/gdk lock
|
|
"""
|
|
self.display_x_scaling_combobox.set_sensitive( True )
|
|
self.display_y_scaling_combobox.set_sensitive( True )
|
|
|
|
x_scale = self.display_x_scaling_combobox.get_active_text()
|
|
y_scale = self.display_y_scaling_combobox.get_active_text()
|
|
print(x_scale)
|
|
if x_scale == "lin":
|
|
self.matplot_axes.set_xscale("linear")
|
|
elif x_scale == "log10":
|
|
self.matplot_axes.set_xscale("log", base=10, nonpositive="mask")
|
|
elif x_scale == "ppm":
|
|
self.matplot_axes.set_xscale("linear")
|
|
self.matplot_axes.invert_xaxis()
|
|
|
|
if y_scale == "lin":
|
|
self.matplot_axes.set_yscale("linear")
|
|
elif y_scale == "log10":
|
|
self.matplot_axes.set_yscale("log", base=10, nonpositive="mask")
|
|
|
|
if not hasattr( self, "__rescale" ):
|
|
self.__rescale = True
|
|
|
|
if not hasattr( self, "measurementresultline" ):
|
|
self.measurementresultline = None
|
|
elif self.measurementresultline is not None:
|
|
# clear line plot
|
|
self.matplot_axes.lines.remove( self.measurementresultline[ 0 ] )
|
|
self.measurementresultline = None
|
|
if hasattr(self, 'matplot_cursor') and self.matplot_cursor is not None:
|
|
self.matplot_cursor.disconnect_events()
|
|
self.matplot_cursor = None
|
|
if not hasattr( self, "measurementresultgraph" ):
|
|
self.measurementresultgraph = None
|
|
elif self.measurementresultgraph is not None:
|
|
# clear errorbars
|
|
self.measurementresultgraph[0].remove()
|
|
for line in self.measurementresultgraph[ 1 ]:
|
|
line.remove()
|
|
for line in self.measurementresultgraph[ 2 ]:
|
|
line.remove()
|
|
self.measurementresultgraph = None
|
|
self.matplot_axes.clear( )
|
|
self.matplot_axes.grid( True )
|
|
if not hasattr( self, "graphen" ):
|
|
self.graphen = [ ]
|
|
elif self.graphen:
|
|
for line in self.graphen:
|
|
line.remove()
|
|
self.graphen = [ ]
|
|
self.matplot_axes.clear( )
|
|
self.matplot_axes.grid( True )
|
|
if self.description_text is not None:
|
|
try:
|
|
self.description_text.remove()
|
|
except:
|
|
pass
|
|
self.description_text = None
|
|
if self.clipping_marker is not None:
|
|
try:
|
|
self.clipping_marker.remove()
|
|
except:
|
|
pass
|
|
self.clipping_marker = None
|
|
self.matplot_canvas.draw_idle( )
|
|
|
|
def update_display( self, subject=None ):
|
|
"""
|
|
try to recycle labels, data, lines....
|
|
assume, object is not changed
|
|
we are inside gtk/gdk lock
|
|
|
|
"""
|
|
in_result = self.data_pool.get( self.displayed_data[ 0 ] )
|
|
if in_result is None:
|
|
self.clear_display( )
|
|
return
|
|
if isinstance( in_result, Accumulation ) or isinstance( in_result, ADC_Result ):
|
|
|
|
xmin = in_result.get_xmin( )
|
|
xmax = in_result.get_xmax( )
|
|
|
|
ymin = in_result.get_ymin( )
|
|
ymax = in_result.get_ymax( )
|
|
|
|
# Initial rescaling needed?
|
|
if self.__rescale:
|
|
x_scale = self.display_x_scaling_combobox.get_active_text( )
|
|
y_scale = self.display_y_scaling_combobox.get_active_text( )
|
|
# x-axis
|
|
if x_scale == "lin":
|
|
self.matplot_axes.set_xscale( "linear" )
|
|
self.matplot_axes.set_xlim(xmin, xmax)
|
|
elif x_scale == "log10":
|
|
xminpos = in_result.get_xminpos( )
|
|
self.matplot_axes.set_xscale("log", base=10, nonpositive="mask")
|
|
self.matplot_axes.set_xlim(xminpos, xmax)
|
|
elif x_scale == "ppm":
|
|
self.matplot_axes.set_xscale("linear")
|
|
self.matplot_axes.set_xlabel("PPM")
|
|
#self.display_autoscaling_checkbutton.set_active(True) #partial fix T141
|
|
self.matplot_axes.set_xlim(xmax, xmin)
|
|
#self.matplot_axes.invert_xaxis()
|
|
# y-axis
|
|
if y_scale == "lin":
|
|
self.matplot_axes.set_yscale( "linear" )
|
|
self.matplot_axes.set_ylim(ymin, ymax)
|
|
elif y_scale == "log10":
|
|
yminpos = in_result.get_yminpos()
|
|
self.matplot_axes.set_yscale("log", base=10, nonpositive="mask")
|
|
self.matplot_axes.set_ylim( yminpos, ymax )
|
|
self.__rescale = False
|
|
|
|
# Autoscaling activated?
|
|
elif self.display_autoscaling_checkbutton.get_active():
|
|
|
|
xlim_min, xlim_max = self.matplot_axes.get_xlim()
|
|
if xlim_min != xmin or xlim_max != xmax:
|
|
self.matplot_axes.set_xlim(xmin, xmax)
|
|
|
|
ylim_min, ylim_max = self.matplot_axes.get_ylim()
|
|
|
|
# Check if y-axis is log scale
|
|
yscale = self.matplot_axes.get_yscale()
|
|
|
|
if yscale == 'log' or yscale == 'symlog':
|
|
# For log scales, work with log values to avoid issues with
|
|
# small numbers (undetected rescale)
|
|
# Use log-space calculations
|
|
log_ymin, log_ymax = numpy.log10(ymin), numpy.log10(ymax)
|
|
|
|
log_diff = log_ymax - log_ymin
|
|
|
|
# Add a small margin in log space (about 0.08 in log10 units ~20% margin)
|
|
new_ymin = 10**(log_ymin - 0.08 * max(log_diff, 1))
|
|
new_ymax = 10**(log_ymax + 0.08 * max(log_diff, 1))
|
|
|
|
# Only update if values would change significantly
|
|
if (ylim_max < ymax or ylim_min > ymin or
|
|
ylim_max > new_ymax * 1.2 or ylim_min < new_ymin / 1.2):
|
|
self.matplot_axes.set_ylim(new_ymin, new_ymax)
|
|
else:
|
|
# Original linear scale logic
|
|
ydiff = ymax - ymin
|
|
if (ylim_max < ymax or ylim_min > ymin or
|
|
ylim_max > ymax + 0.2 * ydiff or ylim_min < ymin - 0.2 * ydiff):
|
|
self.matplot_axes.set_ylim(ymin, ymax)
|
|
|
|
xdata = in_result.get_xdata( )
|
|
chans = in_result.get_number_of_channels( )
|
|
data = [ ]
|
|
colors = [ (0, 0, 0.8, 1), (0.7, 0, 0, 1), (0, 0.7, 0, 1), (0.7, 0.5, 0, 1),
|
|
(0, 0, 0, 1) ] # rgba tuples: blue, red, green, yellow
|
|
|
|
for i in range( chans ):
|
|
data.append( in_result.get_ydata( i ) )
|
|
|
|
moving_average = data_slice = None
|
|
if max_points_to_display > 0 and len( xdata ) > max_points_to_display:
|
|
print("decimating data to %d points by moving average (prevent crash of matplotlib)" % max_points_to_display)
|
|
n = numpy.ceil( len( xdata ) / max_points_to_display )
|
|
moving_average = numpy.ones( n, dtype="float" ) / n
|
|
data_slice = numpy.array( numpy.floor( numpy.arange( max_points_to_display, dtype="float" ) \
|
|
/ max_points_to_display * len( xdata ) ),
|
|
dtype="int" )
|
|
xdata = xdata.take( data_slice ) # no average !?
|
|
for i in range( chans ):
|
|
data[ i ] = numpy.convolve( data[ i ], moving_average, "same" ).take( data_slice )
|
|
|
|
if len( self.graphen ) == 0:
|
|
for i in range( chans ):
|
|
self.graphen.extend(
|
|
self.matplot_axes.plot( xdata, data[ i ], linestyle="-", color=colors[ i ], linewidth=2 ) )
|
|
for i in range( chans ):
|
|
# initialize error bars
|
|
self.graphen.extend(
|
|
self.matplot_axes.plot( [ 0.0 ], [ 0.0 ], linestyle="-", color=colors[ i ], linewidth=0.5 ) )
|
|
self.graphen.extend(
|
|
self.matplot_axes.plot( [ 0.0 ], [ 0.0 ], linestyle="-", color=colors[ i ], linewidth=0.5 ) )
|
|
else:
|
|
for i in range( chans ):
|
|
self.graphen[ i ].set_data( xdata, data[ i ] )
|
|
|
|
# Statistics activated?
|
|
if (self.display_statistics_checkbutton.get_active( ) and
|
|
in_result.uses_statistics( ) and in_result.ready_for_drawing_error( )):
|
|
for i in range( chans ):
|
|
err = in_result.get_yerr( i )
|
|
if moving_average is not None:
|
|
err = numpy.convolve( err, moving_average, "same" ).take( data_slice )
|
|
self.graphen[ chans + 2 * i ].set_data( xdata, data[ i ] + err )
|
|
self.graphen[ chans + 2 * i + 1 ].set_data( xdata, data[ i ] - err )
|
|
else:
|
|
for i in range( chans ):
|
|
self.graphen[ chans + 2 * i ].set_data( [ 0.0 ], [ 0.0 ] )
|
|
self.graphen[ chans + 2 * i + 1 ].set_data( [ 0.0 ], [ 0.0 ] )
|
|
moving_average = data_mask = None
|
|
data = None
|
|
|
|
# Any title to be set?
|
|
in_result_title = in_result.get_title( )
|
|
if in_result_title is not None:
|
|
col = 101
|
|
while len( in_result_title ) - col > 10:
|
|
in_result_title = in_result_title[ :col ] + "\n" + in_result_title[ col: ]
|
|
col += 101
|
|
self.matplot_axes.set_title( in_result_title )
|
|
else:
|
|
self.matplot_axes.set_title( "" )
|
|
|
|
# Any labels to be set?
|
|
if in_result.get_xlabel( ) is not None:
|
|
self.matplot_axes.set_xlabel( in_result.get_xlabel( ) )
|
|
else:
|
|
self.matplot_axes.set_xlabel( "" )
|
|
|
|
if in_result.get_ylabel( ) is not None:
|
|
self.matplot_axes.set_ylabel( in_result.get_ylabel( ) )
|
|
else:
|
|
self.matplot_axes.set_ylabel( "" )
|
|
|
|
# Any variables to be drawn on plot
|
|
description_lines = []
|
|
if self.display_descriptions_checkbutton.get_active():
|
|
if isinstance(in_result, Accumulation):
|
|
description_lines.append("n = %d" % in_result.n)
|
|
if in_result.common_descriptions is not None:
|
|
description_lines.extend(["%s = %s" % (key, in_result.common_descriptions[key]) for key in in_result.common_descriptions.keys()])
|
|
elif isinstance(in_result, ADC_Result):
|
|
if hasattr(in_result, "job_id") and in_result.job_id is not None:
|
|
description_lines.append("job_id = %s" % str(in_result.job_id))
|
|
if hasattr(in_result, "description") and in_result.description is not None:
|
|
description_lines.extend(["%s = %s" % (key, in_result.description[key]) for key in in_result.description.keys()])
|
|
|
|
if description_lines:
|
|
description_string = "\n".join(description_lines)
|
|
if self.description_text is None:
|
|
self.description_text = self.matplot_axes.text(0.98, 0.95, description_string,
|
|
size=10,
|
|
transform=self.matplot_axes.transAxes,
|
|
va='top', ha='right', ma='left',
|
|
bbox=dict(facecolor='lightgray', alpha=0.5, boxstyle='round,pad=0.2'))
|
|
else:
|
|
self.description_text.set_text(description_string)
|
|
elif self.description_text is not None:
|
|
try:
|
|
self.description_text.remove()
|
|
except:
|
|
pass
|
|
self.description_text = None
|
|
|
|
if getattr(in_result, "is_clipped", False):
|
|
if self.clipping_marker is None:
|
|
self.clipping_marker = self.matplot_axes.text(0.5, 0.95, "ADC OVERFLOW",
|
|
transform=self.matplot_axes.transAxes,
|
|
color="red", fontsize=20, fontweight="bold",
|
|
ha="center", va="top",
|
|
bbox=dict(facecolor='white', alpha=0.7, edgecolor='red'))
|
|
elif self.clipping_marker is not None:
|
|
try:
|
|
self.clipping_marker.remove()
|
|
except:
|
|
pass
|
|
self.clipping_marker = None
|
|
|
|
# Draw it!
|
|
self.matplot_canvas.draw_idle( )
|
|
del in_result
|
|
|
|
elif isinstance( in_result, MeasurementResult ):
|
|
|
|
# remove lines and error bars
|
|
if self.measurementresultgraph is not None:
|
|
self.matplot_axes.lines.remove( self.measurementresultgraph[ 0 ] )
|
|
# remove caps
|
|
for l in self.measurementresultgraph[ 1 ]:
|
|
self.matplot_axes.lines.remove( l )
|
|
# and columns
|
|
for l in self.measurementresultgraph[ 2 ]:
|
|
self.matplot_axes.collections.remove( l )
|
|
self.measurementresultgraph = None
|
|
|
|
if self.measurementresultline is not None:
|
|
self.matplot_axes.lines.remove( self.measurementresultline[ 0 ] )
|
|
self.measurementresultline = None
|
|
|
|
[ k, v, e ] = in_result.get_errorplotdata( )
|
|
if k.shape[ 0 ] != 0:
|
|
xmin = k.min( )
|
|
ymin = (v - e).min( )
|
|
|
|
if xmin > 0:
|
|
self.display_x_scaling_combobox.set_sensitive( True )
|
|
else:
|
|
# force switch to lin scale
|
|
self.display_x_scaling_combobox.set_sensitive( False )
|
|
if self.display_x_scaling_combobox.get_active_text( ) != "lin":
|
|
self.__rescale = True
|
|
# and reset to linear
|
|
self.display_x_scaling_combobox.set_active( 0 )
|
|
if ymin > 0:
|
|
self.display_y_scaling_combobox.set_sensitive( True )
|
|
else:
|
|
# force switch to lin scale
|
|
self.display_y_scaling_combobox.set_sensitive( False )
|
|
if self.display_y_scaling_combobox.get_active_text( ) != "lin":
|
|
self.__rescale = True
|
|
# and reset to linear
|
|
self.display_y_scaling_combobox.set_active( 0 )
|
|
|
|
x_scale = self.display_x_scaling_combobox.get_active_text( )
|
|
y_scale = self.display_y_scaling_combobox.get_active_text( )
|
|
|
|
# Initial rescaling needed?
|
|
if self.__rescale: # or self.display_autoscaling_checkbutton.get_active():
|
|
xmax = k.max( )
|
|
ymax = (v + e).max( )
|
|
# is there a range problem?
|
|
if xmin == xmax or ymin == ymax:
|
|
# fix range and scaling problems
|
|
if xmin == xmax:
|
|
if xmin != 0:
|
|
(xmin, xmax) = (xmin - abs( xmin / 10.0 ), xmin + abs( xmin / 10.0 ))
|
|
else:
|
|
(xmin, xmax) = (-1, 1)
|
|
if ymin == ymax:
|
|
if ymin == 0:
|
|
(ymin, ymax) = (ymin - abs( ymin / 10.0 ), ymin + abs( ymin / 10.0 ))
|
|
else:
|
|
(ymin, ymax) = (-1, 1)
|
|
|
|
if xmin <= 0 or x_scale == "lin":
|
|
self.matplot_axes.set_xscale( "linear" )
|
|
if ymin <= 0 or y_scale == "lin":
|
|
self.matplot_axes.set_yscale( "linear" )
|
|
|
|
self.matplot_axes.set_xlim( xmin, xmax )
|
|
self.matplot_axes.set_ylim( ymin, ymax )
|
|
|
|
# finally decide about x log plot
|
|
if x_scale == "log10" and xmin > 0:
|
|
self.matplot_axes.set_xscale( "log", basex=10.0 )
|
|
self.matplot_axes.fmt_xdata = lambda x: "%g" % x
|
|
#elif x_scale=="log" and xmin>0:
|
|
# e scaling implementation not really useful
|
|
# self.matplot_axes.set_xscale("log", basex=numpy.e)
|
|
if y_scale == "log10" and ymin > 0:
|
|
self.matplot_axes.set_yscale( "log", basex=10.0 )
|
|
self.matplot_axes.fmt_ydata = lambda x: "%g" % x
|
|
|
|
self.__rescale = False
|
|
|
|
self.measurementresultgraph = self.matplot_axes.errorbar( x=k, y=v, yerr=e, fmt="bx" )
|
|
|
|
[ k, v ] = in_result.get_lineplotdata( )
|
|
if k.shape[ 0 ] != 0 and v.shape == k.shape:
|
|
self.measurementresultline = self.matplot_axes.plot( k, v, 'r-' )
|
|
|
|
|
|
# Any title to be set?
|
|
title = in_result.get_title( ) + ""
|
|
if title is not None:
|
|
self.matplot_axes.set_title( title )
|
|
else:
|
|
self.matplot_axes.set_title( "" )
|
|
|
|
self.matplot_canvas.draw_idle( )
|
|
del k, v, e
|
|
del in_result
|
|
|
|
def renew_display( self ):
|
|
"""
|
|
set all properties of display
|
|
we are inside gtk/gdk lock
|
|
"""
|
|
self.clear_display( )
|
|
to_draw = self.data_pool[ self.displayed_data[ 0 ] ]
|
|
|
|
if to_draw is None:
|
|
return
|
|
self.update_display( )
|
|
|
|
|
|
class ScriptInterface:
|
|
"""
|
|
texts or code objects are executed as experiment and result script the backend is started with sufficient arguments
|
|
"""
|
|
|
|
def __init__( self, exp_script=None, res_script=None, backend_executable=None, spool_dir="spool", clear_jobs=True,
|
|
clear_results=True ):
|
|
"""
|
|
run experiment scripts and result scripts
|
|
"""
|
|
|
|
self.exp_script = exp_script
|
|
self.res_script = res_script
|
|
self.backend_executable = str( backend_executable )
|
|
self.spool_dir = os.path.abspath( spool_dir )
|
|
self.clear_jobs = clear_jobs
|
|
self.clear_results = clear_results
|
|
self.exp_handling = self.res_handling = None
|
|
|
|
self.exp_writer = self.res_reader = self.back_driver = None
|
|
if self.backend_executable is not None and self.backend_executable != "":
|
|
self.back_driver = BackendDriver.BackendDriver( self.backend_executable, spool_dir, clear_jobs,
|
|
clear_results )
|
|
if self.exp_script:
|
|
self.exp_writer = self.back_driver.get_exp_writer( )
|
|
if self.res_script:
|
|
self.res_reader = self.back_driver.get_res_reader( )
|
|
elif self.exp_script and self.res_script:
|
|
self.back_driver = None
|
|
self.res_reader = ResultReader.BlockingResultReader( spool_dir, clear_jobs=self.clear_jobs,
|
|
clear_results=self.clear_results )
|
|
self.exp_writer = ExperimentWriter.ExperimentWriter( spool_dir, inform_last_job=self.res_reader )
|
|
else:
|
|
self.back_driver = None
|
|
if self.exp_script:
|
|
self.exp_writer = ExperimentWriter.ExperimentWriter( spool_dir )
|
|
if self.res_script:
|
|
self.res_reader = ResultReader.ResultReader( spool_dir, clear_jobs=self.clear_jobs,
|
|
clear_results=self.clear_results )
|
|
self.data = DataPool( )
|
|
|
|
def runScripts( self ):
|
|
|
|
try:
|
|
# get script engines
|
|
self.exp_handling = self.res_handling = None
|
|
if self.exp_script and self.exp_writer:
|
|
self.exp_handling = ExperimentHandling.ExperimentHandling( self.exp_script, self.exp_writer, self.data )
|
|
if self.res_script and self.res_reader:
|
|
self.res_handling = ResultHandling.ResultHandling( self.res_script, self.res_reader, self.data )
|
|
|
|
# start them
|
|
if self.back_driver is not None:
|
|
self.back_driver.start( )
|
|
while (not self.back_driver.quit_flag.isSet( ) and \
|
|
(self.back_driver.core_pid is None or self.back_driver.core_pid <= 0)):
|
|
while gtk.events_pending():
|
|
gtk.main_iteration_do(False)
|
|
self.back_driver.quit_flag.wait( 0.1 )
|
|
if self.exp_handling:
|
|
self.exp_handling.start( )
|
|
if self.res_handling:
|
|
self.res_handling.start( )
|
|
finally:
|
|
self.exp_writer = self.res_reader = None
|
|
|
|
def __del__( self ):
|
|
self.exp_writer = None
|
|
self.res_reader = None
|
|
self.back_driver = None
|
|
self.data = None
|
|
self.exp_handling = None
|
|
self.res_handling = None
|
|
|