From f0ed633f3439d71ab5e617e7481dcfa5a22866fa Mon Sep 17 00:00:00 2001 From: Markus Rosenstihl Date: Thu, 26 Mar 2026 15:02:59 +0100 Subject: [PATCH] reworked the cursor --- src/gui/DamarisGUI.py | 238 ++++++++++++++++++++++------------------- src/gui/damaris3.glade | 17 +-- 2 files changed, 131 insertions(+), 124 deletions(-) diff --git a/src/gui/DamarisGUI.py b/src/gui/DamarisGUI.py index c6677fc..a13e025 100644 --- a/src/gui/DamarisGUI.py +++ b/src/gui/DamarisGUI.py @@ -206,7 +206,7 @@ class DamarisGUI: 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, @@ -241,7 +241,7 @@ class DamarisGUI: "on_main_notebook_switch_page": self.nopevent, "main_window_close": self.nopevent } - self.xml_gui.connect_signals(eventhandlers) + self.xml_gui.connect_signals(eventhandlers) self.main_window.maximize() self.main_window.show_all( ) @@ -255,8 +255,8 @@ class DamarisGUI: self.main_window.connect( "delete-event", self.quit_event ) self.main_window.set_icon_from_file( os.path.join( os.path.dirname( __file__ ), "DAMARIS3.png" ) ) self.main_window.set_title( "DAMARIS-%s" % __version__ ) - - + + def statusbar_init( self ): """ experiment and result thread status, backend state @@ -277,7 +277,7 @@ class DamarisGUI: 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 @@ -296,7 +296,7 @@ class DamarisGUI: # event handling: the real acitons in gui programming # first global events - + def nopevent(self, widget, data=None, otherargument=None): #print(widget) #print(data) @@ -310,7 +310,7 @@ class DamarisGUI: 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, @@ -320,7 +320,7 @@ class DamarisGUI: response = question.run() question.destroy() - + if response == gtk.ResponseType.CANCEL: return True elif response == gtk.ResponseType.YES: @@ -331,7 +331,7 @@ class DamarisGUI: 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, @@ -341,7 +341,7 @@ class DamarisGUI: response = question.run() question.destroy() - + if response == gtk.ResponseType.CANCEL: return True elif response == gtk.ResponseType.YES: @@ -352,8 +352,8 @@ class DamarisGUI: else: self.sw.save_file() self.main_notebook.set_current_page(current_page) - - + + self.config = None self.sw = None @@ -474,7 +474,7 @@ class DamarisGUI: 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") @@ -570,18 +570,18 @@ class DamarisGUI: 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 = "" - + + experimenttimetext = "" + if experimentstarttime > 0 and expectedexperimentruntime > 0: runtimemin, runtimesec = divmod(math.floor(experimentruntime), 60) runtimehours, runtimemin = divmod(runtimemin, 60) @@ -589,14 +589,14 @@ class DamarisGUI: 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 @@ -917,7 +917,7 @@ class DamarisGUI: # 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( ) @@ -1080,13 +1080,13 @@ class LogWindow: 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 ): @@ -1118,17 +1118,17 @@ class ScriptWidgets: # 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) @@ -1143,32 +1143,32 @@ class ScriptWidgets: 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 isinstance issubclass min max abs pow range xrange log_range lin_range staggered_range int float zip 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_xdate set_ydata""" - + keywordsbuffer = gtksourceview2.Buffer() keywordsbuffer.set_text(keywords) - + compexp = self.experiment_script_textview.get_completion() compres = self.data_handling_textview.get_completion() - + compwgen = gtksourceview2.CompletionWords() compwgen.register(keywordsbuffer) - + compwexp = gtksourceview2.CompletionWords() compwexp.register(self.experiment_script_textbuffer) - + compwres = gtksourceview2.CompletionWords() compwres.register(self.data_handling_textbuffer) - + compexp.add_provider(compwgen) compres.add_provider(compwgen) - + compexp.add_provider(compwexp) compres.add_provider(compwres) @@ -1185,8 +1185,8 @@ get_job_id get_description set_description get_xdata get_ydata set_xdate set_yda 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 ) @@ -1208,7 +1208,7 @@ get_job_id get_description set_description get_xdata get_ydata set_xdate set_yda 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") - + # my notebook self.main_notebook = self.xml_gui.get_object( "main_notebook" ) # config toolbar @@ -1271,7 +1271,7 @@ get_job_id get_description set_description get_xdata get_ydata set_xdate set_yda def set_toolbuttons_status( self ): """ - + ToDo: care about associated file names """ current_page = self.main_notebook.get_current_page( ) @@ -1368,7 +1368,7 @@ get_job_id get_description set_description get_xdata get_ydata set_xdate set_yda textview = None newline = None newcol = None - + if page == 0: textview = self.experiment_script_textview newline = self.experiment_script_line_indicator.get_value_as_int( ) - 1 @@ -1379,7 +1379,7 @@ get_job_id get_description set_description get_xdata get_ydata set_xdate set_yda newcol = self.data_handling_column_indicator.get_value_as_int( ) - 1 else: print("unknown line/column selector") - + textbuffer = textview.get_buffer( ) new_place = textbuffer.get_iter_at_line( newline ) if not newcol > new_place.get_chars_in_line( ): @@ -1401,13 +1401,13 @@ get_job_id get_description set_description get_xdata get_ydata set_xdate set_yda 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 ) @@ -1455,43 +1455,43 @@ get_job_id get_description set_description get_xdata get_ydata set_xdate set_yda 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( ) @@ -1499,14 +1499,14 @@ get_job_id get_description set_description get_xdata get_ydata set_xdate set_yda 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 @@ -1551,7 +1551,7 @@ get_job_id get_description set_description get_xdata get_ydata set_xdate set_yda response = question.run() question.destroy() - + if response == gtk.ResponseType.YES: self.save_file() elif response == gtk.ResponseType.CANCEL: @@ -1657,7 +1657,7 @@ get_job_id get_description set_description get_xdata get_ydata set_xdate set_yda response = question.run() question.destroy() - + if response != gtk.ResponseType.YES: return True @@ -1715,7 +1715,7 @@ get_job_id get_description set_description get_xdata get_ydata set_xdate set_yda 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 @@ -1725,7 +1725,7 @@ get_job_id get_description set_description get_xdata get_ydata set_xdate set_yda 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 @@ -1735,20 +1735,20 @@ get_job_id get_description set_description get_xdata get_ydata set_xdate set_yda 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() @@ -1775,14 +1775,14 @@ get_job_id get_description set_description get_xdata get_ydata set_xdate set_yda 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.TEXT_SEARCH_TEXT_ONLY) @@ -1790,7 +1790,7 @@ get_job_id get_description set_description get_xdata get_ydata set_xdate set_yda match_start, match_end = enditer.forward_search(searchstring, gtk.TEXT_SEARCH_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) @@ -1803,21 +1803,21 @@ get_job_id get_description set_description get_xdata get_ydata set_xdate set_yda 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() @@ -1832,20 +1832,20 @@ get_job_id get_description set_description get_xdata get_ydata set_xdate set_yda 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() - + if startit is not None and endit is not None: if current_page == 0: self.experiment_script_textbuffer.select_range(startit, endit) @@ -1853,9 +1853,9 @@ get_job_id get_description set_description get_xdata get_ydata set_xdate set_yda self.data_handling_textbuffer.select_range(startit, endit) elif current_page == 3: logbuffer.select_range(startit, endit) - - - + + + def new_file( self, widget, Data=None ): @@ -1872,12 +1872,12 @@ get_job_id get_description set_description get_xdata get_ydata set_xdate set_yda 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: @@ -1890,12 +1890,12 @@ get_job_id get_description set_description get_xdata get_ydata set_xdate set_yda 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( ) @@ -2014,7 +2014,7 @@ pygobject version %(pygobject)s 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 ): @@ -2204,7 +2204,7 @@ pygobject version %(pygobject)s dirs = os.path.dirname( filename ) if not os.path.isdir( dirs ): os.makedirs( dirs ) - + print("save config to: "+filename) configfile = open( filename, "w" ) @@ -2287,6 +2287,23 @@ class MonitorWidgets: # 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 + button = gtk.ToggleButton(label='') + try: + image = gtk.Image.new_from_file("/usr/share/icons/breeze/actions/24/crosshairs-symbolic.svg") + button.set_image(image) + except: + pass + button.set_tooltip_text('Enable Crosshairs') + button.show() + button.connect('toggled', self.toggle_cursor) + toolitem = gtk.ToolItem() + toolitem.add(button) + self.matplot_toolbar.insert(toolitem, 5) # Make cursor position text bigger and bold - access message label directly try: @@ -2314,6 +2331,12 @@ class MonitorWidgets: # 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 ) @@ -2665,7 +2688,7 @@ class MonitorWidgets: 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 @@ -2685,7 +2708,7 @@ class MonitorWidgets: response = question.run() question.destroy() - + if response != gtk.ResponseType.YES: return True @@ -2720,19 +2743,16 @@ class MonitorWidgets: def toggle_cursor(self, widget): """Toggle matplotlib crosshair cursor""" - from matplotlib.widgets import Cursor if widget.get_active(): - if hasattr(self, 'matplot_cursor') and self.matplot_cursor is not None: - self.matplot_cursor.disconnect_events() - self.matplot_cursor = Cursor(self.matplot_axes, useblit=False, color='red', linewidth=1) + self.matplot_cursor = matplotlib.widgets.Cursor(self.matplot_axes, useblit=False, color='red', linewidth=1) else: - if hasattr(self, 'matplot_cursor') and self.matplot_cursor is not None: - self.matplot_cursor.disconnect_events() - self.matplot_cursor = None + if hasattr(self, 'matplot_cursor'): + self.matplot_cursor = None self.matplot_canvas.draw_idle() def on_canvas_button_press(self, widget, event): """Handle button press events on matplotlib canvas""" + if event.button == 3: # Right click self.show_canvas_context_menu(event) return True @@ -2752,7 +2772,6 @@ class MonitorWidgets: toggle_cursor.connect("toggled", self.toggle_cursor) 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) @@ -2843,9 +2862,10 @@ class MonitorWidgets: """ 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": @@ -2948,10 +2968,10 @@ class MonitorWidgets: 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) diff --git a/src/gui/damaris3.glade b/src/gui/damaris3.glade index 05907bd..42bbde0 100644 --- a/src/gui/damaris3.glade +++ b/src/gui/damaris3.glade @@ -1044,7 +1044,7 @@ Public License instead of this License. True False - Source + Source: False @@ -1053,7 +1053,7 @@ Public License instead of this License. - + True False True @@ -1075,12 +1075,6 @@ Public License instead of this License. False False - - - - 0 - - 1 @@ -1114,12 +1108,6 @@ Public License instead of this License. False False - - - - 0 - - 1 @@ -1931,7 +1919,6 @@ Public License instead of this License. - True model3