Create automatically override function?
Hello,
I have function which the user needs to modify in order to get some functionality. In order to keep this as simple (for USER) as possible, I am hoping to have button, which will open main procedure window and type in
variable someparam
//do something
end
I have the function UserHookFunction defined in the code, but need to give user chance to use modified version they want to use. I have no expectations of user high profiency, but the function is pretty simple and most can modify it.
Normally I would open notebook and provide the template and instructions, but it would be nicer to give them the template, with the override keyword, in the main procedure window and open this all for them.
Is this possible? I can get the function text with:
ProcedureText(macroOrFunctionNameStr [, linesOfContext [, procedureWinTitleStr ]])
But then I run out of ideas. DoWIndow /F Procedure does not work and do not know how to paste there anything anyway.
Help, please?
Not wanting to greatly encourage this level of hackery, I am intrigued to wonder whether some combination of DisplayProcedure, PutScrapText and DoIgorMenu "Edit", "Paste" might suffice.
Plus Execute/P " COMPILEPROCEDURES".
December 4, 2020 at 08:45 pm - Permalink
Here is the code that achieves this unholy hackery (based on code by Tony):
String currScrap = GetScrapText() // copy current scrap text
DisplayProcedure/W=Procedure
DoIgorMenu "Edit" "Select All"
DoIgorMenu "Edit" "Copy"
PutScrapText InjectUserFunc(GetScrapText()) // modify procedure code
DoIgorMenu "Edit" "Paste"
PutScrapText currScrap // put previous scrap text back
Execute/P/Q/Z "COMPILEPROCEDURES " // recompile all
HideProcedures // hide all procedure windows
End
Function/S InjectUserFunc(String procText)
String newCode = "\r\rFunction test()\r\tprint \"test\"\rEnd"
return procText+newCode
End
Running InsertIntoMainProc() will secretly modify the code in the main procedure window via InjectUserFunc(). Do what you want here. Currently, it only writes a new function 'test()' in there. I could imagine you read the user-written function from a notebook, check for sanity and put it in the right place inside procText here. You have to do the error and sanity checking yourself. The procedure window will not compile without error if something is wrong. For added robustness I recommend to put above code into an independent module.
December 5, 2020 at 03:08 am - Permalink
Thanks!!!!
That is a great hack with nice and tidy code. I completely understand the danger of this method...
This is great use of DoIgorMenu. I rarely used up to now, that but clearly need to learn more ;-)
Sounds like fun afternoon today... Once more, thanks a LOT.
December 5, 2020 at 08:41 am - Permalink
Just to add a thought. Do I understand your intent? You want to allow users to create their own analysis "plug-in" method editing it "on-the-fly" while the package is running?
(This sounds like a step into a danger zone for many reasons but if so ...)
I might offer an approach without the extensive hacking of inserting into the main procedure window.
Create a separate .ipf file that is called by #include in your main procedure. In that procedure file, include the blank template function with a return.
variable rtnv = 0
// put your analysis function below
// store the result in the rtnv variable
return rtnv
end
In your main function, call this function with the assurance that it will return the value of rtnv.
When you want to allow users to edit this "on-the-fly", have your button or macro menu call open this specific procedure window for them to make the changes.
The advantage of editing the main procedure is that the plug-in remain resident only to that specific experiment. Perhaps this advantage can be recovered by doing the same thing to edit in a Notebook window instead of what will amount to a global #included procedure file.
December 5, 2020 at 09:24 am - Permalink
There are many ways - many better ways, to be precise - how to do what I need correctly. The point is I need something dumb simple from user point of view. With the code from Stephan above I made this work in 15 minutes. Added check to prevent creating the override function second time and it is working fine. If override function does not exist, it gets created and displayed. If it exists, it gets displayed. Important benefit: general code does not change, this override function is specific to current Igor experiment. Nice hack!!
December 5, 2020 at 03:32 pm - Permalink
Great that it worked well for you. Again, to make it more robust you may want to entertain the idea of putting your controlling code into an independent module. This may be a hassle, but the big benefit is than independent modules stay complied and functional even if your users screw up big time and produce a function full of errors. Independent modules are also hidden, so that users cannot sniff around or screw with the code (this may be a drawback, depending on how open you are with your projects). Also, if your project is already big it will be a pain to implement this. Anyway, I leave this here:
DisplayHelpTopic "Independent Modules"
December 6, 2020 at 02:08 am - Permalink
I don't think that a function override will work when the original function is in a different namespace, so it's unlikely that an independent module can be used. Is the return value important here?
I ran a quick test involving conditional compilation and managed to crash both Igor 8 and 9 in different ways, so be cautious!
Edit: This comment was written when Igor 9 was in an early stage of beta testing. Any bugs that caused a crash have long since been fixed.
December 6, 2020 at 02:51 am - Permalink
here is what i tried:
in the procedure window
print GetIndependentModuleName()
return 1
end
in second procedure window:
function doit()
test()
end
function test()
variable /G varReturn=nan
NVAR varReturn=varReturn
execute /Z "varReturn=test()"
if(v_flag==0)
return varReturn
endif
print GetIndependentModuleName()
end
I thought this might work based on
but managed only to crash Igor :(
December 6, 2020 at 03:22 am - Permalink
OK, I see that independent modules actually make things more unstable in some cases. ;) If you have an independent module going then you can just skip the Override. But this will not work for a hook function (this seems to be the goal here?) which needs to be always present in some form. If it is OK to not run the user function if it does not exist, e.g., by wrapping the user function call in an if-statement (check via Exists("UserHookFunction") == 6) inside the real hook function, then it might work. But, yeah, we don't know what Jan is really aiming for. If it is sufficient to have some really simple (user-chosen) calculation etc. executed, then grabbing the input string and throwing it at an Execute command might be enough.
December 6, 2020 at 03:59 am - Permalink
I think I have found the hack I was looking for :)
in procglobal:
// override code goes here
print GetIndependentModuleName()
return 1
end
function test(variable v1)
variable /G varReturn=nan
NVAR varReturn=varReturn
string cmd
sprintf cmd, "varReturn=procglobal#userFunc(%g)", v1
execute /Z cmd
if(v_flag==0)
return varReturn
endif
// default code goes here
print GetIndependentModuleName()
return 2
end
December 6, 2020 at 04:22 am - Permalink
In reply to I think I have found the… by tony
Note that ProcGlobal# is required in the string for execute.
When UserFunc exists, it replaces the default code in imName#test()
December 6, 2020 at 04:26 am - Permalink
Maybe what you should be using are funcRefs. The prototype would have the default code.
DisplayHelpTopic "Function References"
December 22, 2021 at 05:09 pm - Permalink