Public Access
fix return-value/deprecated API bugs, accumulation lock leaks, code cleanup
- raise instead of silent None for unsupported operands and empty negation - validate before acquiring lock in Accumulation operators (no RLock leak) - Accumulation._copy_provenance helper, ADC_Result._copy_metadata for all result paths - shared _sandbox_stdout_redirect contextmanager for sandboxed scripts - TemperatureMonitor dead code removal (gtk helper, traceback, unused properties) - new unit tests incl. lock-freedom regression test - gitignore sphinx generated doc/_autosummary/ and doc/damaris/
This commit is contained in:
1 parent
91ade4376c
commit
f9d8de7697
26 files changed
+590
-236
No files matched your search
@@ -52,6 +52,31 @@ from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FigureCan
|
||||
|
||||
max_points_to_display = 0
|
||||
|
||||
|
||||
def apply_font_css(widget, font_string):
|
||||
"""
|
||||
Non-deprecated replacement for the deprecated widget.modify_font():
|
||||
converts a pango font description string (family, optional size in pt,
|
||||
optional weight) into a CSS rule applied to the widget's style context.
|
||||
|
||||
Examples:
|
||||
>>> apply_font_css(textview, "Monospace 10")
|
||||
>>> apply_font_css(label, "sans bold 16")
|
||||
"""
|
||||
fd = pango.FontDescription(font_string)
|
||||
decl = "font-family: %s;" % fd.get_family()
|
||||
if fd.get_size() > 0 and not fd.get_size_is_absolute():
|
||||
decl += " font-size: %.6gpt;" % (fd.get_size() / float(pango.SCALE))
|
||||
if fd.get_weight() != pango.Weight.NORMAL:
|
||||
decl += " font-weight: %d;" % fd.get_weight()
|
||||
provider = gtk.CssProvider()
|
||||
provider.load_from_data(("%s { %s }" % (widget.get_css_name(), decl)).encode("utf-8"))
|
||||
context = widget.get_style_context()
|
||||
context.add_provider(provider, gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
|
||||
# keep a reference so the provider is not garbage collected
|
||||
setattr(widget, "_damaris_font_css_provider", provider)
|
||||
|
||||
|
||||
import matplotlib.axes
|
||||
import matplotlib.figure
|
||||
|
||||
@@ -267,12 +292,10 @@ class DamarisGUI:
|
||||
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
|
||||
@@ -822,14 +845,12 @@ class DamarisGUI:
|
||||
dump_size = os.stat( self.dump_filename ).st_size / 1e6
|
||||
logger.info("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:
|
||||
@@ -838,9 +859,7 @@ class DamarisGUI:
|
||||
if self.save_thread is None and self.dump_filename != "":
|
||||
logger.info("All subprocesses ended, saving data pool")
|
||||
# Disable run button during data saving to prevent UI confusion
|
||||
gdk.threads_enter()
|
||||
self.toolbar_run_button.set_sensitive(False)
|
||||
gdk.threads_leave()
|
||||
|
||||
# thread to save data...
|
||||
self.save_thread = threading.Thread( target=self.dump_states, name="dump states" )
|
||||
@@ -850,10 +869,8 @@ class DamarisGUI:
|
||||
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:
|
||||
logger.warning("Subprocess(es) still running: %s", ', '.join( [s.getName( ) for s in still_running] ))
|
||||
return True
|
||||
@@ -878,12 +895,10 @@ class DamarisGUI:
|
||||
|
||||
# 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( )
|
||||
return False
|
||||
|
||||
# dump states?
|
||||
@@ -1334,8 +1349,7 @@ class LogWindow:
|
||||
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)
|
||||
apply_font_css(self.textview, "Monospace 10")
|
||||
self.textbuffer = self.textview.get_buffer( )
|
||||
self.last_timetag = None
|
||||
self( "Started in directory %s\n" % os.getcwd( ) )
|
||||
@@ -1351,11 +1365,9 @@ class LogWindow:
|
||||
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:
|
||||
@@ -1449,22 +1461,20 @@ class ScriptWidgets:
|
||||
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)
|
||||
apply_font_css(self.experiment_script_textview, "monospace")
|
||||
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)
|
||||
apply_font_css(self.data_handling_textview, "monospace")
|
||||
|
||||
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
|
||||
@@ -1918,7 +1928,6 @@ get_ylabel set_ylabel"""
|
||||
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 )
|
||||
@@ -1944,7 +1953,6 @@ get_ylabel set_ylabel"""
|
||||
parent_window = self.xml_gui.get_object( "main_window" )
|
||||
dialog = gtk.FileChooserDialog( title="Open HDF5 File...",
|
||||
parent=parent_window,
|
||||
action=gtk.FileChooserAction.OPEN,
|
||||
buttons=("_Open", gtk.ResponseType.OK, "_Cancel", gtk.ResponseType.CANCEL)
|
||||
)
|
||||
dialog.set_default_response( gtk.ResponseType.OK )
|
||||
@@ -2106,8 +2114,8 @@ get_ylabel set_ylabel"""
|
||||
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_property("action", gtk.FileChooserAction.SAVE)
|
||||
|
||||
dialog.set_default_response( gtk.ResponseType.OK )
|
||||
dialog.set_select_multiple( False )
|
||||
@@ -2671,11 +2679,9 @@ pygobject version %(pygobject)s
|
||||
title="Select Temperature Controller Device",
|
||||
parent=None,
|
||||
action=gtk.FileChooserAction.OPEN,
|
||||
buttons=(
|
||||
gtk.STOCK_CANCEL, gtk.ResponseType.CANCEL,
|
||||
gtk.STOCK_OPEN, gtk.ResponseType.OK
|
||||
)
|
||||
)
|
||||
dialog.add_button("Cancel", gtk.ResponseType.CANCEL)
|
||||
dialog.add_button("Open", gtk.ResponseType.OK)
|
||||
|
||||
# Set default folder to /dev/serial/by-id/ if it exists
|
||||
default_folder = "/dev/serial/by-id/"
|
||||
@@ -2767,10 +2773,10 @@ pygobject version %(pygobject)s
|
||||
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 ) )
|
||||
apply_font_css(experiment_script_textview, font)
|
||||
data_handling_textview = self.xml_gui.get_object( "data_handling_textview" )
|
||||
if data_handling_textview:
|
||||
data_handling_textview.modify_font( pango.FontDescription( font ) )
|
||||
apply_font_css(data_handling_textview, font)
|
||||
|
||||
def browse_backend_executable_dialog( self, widget ):
|
||||
"""
|
||||
@@ -3031,7 +3037,7 @@ class MonitorWidgets:
|
||||
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"))
|
||||
apply_font_css(self.matplot_toolbar.message, "sans bold 16")
|
||||
except:
|
||||
pass
|
||||
|
||||
@@ -3214,50 +3220,46 @@ class MonitorWidgets:
|
||||
register a listener and save reference to data
|
||||
assume to be in gtk/gdk lock
|
||||
"""
|
||||
gdk.threads_enter( )
|
||||
try:
|
||||
if self.data_pool is not None:
|
||||
# maybe some extra cleanup needed
|
||||
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.update_counter_lock.acquire( )
|
||||
self.update_counter = 0
|
||||
self.update_counter_lock.release( )
|
||||
|
||||
# display states
|
||||
self.source_list_reset( )
|
||||
self.__rescale = True
|
||||
if self.data_pool is not None:
|
||||
# maybe some extra cleanup needed
|
||||
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.display_source_path_label.set_label( "" )
|
||||
self.clear_display( )
|
||||
|
||||
self.data_pool.unregister_listener( self.datapool_listener )
|
||||
self.data_pool = None
|
||||
|
||||
if data_pool is not None:
|
||||
# keep track of data
|
||||
self.data_pool = data_pool
|
||||
self.data_pool.register_listener( self.datapool_listener )
|
||||
|
||||
# Populate source list with existing data since the listener
|
||||
# was registered after the data was loaded
|
||||
keys = sorted([k for k in data_pool if k != "__builtins__"])
|
||||
for key in keys:
|
||||
self.source_list_add(key)
|
||||
|
||||
# Select first data item if available
|
||||
keys = [k for k in data_pool if not k.startswith("__")]
|
||||
if keys:
|
||||
keys.sort()
|
||||
first_key = keys[0]
|
||||
first_iter = self.source_list_find(first_key.split("/"))
|
||||
if first_iter:
|
||||
self.display_source_combobox.set_active_iter(first_iter)
|
||||
finally:
|
||||
gdk.threads_leave( )
|
||||
self.update_counter_lock.acquire( )
|
||||
self.update_counter = 0
|
||||
self.update_counter_lock.release( )
|
||||
|
||||
# display states
|
||||
self.source_list_reset( )
|
||||
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 )
|
||||
|
||||
# Populate source list with existing data since the listener
|
||||
# was registered after the data was loaded
|
||||
keys = sorted([k for k in data_pool if k != "__builtins__"])
|
||||
for key in keys:
|
||||
self.source_list_add(key)
|
||||
|
||||
# Select first data item if available
|
||||
keys = [k for k in data_pool if not k.startswith("__")]
|
||||
if keys:
|
||||
keys.sort()
|
||||
first_key = keys[0]
|
||||
first_iter = self.source_list_find(first_key.split("/"))
|
||||
if first_iter:
|
||||
self.display_source_combobox.set_active_iter(first_iter)
|
||||
|
||||
#################### observing data structures and produce idle events
|
||||
|
||||
@@ -3348,11 +3350,7 @@ class MonitorWidgets:
|
||||
if self.update_counter > 10:
|
||||
logger.warning("Update queue too long (%d>10): skipping one update", self.update_counter)
|
||||
else:
|
||||
gdk.threads_enter( )
|
||||
try:
|
||||
self.update_display( )
|
||||
finally:
|
||||
gdk.threads_leave( )
|
||||
self.update_display( )
|
||||
else:
|
||||
# unregister old one
|
||||
if self.displayed_data[ 1 ] is not None and hasattr( self.displayed_data[ 1 ],
|
||||
@@ -3366,20 +3364,13 @@ class MonitorWidgets:
|
||||
if self.update_counter > 10:
|
||||
logger.warning("Update queue too long (%d>10): skipping one update", self.update_counter)
|
||||
else:
|
||||
gdk.threads_enter( )
|
||||
try:
|
||||
self.renew_display( )
|
||||
finally:
|
||||
gdk.threads_leave( )
|
||||
self.renew_display( )
|
||||
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 ]
|
||||
@@ -3389,13 +3380,10 @@ class MonitorWidgets:
|
||||
# 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( )
|
||||
self.displayed_data = [ None, None ]
|
||||
self.clear_display( )
|
||||
gdk.threads_leave( )
|
||||
return
|
||||
|
||||
def update_display_idle_event( self, subject=None ):
|
||||
@@ -3409,11 +3397,7 @@ class MonitorWidgets:
|
||||
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( )
|
||||
self.update_display( )
|
||||
|
||||
|
||||
######################## events from buttons
|
||||
@@ -4159,7 +4143,7 @@ class ScriptInterface:
|
||||
# start them
|
||||
if self.back_driver is not None:
|
||||
self.back_driver.start( )
|
||||
while (not self.back_driver.quit_flag.isSet( ) and \
|
||||
while (not self.back_driver.quit_flag.is_set( ) and \
|
||||
(self.back_driver.core_pid is None or self.back_driver.core_pid <= 0)):
|
||||
while gtk.events_pending():
|
||||
gtk.main_iteration_do(False)
|
||||
|
||||
Reference in new issue
Block a user