[GRASS-dev] Python scripts receiving input from the GRASS GUI?

Dear Devs,
what would be the best and least invasive way of receiving input from the grass GUI to use in python scripts. The following functionalities are on my mind:

- when double-clicking a line in the dbmgr, trigger a script/function using the line's content as input
- when selecting a feature in the map display, trigger a script/function using the query results as input

I guess changing the effect of a dbmgr line double-click can only be changed by fiddling with the gui/wxpython/dbmgr module files. Receiving the select query results as input on the command line is in theory possible with the 'Redirect to console' option in the select pop-up, but how can I read the GUI console?

In case someone had similar intentions and has some clues, I would appreciate hearing about them.

Best regards,
Michel

Hi Michel,

···

On Thu, Aug 13, 2015 at 10:32 AM, Michel Wortmann <wortmann@pik-potsdam.de> wrote:

Dear Devs,
what would be the best and least invasive way of receiving input from the grass GUI to use in python scripts. The following functionalities are on my mind:

  • when double-clicking a line in the dbmgr, trigger a script/function using the line’s content as input
  • when selecting a feature in the map display, trigger a script/function using the query results as input

I guess changing the effect of a dbmgr line double-click can only be changed by fiddling with the gui/wxpython/dbmgr module files. Receiving the select query results as input on the command line is in theory possible with the ‘Redirect to console’ option in the select pop-up, but how can I read the GUI console?

In case someone had similar intentions and has some clues, I would appreciate hearing about them.

these are reasonable requests. I suppose this would make your module more convenient for users to use. Unfortunately, there is no easy way. Anyway, these are the options:

  1. Create a new functionality similar to the button to select coordinates. Module would specify option type as feature ID and when the module’s GUI is started from the main GUI, the button is associated with Map Display. User clicks the button. Clicks in Map Display. Some query function is invoked and result goes to the input field in module’s GUI. There is already a “template” for implementing this - the coordinates button.

  2. Create a plugin system for wxGUI (thanks to Python relatively simple) and writing a plugin. If somebody wants to do that, please contact me, I can give you few pointers.

  3. Create a new feature in GUI Command Console to start a module with current output in console as standard input for the module. I’m not sure if this would overcomplicate things or how this can be made actually convenient for users, but it is en interesting option.

  4. For attribute data (dbmgr), “copy and paste” the line’s content can be enough. Same for query results. For query, the copy is implemented but the format might not be good for parsing. For dbmgr, the button to copy should be easy to add. This seems convenient enough for user.

I suggest to first check the option number 4. The most general option is number 2 but when implemented, it would require writing a module to do the processing and then also a plugin for GUI to do the interaction. In this light option number 1 seems as a good way.

Best,

Vaclav

Hi Vaclav,
thanks for the ideas. I have already implemented an idea which I find works very well and is based on an add on, which works well with GRASS’ existing addon structure. But it did require me to add a few lines in the gui_core.query as well as the dbmgr.base python files, which I guess is the downside of it. Here is the implementation:

g.gui.triggers is a little addon that manages a sqlite table (triggers) in the current mapset, it records the vector name, the command and the argument=column mapping and additional arguments to be used. It can also be called with the arguments map= and cat= to actually do the triggering of the command listed in the table for the respective cat.

To implement the dbmgr double-clicking, I add the following to dbmgr.base to call g.gui.triggers if the addon exists and if the vector is listed in the triggers table.

%%%% in header:
try: import guitriggers
except: guitriggers=None

%%%% line 1063
if guitriggers and guitriggers.hasTrigger(**{a:self.dbMgrData[‘mapDBInfo’].layers[layer][a] for a in [‘table’,‘database’]}):
win.Bind(wx.EVT_LEFT_DCLICK, self.OnDataTrigger)
win.Bind(wx.EVT_COMMAND_LEFT_DCLICK, self.OnDataTrigger)
elif UserSettings.Get(group = ‘atm’, key = ‘leftDbClick’, subkey = ‘selection’) == 0:
%%%%

%%%% anywhere as method to the DbMgrBrowsePage class
def OnDataTrigger(self,event):
info = self.dbMgrData[‘mapDBInfo’].layers[self.selLayer]
tlist = self.FindWindowById(self.layerPage[self.selLayer][‘data’])

get categories

cats = map(int,tlist.GetSelectedItems())
guitriggers.trigger(info[‘table’],cats,database=info[‘database’])
return
%%%%

To implement the selection idea in the map display, I added a check box to the query dialog (if the addon exists). If it is checked, the command listed in the triggers table is executed. I added the following to the gui_core.query file:

%%%% in header:
try: import guitriggers
except: guitriggers=None

%%%% in QueryDialog.init line 77
if guitriggers:
self.trigger = wx.CheckBox(self.panel, label=_(“Enable trigger”))
self.trigger.SetValue(False)
hbox = wx.BoxSizer(wx.HORIZONTAL)
hbox.Add(item=self.trigger, proportion=0, flag=wx.EXPAND | wx.RIGHT, border=5)
self.mainSizer.Add(item=hbox, proportion=0, flag=wx.EXPAND | wx.ALL, border=5)
%%%%

%%%% in QueryDialog.SetData, line 99
if guitriggers and self.trigger.IsChecked():
for m in data[1:]:
info = m[m.keys()[0]] #unpack
try: guitriggers.trigger(info[‘Table’].split()[0],[info[‘Category’]],
database=info[‘Database’].split()[0])
except: pass
%%%%

I have not found any major flaws with this approach, the only downside being the few added lines in the existing code (which are all pretty much copied from existing functionalities). The ‘guitriggers’ module is so far still imported as a python module, but I guess it doesnt take much to check whether it’s an installed addon.

Let me know if you want more details or would like me to upload the addon to the GRASS addon repro.

Regards,
Michel

···

On 08/19/2015 03:47 PM, Vaclav Petras wrote:

Hi Michel,

On Thu, Aug 13, 2015 at 10:32 AM, Michel Wortmann <wortmann@pik-potsdam.de> wrote:

Dear Devs,
what would be the best and least invasive way of receiving input from the grass GUI to use in python scripts. The following functionalities are on my mind:

  • when double-clicking a line in the dbmgr, trigger a script/function using the line’s content as input
  • when selecting a feature in the map display, trigger a script/function using the query results as input

I guess changing the effect of a dbmgr line double-click can only be changed by fiddling with the gui/wxpython/dbmgr module files. Receiving the select query results as input on the command line is in theory possible with the ‘Redirect to console’ option in the select pop-up, but how can I read the GUI console?

In case someone had similar intentions and has some clues, I would appreciate hearing about them.

these are reasonable requests. I suppose this would make your module more convenient for users to use. Unfortunately, there is no easy way. Anyway, these are the options:

  1. Create a new functionality similar to the button to select coordinates. Module would specify option type as feature ID and when the module’s GUI is started from the main GUI, the button is associated with Map Display. User clicks the button. Clicks in Map Display. Some query function is invoked and result goes to the input field in module’s GUI. There is already a “template” for implementing this - the coordinates button.

  2. Create a plugin system for wxGUI (thanks to Python relatively simple) and writing a plugin. If somebody wants to do that, please contact me, I can give you few pointers.

  3. Create a new feature in GUI Command Console to start a module with current output in console as standard input for the module. I’m not sure if this would overcomplicate things or how this can be made actually convenient for users, but it is en interesting option.

  4. For attribute data (dbmgr), “copy and paste” the line’s content can be enough. Same for query results. For query, the copy is implemented but the format might not be good for parsing. For dbmgr, the button to copy should be easy to add. This seems convenient enough for user.

I suggest to first check the option number 4. The most general option is number 2 but when implemented, it would require writing a module to do the processing and then also a plugin for GUI to do the interaction. In this light option number 1 seems as a good way.

Best,

Vaclav

On Wed, Aug 19, 2015 at 11:33 AM, Michel Wortmann <wortmann@pik-potsdam.de>
wrote:

Hi Vaclav,
thanks for the ideas. I have already implemented an idea which I find
works very well and is based on an add on, which works well with GRASS'
existing addon structure. But it did require me to add a few lines in the
gui_core.query as well as the dbmgr.base python files, which I guess is the
downside of it. Here is the implementation:

g.gui.triggers is a little addon that manages a sqlite table
(__triggers__) in the current mapset, it records the vector name, the
command and the argument=column mapping and additional arguments to be
used. It can also be called with the arguments map= and cat= to actually do
the triggering of the command listed in the table for the respective cat.

To implement the dbmgr double-clicking, I add the following to dbmgr.base
to call g.gui.triggers if the addon exists and if the vector is listed in
the __triggers__ table.

%%%% in header:
try: import guitriggers
except: guitriggers=None

%%%% line 1063
        if guitriggers and
guitriggers.hasTrigger(**{a:self.dbMgrData['mapDBInfo'].layers[layer][a]
for a in ['table','database']}):
            win.Bind(wx.EVT_LEFT_DCLICK, self.OnDataTrigger)
            win.Bind(wx.EVT_COMMAND_LEFT_DCLICK, self.OnDataTrigger)
        elif UserSettings.Get(group = 'atm', key = 'leftDbClick', subkey =
'selection') == 0:
%%%%

%%%% anywhere as method to the DbMgrBrowsePage class
    def OnDataTrigger(self,event):
        info = self.dbMgrData['mapDBInfo'].layers[self.selLayer]
        tlist = self.FindWindowById(self.layerPage[self.selLayer]['data'])
        # get categories
        cats = map(int,tlist.GetSelectedItems())
        guitriggers.trigger(info['table'],cats,database=info['database'])
        return
%%%%

To implement the selection idea in the map display, I added a check box to
the query dialog (if the addon exists). If it is checked, the command
listed in the __triggers__ table is executed. I added the following to the
gui_core.query file:

%%%% in header:
try: import guitriggers
except: guitriggers=None

%%%% in QueryDialog.__init__ line 77
        if guitriggers:
           self.trigger = wx.CheckBox(self.panel, label=_("Enable
trigger"))
           self.trigger.SetValue(False)
           hbox = wx.BoxSizer(wx.HORIZONTAL)
           hbox.Add(item=self.trigger, proportion=0, flag=wx.EXPAND |
wx.RIGHT, border=5)
           self.mainSizer.Add(item=hbox, proportion=0, flag=wx.EXPAND |
wx.ALL, border=5)
%%%%

%%%% in QueryDialog.SetData, line 99
        if guitriggers and self.trigger.IsChecked():
            for m in data[1:]:
                info = m[m.keys()[0]] #unpack
                try:
guitriggers.trigger(info['Table'].split()[0],[info['Category']],
                                    database=info['Database'].split()[0])
                except: pass
%%%%

I have not found any major flaws with this approach, the only downside
being the few added lines in the existing code (which are all pretty much
copied from existing functionalities). The 'guitriggers' module is so far
still imported as a python module, but I guess it doesnt take much to check
whether it's an installed addon.

I'm glad you got that far. I don't have any opinion on this "triggers" yet.
This seems like a general "custom action" system; something like a limited
plugin system. It could be quite useful but we should have a more general
analysis of this before putting something to the main code base. Also,
typically for wxGUI, some refactoring is needed before adding new features.

Anyway, you should not put the "triggers" to SQLite database of the current
Mapset, that's for data. This is GUI and behavior, this should go to
~/.grass7. It can be text file or anything else.

Also, main code should not depend on an addon, either main code should
implement a more general mechanism or addon's code should be moved to the
main code.

Let me know if you want more details or would like me to upload the addon
to the GRASS addon repro.

When ready, please post here the code of the module to test and diff for
the changes in existing code.

Vaclav

Regards,
Michel

On 08/19/2015 03:47 PM, Vaclav Petras wrote:

Hi Michel,

On Thu, Aug 13, 2015 at 10:32 AM, Michel Wortmann <wortmann@pik-potsdam.de
> wrote:

Dear Devs,
what would be the best and least invasive way of receiving input from the
grass GUI to use in python scripts. The following functionalities are on my
mind:

- when double-clicking a line in the dbmgr, trigger a script/function
using the line's content as input
- when selecting a feature in the map display, trigger a script/function
using the query results as input

I guess changing the effect of a dbmgr line double-click can only be
changed by fiddling with the gui/wxpython/dbmgr module files. Receiving the
select query results as input on the command line is in theory possible
with the 'Redirect to console' option in the select pop-up, but how can I
read the GUI console?

In case someone had similar intentions and has some clues, I would
appreciate hearing about them.

these are reasonable requests. I suppose this would make your module more
convenient for users to use. Unfortunately, there is no easy way. Anyway,
these are the options:

1. Create a new functionality similar to the button to select coordinates.
Module would specify option type as feature ID and when the module's GUI is
started from the main GUI, the button is associated with Map Display. User
clicks the button. Clicks in Map Display. Some query function is invoked
and result goes to the input field in module's GUI. There is already a
"template" for implementing this - the coordinates button.

2. Create a plugin system for wxGUI (thanks to Python relatively simple)
and writing a plugin. If somebody wants to do that, please contact me, I
can give you few pointers.

3. Create a new feature in GUI Command Console to start a module with
current output in console as standard input for the module. I'm not sure if
this would overcomplicate things or how this can be made actually
convenient for users, but it is en interesting option.

4. For attribute data (dbmgr), "copy and paste" the line's content can be
enough. Same for query results. For query, the copy is implemented but the
format might not be good for parsing. For dbmgr, the button to copy should
be easy to add. This seems convenient enough for user.

I suggest to first check the option number 4. The most general option is
number 2 but when implemented, it would require writing a module to do the
processing and then also a plugin for GUI to do the interaction. In this
light option number 1 seems as a good way.

Best,
Vaclav

Michel, one more thing:

On Wed, Aug 19, 2015 at 9:47 AM, Vaclav Petras <wenzeslaus@gmail.com> wrote:

4. For attribute data (dbmgr), "copy and paste" the line's content can be
enough. Same for query results. For query, the copy is implemented but the
format might not be good for parsing. For dbmgr, the button to copy should
be easy to add. This seems convenient enough for user.

Perhaps, you should first answer (especially to yourself :slight_smile: why this
solution is not enough for you. If we would go with the "triggers" solution
we need will need anyway some rationale and example for the documentation
and this is perhaps a good place to start.

Thanks Vaclav,
I agree that it is a bit of a dirty implementation, but I guess I just wanted to make this work quickly.
First to your copy-paste solution: I was more looking for something more interactive/clickable to precisely avoid the typing/reformatting. Something to seamlessly parse a few arguments to existing modules/addons or even scripts.
Taking on your comments about the non-dependency and sqlite DB issue, I will rewrite g.gui.triggers to work with a file in ~/.grass7 and the existence of which could then be checked in the dbmgr.base file and the gui_core.query file. There could be a ‘Launch…’ section in the right-click menu maybe?
I’ll update you once I have come that far and maybe you can play around with it to see if it’s useful for general inclusion.

Best,
Michel

···

On 08/19/2015 06:30 PM, Vaclav Petras wrote:

On Wed, Aug 19, 2015 at 11:33 AM, Michel Wortmann <wortmann@pik-potsdam.de> wrote:

Hi Vaclav,
thanks for the ideas. I have already implemented an idea which I find works very well and is based on an add on, which works well with GRASS’ existing addon structure. But it did require me to add a few lines in the gui_core.query as well as the dbmgr.base python files, which I guess is the downside of it. Here is the implementation:

g.gui.triggers is a little addon that manages a sqlite table (triggers) in the current mapset, it records the vector name, the command and the argument=column mapping and additional arguments to be used. It can also be called with the arguments map= and cat= to actually do the triggering of the command listed in the table for the respective cat.

To implement the dbmgr double-clicking, I add the following to dbmgr.base to call g.gui.triggers if the addon exists and if the vector is listed in the triggers table.

%%%% in header:
try: import guitriggers
except: guitriggers=None

%%%% line 1063
if guitriggers and guitriggers.hasTrigger(**{a:self.dbMgrData[‘mapDBInfo’].layers[layer][a] for a in [‘table’,‘database’]}):
win.Bind(wx.EVT_LEFT_DCLICK, self.OnDataTrigger)
win.Bind(wx.EVT_COMMAND_LEFT_DCLICK, self.OnDataTrigger)
elif UserSettings.Get(group = ‘atm’, key = ‘leftDbClick’, subkey = ‘selection’) == 0:
%%%%

%%%% anywhere as method to the DbMgrBrowsePage class
def OnDataTrigger(self,event):
info = self.dbMgrData[‘mapDBInfo’].layers[self.selLayer]
tlist = self.FindWindowById(self.layerPage[self.selLayer][‘data’])

get categories

cats = map(int,tlist.GetSelectedItems())
guitriggers.trigger(info[‘table’],cats,database=info[‘database’])
return
%%%%

To implement the selection idea in the map display, I added a check box to the query dialog (if the addon exists). If it is checked, the command listed in the triggers table is executed. I added the following to the gui_core.query file:

%%%% in header:
try: import guitriggers
except: guitriggers=None

%%%% in QueryDialog.init line 77
if guitriggers:
self.trigger = wx.CheckBox(self.panel, label=_(“Enable trigger”))
self.trigger.SetValue(False)
hbox = wx.BoxSizer(wx.HORIZONTAL)
hbox.Add(item=self.trigger, proportion=0, flag=wx.EXPAND | wx.RIGHT, border=5)
self.mainSizer.Add(item=hbox, proportion=0, flag=wx.EXPAND | wx.ALL, border=5)
%%%%

%%%% in QueryDialog.SetData, line 99
if guitriggers and self.trigger.IsChecked():
for m in data[1:]:
info = m[m.keys()[0]] #unpack
try: guitriggers.trigger(info[‘Table’].split()[0],[info[‘Category’]],
database=info[‘Database’].split()[0])
except: pass
%%%%

I have not found any major flaws with this approach, the only downside being the few added lines in the existing code (which are all pretty much copied from existing functionalities). The ‘guitriggers’ module is so far still imported as a python module, but I guess it doesnt take much to check whether it’s an installed addon.

I’m glad you got that far. I don’t have any opinion on this “triggers” yet. This seems like a general “custom action” system; something like a limited plugin system. It could be quite useful but we should have a more general analysis of this before putting something to the main code base. Also, typically for wxGUI, some refactoring is needed before adding new features.

Anyway, you should not put the “triggers” to SQLite database of the current Mapset, that’s for data. This is GUI and behavior, this should go to ~/.grass7. It can be text file or anything else.

Also, main code should not depend on an addon, either main code should implement a more general mechanism or addon’s code should be moved to the main code.

Let me know if you want more details or would like me to upload the addon to the GRASS addon repro.

When ready, please post here the code of the module to test and diff for the changes in existing code.

Vaclav

Regards,
Michel

On 08/19/2015 03:47 PM, Vaclav Petras wrote:

Hi Michel,

On Thu, Aug 13, 2015 at 10:32 AM, Michel Wortmann <wortmann@pik-potsdam.de> wrote:

Dear Devs,
what would be the best and least invasive way of receiving input from the grass GUI to use in python scripts. The following functionalities are on my mind:

  • when double-clicking a line in the dbmgr, trigger a script/function using the line’s content as input
  • when selecting a feature in the map display, trigger a script/function using the query results as input

I guess changing the effect of a dbmgr line double-click can only be changed by fiddling with the gui/wxpython/dbmgr module files. Receiving the select query results as input on the command line is in theory possible with the ‘Redirect to console’ option in the select pop-up, but how can I read the GUI console?

In case someone had similar intentions and has some clues, I would appreciate hearing about them.

these are reasonable requests. I suppose this would make your module more convenient for users to use. Unfortunately, there is no easy way. Anyway, these are the options:

  1. Create a new functionality similar to the button to select coordinates. Module would specify option type as feature ID and when the module’s GUI is started from the main GUI, the button is associated with Map Display. User clicks the button. Clicks in Map Display. Some query function is invoked and result goes to the input field in module’s GUI. There is already a “template” for implementing this - the coordinates button.

  2. Create a plugin system for wxGUI (thanks to Python relatively simple) and writing a plugin. If somebody wants to do that, please contact me, I can give you few pointers.

  3. Create a new feature in GUI Command Console to start a module with current output in console as standard input for the module. I’m not sure if this would overcomplicate things or how this can be made actually convenient for users, but it is en interesting option.

  4. For attribute data (dbmgr), “copy and paste” the line’s content can be enough. Same for query results. For query, the copy is implemented but the format might not be good for parsing. For dbmgr, the button to copy should be easy to add. This seems convenient enough for user.

I suggest to first check the option number 4. The most general option is number 2 but when implemented, it would require writing a module to do the processing and then also a plugin for GUI to do the interaction. In this light option number 1 seems as a good way.

Best,

Vaclav

On Thu, Aug 20, 2015 at 5:49 AM, Michel Wortmann <wortmann@pik-potsdam.de>
wrote:

I agree that it is a bit of a dirty implementation, but I guess I just
wanted to make this work quickly.

Sure this happens, but we need to avoid that as much as possible. That's
why I was mentioning that there is some refactoring connected to adding new
features - you need to change the code to add new feature seamlessly (and
perhaps to make adding a new one easier) but also fix old mistakes in the
code design (old lack of design :).

First to your copy-paste solution: I was more looking for something more
interactive/clickable to precisely avoid the typing/reformatting. Something
to seamlessly parse a few arguments to existing modules/addons or even
scripts.

In any case, you need to generate nice parseable text in GUI. For example,
context menu in Map Display can give you coordinates which are nicely
formatted for stdin or the coordinates option. Now there is also the button
I mentioned before but the method of coping well formated text from GUI and
pasting in to module GUI (option/field or direct input of file content) is
most general and easy to implement since it requires no coupling. Text
generation and module call are independent. Only the format must be the
same. The disadvantage is that user has to do one additional step. It also
seems that it fits well with the 80-20 rule: with 20% of effort and code
complexity we get 80% of use cases covered.

Taking on your comments about the non-dependency and sqlite DB issue, I
will rewrite g.gui.triggers to work with a file in ~/.grass7 and the
existence of which could then be checked in the dbmgr.base file and the
gui_core.query file. There could be a 'Launch...' section in the
right-click menu maybe?

Launch... sounds quite general but I'm not sure what exactly would be in
the dialog which would come up?

I'll update you once I have come that far and maybe you can play around
with it to see if it's useful for general inclusion.

I'm more and more convinced that you should first try the copy and paste
solution. It has a lot of overlap with what you want to do from
implementation point of view: you have to decide the format, implement the
buttons or context menu items which will create the text (but instead of
calling module, they will put the text into the clipboard) and finally you
have the implement the right input in the particular module you want to
call. Once you have this and you can test it and if it will not be
convincing enough, you can then insert the layer which will couple the two
parts together (Launch... dialog or association specified in ~/.grass7).

Vaclav