[GRASS-SVN] r30486 - grass/trunk/gui/wxpython/gui_modules
svn_grass at osgeo.org
svn_grass at osgeo.org
Thu Mar 6 11:49:55 EST 2008
Author: martinl
Date: 2008-03-06 11:49:55 -0500 (Thu, 06 Mar 2008)
New Revision: 30486
Modified:
grass/trunk/gui/wxpython/gui_modules/gcmd.py
grass/trunk/gui/wxpython/gui_modules/menuform.py
grass/trunk/gui/wxpython/gui_modules/preferences.py
grass/trunk/gui/wxpython/gui_modules/wxgui_utils.py
Log:
wxGUI (menuform) Abort running command
(preferences) 'Command' settings (overwrite, close)
Modified: grass/trunk/gui/wxpython/gui_modules/gcmd.py
===================================================================
--- grass/trunk/gui/wxpython/gui_modules/gcmd.py 2008-03-06 07:05:05 UTC (rev 30485)
+++ grass/trunk/gui/wxpython/gui_modules/gcmd.py 2008-03-06 16:49:55 UTC (rev 30486)
@@ -25,6 +25,7 @@
import sys
import time
import errno
+import signal
import wx
@@ -123,7 +124,14 @@
def _close(self, which):
getattr(self, which).close()
setattr(self, which, None)
-
+
+ def kill(self):
+ """Try to kill running process"""
+ try:
+ os.kill(-self.pid, signal.SIGTERM) # kill whole group
+ except OSError:
+ pass
+
if subprocess.mswindows:
def send(self, input):
if not self.stdin:
@@ -205,6 +213,22 @@
if not conn.closed:
fcntl.fcntl(conn, fcntl.F_SETFL, flags)
+# Define notification event for thread completion
+EVT_RESULT_ID = wx.NewId()
+
+def EVT_RESULT(win, func):
+ """Define Result Event"""
+ win.Connect(-1, -1, EVT_RESULT_ID, func)
+
+class ResultEvent(wx.PyEvent):
+ """Simple event to carry arbitrary result data"""
+ def __init__(self, data):
+ wx.PyEvent.__init__(self)
+
+ self.SetEventType(EVT_RESULT_ID)
+
+ self.cmdThread = data
+
class Command:
"""
Run GRASS command in separate thread
@@ -428,16 +452,22 @@
Thread.__init__(self)
- self.cmd = cmd
- self.stdin = stdin
- self.stdout = stdout
- self.stderr = stderr
+ self.cmd = cmd
+ self.stdin = stdin
+ self.stdout = stdout
+ self.stderr = stderr
- self.module = None
- self.rerr = ''
+ self.module = None
+ self.rerr = ''
+ self._want_abort = False
+ self.startTime = None
+
+ self.setDaemon(True)
+
def run(self):
"""Run command"""
+ self.startTime = time.time()
# TODO: wx.Exectute/wx.Process (?)
self.module = Popen(self.cmd,
stdin=subprocess.PIPE,
@@ -491,6 +521,12 @@
# wait for the process to end, sucking in stuff until it does end
while self.module.poll() is None:
time.sleep(.1)
+ if self._want_abort: # abort running process
+ if hasattr(self.stderr, "gmstc"):
+ self.module.kill()
+ # -> GMConsole
+ wx.PostEvent(self.stderr.gmstc.parent, ResultEvent(None))
+ return
if self.stdout:
line = self.__read_all(self.module.stdout)
self.stdout.write(line)
@@ -508,6 +544,17 @@
self.rerr = self.__parseString(line)
+ if hasattr(self.stderr, "gmstc"):
+ # -> GMConsole
+ if self._want_abort: # abort running process
+ wx.PostEvent(self.stderr.gmstc.parent, ResultEvent(None))
+ else:
+ wx.PostEvent(self.stderr.gmstc.parent, ResultEvent(self))
+
+ def abort(self):
+ """Abort running process, used by main thread to signal an abort"""
+ self._want_abort = True
+
def __parseString(self, string):
"""Parse line
Modified: grass/trunk/gui/wxpython/gui_modules/menuform.py
===================================================================
--- grass/trunk/gui/wxpython/gui_modules/menuform.py 2008-03-06 07:05:05 UTC (rev 30485)
+++ grass/trunk/gui/wxpython/gui_modules/menuform.py 2008-03-06 16:49:55 UTC (rev 30486)
@@ -88,6 +88,8 @@
import grassenv
import gselect
import gcmd
+import wxgui_utils
+from preferences import globalSettings as UserSettings
try:
import subprocess
except:
@@ -602,7 +604,8 @@
guisizer.Add (item=topsizer, proportion=0, flag=wx.EXPAND)
# notebooks
- self.notebookpanel = cmdPanel (parent=self.panel, task=self.task, standalone=self.standalone)
+ self.notebookpanel = cmdPanel (parent=self.panel, task=self.task, standalone=self.standalone,
+ mainFrame=self)
### add 'command output' tab also for dialog open from menu
# if self.standalone:
self.goutput = self.notebookpanel.goutput
@@ -644,31 +647,39 @@
btn_ok.Bind(wx.EVT_BUTTON, self.OnOK)
else: # We're standalone
# run
- btn_run = wx.Button(parent=self.panel, id=wx.ID_OK, label= _("&Run"))
- btn_run.SetToolTipString(_("Run the command"))
- btn_run.SetDefault()
+ self.btn_run = wx.Button(parent=self.panel, id=wx.ID_OK, label= _("&Run"))
+ self.btn_run.SetToolTipString(_("Run the command"))
+ self.btn_run.SetDefault()
+ # abort
+ btn_abort = wx.Button(parent=self.panel, id=wx.ID_STOP)
+ btn_abort.SetToolTipString(_("Abort the running command"))
# copy
- btn_clipboard = wx.Button(parent=self.panel, id=wx.ID_COPY, label=_("C&opy"))
+ btn_clipboard = wx.Button(parent=self.panel, id=wx.ID_COPY)
btn_clipboard.SetToolTipString(_("Copy the current command string to the clipboard"))
- btnsizer.Add(item=btn_run, proportion=0,
+ btnsizer.Add(item=btn_abort, proportion=0,
flag=wx.ALL | wx.ALIGN_CENTER,
border=10)
+ btnsizer.Add(item=self.btn_run, proportion=0,
+ flag=wx.ALL | wx.ALIGN_CENTER,
+ border=10)
+
btnsizer.Add(item=btn_clipboard, proportion=0,
flag=wx.ALL | wx.ALIGN_CENTER,
border=10)
- btn_run.Bind(wx.EVT_BUTTON, self.OnRun)
+ self.btn_run.Bind(wx.EVT_BUTTON, self.OnRun)
+ btn_abort.Bind(wx.EVT_BUTTON, self.OnAbort)
btn_clipboard.Bind(wx.EVT_BUTTON, self.OnCopy)
guisizer.Add(item=btnsizer, proportion=0, flag=wx.ALIGN_CENTER)
if self.get_dcmd is None:
- # close dialog on run?
+ # close dialog when command is terminated
self.closebox = wx.CheckBox(parent=self.panel,
- label=_('Close dialog on run'), style = wx.NO_BORDER)
- self.closebox.SetValue(False)
+ label=_('Close dialog on finish'), style = wx.NO_BORDER)
+ self.closebox.SetValue(UserSettings.Get(group='cmd', key='closeDlg', subkey='enabled'))
guisizer.Add(item=self.closebox, proportion=0,
flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM,
border=5)
@@ -680,12 +691,16 @@
#self.notebookpanel.SetSize( (constrained_size[0] + 25, constrained_size[1]) )
#self.notebookpanel.Layout()
- # for too long descriptions
+ #
+ # put module description
+ #
self.description = StaticWrapText (parent=self.panel, label=self.task.description)
topsizer.Add (item=self.description, proportion=1, border=5,
flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND)
+ #
# do layout
+ #
guisizer.SetSizeHints(self.panel)
# called automatically by SetSizer()
self.panel.SetAutoLayout(True)
@@ -726,6 +741,9 @@
def OnRun(self, event):
"""Run the command"""
+ if len(self.goutput.GetListOfCmdThreads()) > 0:
+ return
+
cmd = self.createCmd()
if cmd == [] or cmd == None:
@@ -741,17 +759,21 @@
print >> sys.stderr, "parent window is: %s" % (str(self.parent))
# Send any other command to the shell.
else:
- runCmd = gcmd.Command(cmd)
+ gcmd.Command(cmd)
- #if self.standalone:
+ # if self.standalone:
# change page if needed
if self.notebookpanel.notebook.GetSelection() != self.notebookpanel.outpageid:
self.notebookpanel.notebook.SetSelection(self.notebookpanel.outpageid)
- if self.get_dcmd is None:
- # close dialog?
- if self.closebox.IsChecked():
- self.Close()
+ self.btn_run.Enable(False)
+
+ def OnAbort(self, event):
+ """Abort running command"""
+ try:
+ self.goutput.GetListOfCmdThreads()[0].abort()
+ except KeyError:
+ pass
def OnCopy(self, event):
"""Copy the command"""
@@ -802,9 +824,10 @@
"""
A panel containing a notebook dividing in tabs the different guisections of the GRASS cmd.
"""
- def __init__( self, parent, task, standalone, *args, **kwargs ):
+ def __init__( self, parent, task, standalone, mainFrame, *args, **kwargs ):
wx.Panel.__init__( self, parent, *args, **kwargs )
+ self.parent = mainFrame
self.task = task
fontsize = 10
@@ -853,8 +876,7 @@
# are we running from command line?
### add 'command output' tab regardless standalone dialog
# if standalone:
- from gui_modules import wxgui_utils
- self.goutput = wxgui_utils.GMConsole(self)
+ self.goutput = wxgui_utils.GMConsole(parent=self, margin=False)
self.outpage = self.notebook.AddPage(self.goutput, text=_("Command output") )
self.outpageid = self.notebook.GetPageCount() - 1
@@ -895,7 +917,9 @@
chk.Bind(wx.EVT_CHECKBOX, self.OnSetValue)
if f['name'] in ('verbose', 'quiet'):
chk.Bind(wx.EVT_CHECKBOX, self.OnVerbosity)
-
+ elif f['name'] == 'overwrite':
+ chk.SetValue(UserSettings.Get(group='cmd', key='overwrite', subkey='enabled'))
+
#
# parameters
#
Modified: grass/trunk/gui/wxpython/gui_modules/preferences.py
===================================================================
--- grass/trunk/gui/wxpython/gui_modules/preferences.py 2008-03-06 07:05:05 UTC (rev 30485)
+++ grass/trunk/gui/wxpython/gui_modules/preferences.py 2008-03-06 16:49:55 UTC (rev 30486)
@@ -75,6 +75,14 @@
'leftDbClick' : { 'selection' : 0 },
},
#
+ # Command
+ #
+ 'cmd': {
+ 'overwrite' : { 'enabled' : False },
+ 'closeDlg' : { 'enabled' : False },
+ 'verbosity' : { 'verbose' : 'grassenv' },
+ },
+ #
# vdigit
#
'vdigit' : {
@@ -149,6 +157,9 @@
'silk']
self.internalSettings['advanced']['digitInterface']['choices'] = ['vedit',
'vdigit']
+ self.internalSettings['cmd']['verbosity']['choices'] = ['grassenv',
+ 'verbose',
+ 'quiet']
def GetMapsetPath(self):
"""Store mapset search path"""
@@ -323,7 +334,7 @@
"""User preferences dialog"""
def __init__(self, parent, title=_("User settings"),
settings=globalSettings,
- style=wx.DEFAULT_DIALOG_STYLE):
+ style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER):
self.parent = parent # GMFrame
self.title = title
wx.Dialog.__init__(self, parent=parent, id=wx.ID_ANY, title=title,
@@ -339,6 +350,7 @@
# create notebook pages
self.__CreateGeneralPage(notebook)
self.__CreateDisplayPage(notebook)
+ self.__CreateCmdPage(notebook)
self.__CreateAttributeManagerPage(notebook)
self.__CreateAdvancedPage(notebook)
@@ -405,7 +417,7 @@
pos=(row, 1))
sizer.Add(item=gridSizer, proportion=1, flag=wx.ALL | wx.EXPAND, border=5)
- border.Add(item=sizer, proportion=1, flag=wx.ALL | wx.EXPAND, border=3)
+ border.Add(item=sizer, proportion=0, flag=wx.ALL | wx.EXPAND, border=3)
panel.SetSizer(border)
@@ -474,6 +486,56 @@
return panel
+ def __CreateCmdPage(self, notebook):
+ """Create notebook page for commad dialog settings"""
+ panel = wx.Panel(parent=notebook, id=wx.ID_ANY)
+ notebook.AddPage(page=panel, text=_("Command"))
+
+ border = wx.BoxSizer(wx.VERTICAL)
+ box = wx.StaticBox (parent=panel, id=wx.ID_ANY, label=" %s " % _("Command dialog settings"))
+ sizer = wx.StaticBoxSizer(box, wx.VERTICAL)
+
+ gridSizer = wx.GridBagSizer (hgap=3, vgap=3)
+ gridSizer.AddGrowableCol(0)
+
+ #
+ # command dialog settings
+ #
+ row = 0
+ # overwrite
+ overwrite = wx.CheckBox(parent=panel, id=wx.ID_ANY,
+ label=_("Allow output files to overwrite existing files"),
+ name="IsChecked")
+ overwrite.SetValue(self.settings.Get(group='cmd', key='overwrite', subkey='enabled'))
+ self.winId['cmd:overwrite:enabled'] = overwrite.GetId()
+
+ gridSizer.Add(item=overwrite,
+ pos=(row, 0), span=(1, 2))
+ row += 1
+ # close
+ close = wx.CheckBox(parent=panel, id=wx.ID_ANY,
+ label=_("Close on finish"),
+ name="IsChecked")
+ close.SetValue(self.settings.Get(group='cmd', key='closeDlg', subkey='enabled'))
+ self.winId['cmd:closeDlg:enabled'] = close.GetId()
+
+ gridSizer.Add(item=close,
+ pos=(row, 0), span=(1, 2))
+ row += 1
+ # verbosity
+ verbosity = wx.Choice(parent=panel, id=wx.ID_ANY, size=(200, -1),
+ choices=self.settings.Get(group='general', key='verbosity', subkey='choices', internal=True),
+ name="GetSelection")
+ mapsetPath.SetSelection(self.settings.Get(group='general', key='mapsetPath', subkey='selection'))
+ self.winId['general:mapsetPath:selection'] = mapsetPath.GetId()
+
+ sizer.Add(item=gridSizer, proportion=1, flag=wx.ALL | wx.EXPAND, border=5)
+ border.Add(item=sizer, proportion=0, flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border=3)
+
+ panel.SetSizer(border)
+
+ return panel
+
def __CreateAttributeManagerPage(self, notebook):
"""Create notebook page concerning for 'Attribute Table Manager' settings"""
panel = wx.Panel(parent=notebook, id=wx.ID_ANY)
Modified: grass/trunk/gui/wxpython/gui_modules/wxgui_utils.py
===================================================================
--- grass/trunk/gui/wxpython/gui_modules/wxgui_utils.py 2008-03-06 07:05:05 UTC (rev 30485)
+++ grass/trunk/gui/wxpython/gui_modules/wxgui_utils.py 2008-03-06 16:49:55 UTC (rev 30486)
@@ -1022,15 +1022,15 @@
Create and manage output console for commands entered on the
GIS Manager command line.
"""
- def __init__(self, parent, id=wx.ID_ANY,
+ def __init__(self, parent, id=wx.ID_ANY, margin=False,
pos=wx.DefaultPosition, size=wx.DefaultSize,
- style=wx.TAB_TRAVERSAL|wx.FULL_REPAINT_ON_RESIZE):
+ style=wx.TAB_TRAVERSAL | wx.FULL_REPAINT_ON_RESIZE):
wx.Panel.__init__(self, parent, id, pos, size, style)
# initialize variables
self.Map = None
- self.parent = parent # GMFrame
- self.cmdThreads = [] # list of command threads (alive or dead)
+ self.parent = parent # GMFrame
+ self.cmdThreads = [] # list of running commands (alive or dead)
# progress bar
self.console_progressbar = wx.Gauge(parent=self, id=wx.ID_ANY,
@@ -1038,11 +1038,7 @@
style=wx.GA_HORIZONTAL)
# text control for command output
- ### self.cmd_output = wx.TextCtrl(parent=self, id=wx.ID_ANY, value="",
- ### style=wx.TE_MULTILINE| wx.TE_READONLY)
- ### self.cmd_output.SetFont(wx.Font(10, wx.FONTFAMILY_MODERN,
- ### wx.NORMAL, wx.NORMAL, 0, ''))
- self.cmd_output = GMStc(parent=self, id=wx.ID_ANY)
+ self.cmd_output = GMStc(parent=self, id=wx.ID_ANY, margin=margin)
# redirect
self.cmd_stdout = GMStdout(self.cmd_output)
self.cmd_stderr = GMStderr(self.cmd_output,
@@ -1054,7 +1050,7 @@
self.Bind(wx.EVT_BUTTON, self.ClearHistory, self.console_clear)
self.Bind(wx.EVT_BUTTON, self.SaveHistory, self.console_save)
- # output control layout
+ # output control layout
boxsizer1 = wx.BoxSizer(wx.VERTICAL)
gridsizer1 = wx.GridSizer(rows=1, cols=2, vgap=0, hgap=0)
boxsizer1.Add(item=self.cmd_output, proportion=1,
@@ -1074,12 +1070,32 @@
boxsizer1.Fit(self)
boxsizer1.SetSizeHints(self)
+ # set up event handler for any command thread results
+ gcmd.EVT_RESULT(self, self.OnResult)
+
+ # layout
self.SetAutoLayout(True)
self.SetSizer(boxsizer1)
+ def WriteCmdLog(self, line, pid=None):
+ """Write out line in selected style"""
+ self.cmd_output.GotoPos(self.cmd_output.GetEndStyled())
+ p1 = self.cmd_output.GetCurrentPos()
+ if pid:
+ line = '(' + str(pid) + ') ' + line
+ if len(line) < 80:
+ diff = 80 - len(line)
+ line += diff * ' '
+ line += '%s' % os.linesep
+ self.cmd_output.AddText(line)
+ self.cmd_output.EnsureCaretVisible()
+ p2 = self.cmd_output.GetCurrentPos()
+ self.cmd_output.StartStyling(p1, 0xff)
+ self.cmd_output.SetStyling(p2 - p1, self.cmd_output.StyleCommand)
+
def RunCmd(self, command):
"""
- Run in GUI or shell GRASS (or other) commands typed into
+ Run in GUI GRASS (or other) commands typed into
console command text widget, and send stdout output to output
text widget.
@@ -1098,8 +1114,8 @@
except:
curr_disp = None
+ # command given as a string ?
try:
- # if command is not already a list, make it one
cmdlist = command.strip().split(' ')
except:
cmdlist = command
@@ -1127,47 +1143,28 @@
wx.MessageBox(message=_("Command '%s' not yet implemented") % cmdlist[0])
return False
- # add layer
+ # add layer into layer tree
self.parent.curr_page.maptree.AddLayer(ltype=layertype,
lcmd=cmdlist)
- else: # other GRASS commands
+ else: # other GRASS commands (r|v|g|...)
if self.parent.notebook.GetSelection() != 1:
# select 'Command output' tab
self.parent.notebook.SetSelection(1)
- if len(self.GetListOfCmdThreads(onlyAlive=True)) > 0:
- busy = wx.BusyInfo(message=_("Please wait, there is another command "
- "currently running"),
- parent=self.parent)
- # wx.Yield()
- time.sleep(3)
- busy.Destroy()
- else:
# activate computational region (set with g.region)
# for all non-display commands.
tmpreg = os.getenv("GRASS_REGION")
os.unsetenv("GRASS_REGION")
if len(cmdlist) == 1:
- #process GRASS command without argument
+ # process GRASS command without argument
menuform.GUI().ParseCommand(cmdlist, parentframe=self)
else:
# process GRASS command with argument
- self.cmd_output.GotoPos(self.cmd_output.GetEndStyled())
- p1 = self.cmd_output.GetCurrentPos()
- line = '$ %s' % ' '.join(cmdlist)
- if len(line) < 80:
- diff = 80 - len(line)
- line += diff * ' '
- line += '%s' % os.linesep
- self.cmd_output.AddText(line)
- self.cmd_output.EnsureCaretVisible()
- p2 = self.cmd_output.GetCurrentPos()
- self.cmd_output.StartStyling(p1, 0xff)
- self.cmd_output.SetStyling(p2 - p1, self.cmd_output.StyleCommand)
-
- # TODO: allow running multiple instances
+ self.cmdPID = len(self.cmdThreads)+1
+ self.WriteCmdLog('%s' % ' '.join(cmdlist), pid=self.cmdPID)
+
grassCmd = gcmd.Command(cmdlist, wait=False,
stdout=self.cmd_stdout,
stderr=self.cmd_stderr)
@@ -1178,6 +1175,7 @@
if tmpreg:
os.environ["GRASS_REGION"] = tmpreg
+ return grassCmd
else:
# Send any other command to the shell. Send output to
# console output window
@@ -1191,13 +1189,12 @@
# if command is not a GRASS command, treat it like a shell command
generalCmd = subprocess.Popen(cmdlist,
stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- close_fds=True)
+ stderr=subprocess.PIPE)
for outline in generalCmd.stdout:
print outline
- return True
+ return None
def ClearHistory(self, event):
"""Clear history of commands"""
@@ -1231,7 +1228,7 @@
dlg.Destroy()
- def GetListOfCmdThreads(self, onlyAlive=False):
+ def GetListOfCmdThreads(self, onlyAlive=True):
"""Return list of command threads)"""
list = []
for t in self.cmdThreads:
@@ -1243,6 +1240,26 @@
return list
+ def OnResult(self, event):
+ """Show result status"""
+ if event.cmdThread is None:
+ # Thread aborted (using our convention of None return)
+ self.WriteCmdLog(_('Command aborted'),
+ pid=self.cmdPID)
+ else:
+ # Process results here
+ self.WriteCmdLog(_('Command finished (%d sec)') % (time.time() - event.cmdThread.startTime),
+ pid=self.cmdPID)
+
+ self.console_progressbar.SetValue(0) # reset progress bar on '0%'
+ if hasattr(self.parent.parent, "btn_run"): # menuform.mainFrame
+ dialog = self.parent.parent
+ dialog.btn_run.Enable(True)
+ if dialog.get_dcmd is None and \
+ dialog.closebox.IsChecked():
+ time.sleep(1)
+ dialog.Close()
+
class GMStdout:
"""GMConsole standard output
@@ -1297,7 +1314,7 @@
if value < 100:
self.gmgauge.SetValue(value)
else:
- self.gmgauge.SetValue(0) # reset progress bar on '100%'
+ self.gmgauge.SetValue(0) # reset progress bar on '0%'
elif 'GRASS_INFO_MESSAGE' in line:
type = 'message'
message += line.split(':')[1].strip()
@@ -1346,7 +1363,7 @@
Copyright: (c) 2005-2007 Jean-Michel Fauth
Licence: GPL
"""
- def __init__(self, parent, id):
+ def __init__(self, parent, id, margin=False):
wx.stc.StyledTextCtrl.__init__(self, parent, id)
self.parent = parent
@@ -1381,23 +1398,25 @@
self.StyleSetSpec(self.StyleWarning, self.StyleWarningSpec)
self.StyleSetSpec(self.StyleMessage, self.StyleMessageSpec)
self.StyleSetSpec(self.StyleUnknown, self.StyleUnknownSpec)
-
+
#
- # margin widths
+ # line margins
#
- self.SetMarginWidth(0, 0)
+ # TODO print number only from cmdlog
self.SetMarginWidth(1, 0)
self.SetMarginWidth(2, 0)
+ if margin:
+ self.SetMarginType(0, wx.stc.STC_MARGIN_NUMBER)
+ self.SetMarginWidth(0, 30)
+ else:
+ self.SetMarginWidth(0, 0)
#
# miscellaneous
#
- self.SetMarginLeft(2)
self.SetViewWhiteSpace(False)
self.SetTabWidth(4)
self.SetUseTabs(False)
- # self.SetEOLMode(wx.stc.STC_EOL_CRLF)
- # self.SetViewEOL(True)
self.UsePopUp(True)
self.SetSelBackground(True, "#FFFF00")
self.SetUseHorizontalScrollBar(True)
More information about the grass-commit
mailing list