def__rename(self,index:int)->None:"""Rename tab Args: index: Index of the tab """self.SIG_RENAME.emit(index+1)def__remove(self,index:int)->None:"""Remove tab Args: index: Index of the tab """self.SIG_REMOVE.emit(index+1)def__update_tab_titles(self)->None:"""Update tab titles"""fornumber,titleinenumerate(self.__titles,1):self.setTabText(number-1,f"{number:02d}: {title}")
[docs]defadd_tab(self,macro:Macro)->int:"""Add tab Args: macro: Macro object Returns: int: Number of the tab (starting at 1) """self.__titles.append(macro.title)index=self.addTab(macro.editor,"")self.__update_tab_titles()returnindex+1# Numbering starts at 1
[docs]defremove_tab(self,number:int)->None:"""Remove tab Args: number: Number of the tab (starting at 1) """self.removeTab(number-1)self.__titles.pop(number-1)self.__update_tab_titles()
[docs]defget_widget(self,number:int)->CodeEditor:"""Return macro editor widget at number Args: number: Number of the tab (starting at 1) Returns: Macro editor widget """returnself.widget(number-1)
[docs]defset_current_number(self,number:int)->None:"""Set current tab number Args: number: Number of the tab (starting at 1) """self.setCurrentIndex(number-1)
[docs]defget_current_number(self)->int:"""Return current tab number Returns: int: Number of the tab (starting at 1) """returnself.currentIndex()+1
[docs]defset_tab_title(self,number:int,name:str)->None:"""Set tab title Args: number: Number of the tab (starting at 1) name: Macro name """self.__titles[number-1]=nameself.__update_tab_titles()
[docs]classMacroPanel(AbstractPanel,DockableWidgetMixin):"""Macro Panel widget Args: parent (QWidget): Parent widget """LOCATION=QC.Qt.RightDockWidgetAreaPANEL_STR=_("Macro panel")H5_PREFIX="DataLab_Mac"SIG_OBJECT_MODIFIED=QC.Signal()FILE_FILTERS=f"{_('Python files')} (*.py)"def__init__(self,parent:QW.QWidget|None=None)->None:super().__init__(parent)self.setWindowTitle(self.PANEL_STR)self.setWindowIcon(get_icon("libre-gui-cogs.svg"))self.setOrientation(QC.Qt.Vertical)self.context_menu=QW.QMenu()self.tabwidget_tb=QW.QToolBar(self)self.tabwidget_tb.setOrientation(QC.Qt.Vertical)self.console=PythonShellWidget(self,read_only=True)self.console.setMaximumBlockCount(5000)font=get_font(CONF,"console")font.setPointSize(10)self.console.set_font(font)self.console.write(_("-***- Macro Console -***-"),prompt=True)self.tabwidget=MacroTabs(self)self.tabwidget.SIG_RENAME.connect(self.rename_macro)self.tabwidget.SIG_REMOVE.connect(self.remove_macro)self.tabwidget.currentChanged.connect(self.__update_actions)tabwidget_with_tb=QW.QWidget(self)tabwidget_with_tb.setLayout(QW.QHBoxLayout())tabwidget_with_tb.layout().addWidget(self.tabwidget_tb)tabwidget_with_tb.layout().addWidget(self.tabwidget)# Put console in a groupbox to have a titleconsole_groupbox=QW.QGroupBox(_("Console"),self)console_groupbox.setLayout(QW.QHBoxLayout())console_groupbox.layout().addWidget(self.console)# Put console groupbox in a frame to have a nice marginconsole_frame=QW.QFrame(self)console_frame.setLayout(QW.QHBoxLayout())console_frame.layout().addWidget(console_groupbox)forwidgetin(tabwidget_with_tb,console_frame):self.addWidget(widget)self.setStretchFactor(0,2)self.setStretchFactor(1,1)self.run_action=Noneself.stop_action=Noneself.obj_actions:list[QW.QAction]=[]# Object-dependent actionsself.__macros:list[Macro]=[]self.setup_actions()
[docs]defupdate_color_mode(self)->None:"""Update color mode according to the current theme"""self.console.update_color_mode()formacroinself.__macros:macro.editor.update_color_mode()
[docs]defget_serializable_name(self,obj:Macro)->str:"""Return serializable name of object"""title=re.sub("[^-a-zA-Z0-9_.() ]+","",obj.title.replace("/","_"))name=f"{obj.PREFIX}{(self.__macros.index(obj)+1):03d}: {title}"returnname
[docs]defserialize_to_hdf5(self,writer:NativeH5Writer)->None:"""Serialize whole panel to a HDF5 file Args: writer: HDF5 writer """withwriter.group(self.H5_PREFIX):forobjinself.__macros:self.serialize_object_to_hdf5(obj,writer)
[docs]defdeserialize_from_hdf5(self,reader:NativeH5Reader)->None:"""Deserialize whole panel from a HDF5 file Args: reader: HDF5 reader """withreader.group(self.H5_PREFIX):fornameinreader.h5.get(self.H5_PREFIX,[]):# Contrary to signal or image panels, macros are not stored# in a group but directly in the root of the HDF5 fileobj=self.deserialize_object_from_hdf5(reader,name)self.add_object(obj)
def__len__(self)->int:"""Return number of objects"""returnlen(self.__macros)def__getitem__(self,nb:int)->Macro:"""Return object from its number (1 to N)"""returnself.__macros[nb-1]def__iter__(self):"""Iterate over objects"""returniter(self.__macros)
[docs]defget_macro(self,number_or_title:int|str|None=None)->Macro|None:"""Return macro at number (if number is None, return current macro) Args: number: Number of the macro (starting at 1) or title of the macro. Defaults to None (current macro). Returns: Macro object or None (if not found) """ifnumber_or_titleisNone:number_or_title=self.tabwidget.get_current_number()ifisinstance(number_or_title,str):returnself.get_macro(self.get_number_from_title(number_or_title))formacroinself.__macros:ifself.tabwidget.get_widget(number_or_title)ismacro.editor:returnmacroreturnNone
[docs]defget_number_from_title(self,title:str)->int|None:"""Return macro number from title Args: title: Title of the macro Returns: Number of the macro (starting at 1) or None (if not found) """fornumberinrange(1,self.tabwidget.count()+1):ifself.tabwidget.tabText(number-1).endswith(title):returnnumberreturnNone
[docs]defget_number_from_macro(self,macro:Macro)->int|None:"""Return macro number from macro object Args: macro: Macro object Returns: Number of the macro (starting at 1) or None (if not found) """fornumberinrange(1,self.tabwidget.count()+1):ifself.tabwidget.get_widget(number)ismacro.editor:returnnumberreturnNone
[docs]defget_macro_titles(self)->list[str]:"""Return list of macro titles"""return[macro.titleformacroinself.__macros]
[docs]defmacro_contents_changed(self)->None:"""One of the macro contents has changed"""self.SIG_OBJECT_MODIFIED.emit()
[docs]defrun_macro(self,number_or_title:int|str|None=None)->None:"""Run current macro Args: number: Number of the macro (starting at 1). Defaults to None (run current macro, or does nothing if there is no macro). """macro=self.get_macro(number_or_title)ifmacroisnotNone:macro:Macromacro.run()
[docs]defstop_macro(self,number_or_title:int|str|None=None)->None:"""Stop current macro Args: number: Number of the macro (starting at 1). Defaults to None (run current macro, or does nothing if there is no macro). """macro=self.get_macro(number_or_title)ifmacroisnotNone:macro:Macromacro.kill()
[docs]defmacro_state_changed(self,orig_macro:Macro,state:bool)->None:"""Macro state has changed (True: started, False: stopped) Args: orig_macro: Macro object state: State of the macro """macro=self.get_macro()ifmacroisorig_macro:self.run_action.setEnabled(notstate)self.stop_action.setEnabled(state)
[docs]defadd_macro(self)->Macro:"""Add macro, optionally with name Returns: Macro object """macro=self.create_object()self.add_object(macro)ifnotmacro.title:self.rename_macro()returnmacro
[docs]defmacro_name_changed(self,name:str)->None:"""Macro name has been changed Args: name: New name of the macro """number=self.get_number_from_macro(self.sender())ifnumberisnotNone:self.tabwidget.set_tab_title(number,name)
[docs]defrename_macro(self,number:int|None=None,title:str|None=None)->None:"""Rename macro Args: number: Number of the macro (starting at 1). Defaults to None. title: Title of the macro. Defaults to None. """macro=self.get_macro(number)assertisinstance(macro,Macro)iftitleisNone:title,valid=QW.QInputDialog.getText(self,_("Rename"),_("New title:"),QW.QLineEdit.Normal,macro.title,)title=titleifvalidelseNoneiftitle:macro.title=titleifnumberisnotNone:self.tabwidget.set_current_number(number)
[docs]defexport_macro_to_file(self,number_or_title:int|str|None=None,filename:str|None=None)->None:"""Export macro to file Args: number_or_title: Number of the macro (starting at 1) or title of the macro. Defaults to None. filename: Filename. Defaults to None. Raises: ValueError: If title is not found """macro=self.get_macro(number_or_title)assertisinstance(macro,Macro)iffilenameisNone:basedir=Conf.main.base_dir.get()withsave_restore_stds():filename,_filt=getsavefilename(self,_("Save as"),basedir,self.FILE_FILTERS)iffilename:withqt_try_loadsave_file(self.parent(),filename,"save"):Conf.main.base_dir.set(filename)macro.title=osp.basename(filename)macro.to_file(filename)
[docs]defimport_macro_from_file(self,filename:str|None=None)->int:"""Import macro from file Args: filename: Filename. Defaults to None. Returns: Number of the macro (starting at 1) """iffilenameisNone:basedir=Conf.main.base_dir.get()withsave_restore_stds():filename,_filt=getopenfilename(self,_("Open"),basedir,self.FILE_FILTERS)iffilename:withqt_try_loadsave_file(self.parent(),filename,"load"):Conf.main.base_dir.set(filename)macro=self.add_macro()macro.from_file(filename)returnself.get_number_from_macro(macro)return-1
[docs]defremove_macro(self,number_or_title:int|str|None=None)->None:"""Remove macro Args: number_or_title: Number of the macro (starting at 1) or title of the macro. Defaults to None. """ifnumber_or_titleisNone:number_or_title=self.tabwidget.get_current_number()ifisinstance(number_or_title,str):number_or_title=self.get_number_from_title(number_or_title)txt="<br>".join([_("When closed, the macro is <u>permanently destroyed</u>, ""unless it has been exported first."),"",_("Do you want to continue?"),])btns=QW.QMessageBox.StandardButton.Yes|QW.QMessageBox.StandardButton.Noifexecenv.unattended:choice=QW.QMessageBox.StandardButton.Yeselse:choice=QW.QMessageBox.warning(self,self.windowTitle(),txt,btns)ifchoice==QW.QMessageBox.StandardButton.Yes:self.tabwidget.remove_tab(number_or_title)self.__macros.pop(number_or_title-1)self.SIG_OBJECT_REMOVED.emit()