| 1 | """!
|
|---|
| 2 | @package gui_core.goutput
|
|---|
| 3 |
|
|---|
| 4 | @brief Command output widgets
|
|---|
| 5 |
|
|---|
| 6 | Classes:
|
|---|
| 7 | - goutput::CmdThread
|
|---|
| 8 | - goutput::GMConsole
|
|---|
| 9 | - goutput::GMStdout
|
|---|
| 10 | - goutput::GMStderr
|
|---|
| 11 | - goutput::GMStc
|
|---|
| 12 | - goutput::PyStc
|
|---|
| 13 |
|
|---|
| 14 | (C) 2007-2012 by the GRASS Development Team
|
|---|
| 15 |
|
|---|
| 16 | This program is free software under the GNU General Public License
|
|---|
| 17 | (>=v2). Read the file COPYING that comes with GRASS for details.
|
|---|
| 18 |
|
|---|
| 19 | @author Michael Barton (Arizona State University)
|
|---|
| 20 | @author Martin Landa <landa.martin gmail.com>
|
|---|
| 21 | @author Vaclav Petras <wenzeslaus gmail.com> (copy&paste customization)
|
|---|
| 22 | """
|
|---|
| 23 |
|
|---|
| 24 | import os
|
|---|
| 25 | import sys
|
|---|
| 26 | import textwrap
|
|---|
| 27 | import time
|
|---|
| 28 | import threading
|
|---|
| 29 | import Queue
|
|---|
| 30 | import codecs
|
|---|
| 31 | import locale
|
|---|
| 32 | import keyword
|
|---|
| 33 |
|
|---|
| 34 | import wx
|
|---|
| 35 | from wx import stc
|
|---|
| 36 | from wx.lib.newevent import NewEvent
|
|---|
| 37 |
|
|---|
| 38 | import grass.script as grass
|
|---|
| 39 | from grass.script import task as gtask
|
|---|
| 40 |
|
|---|
| 41 | from core import globalvar
|
|---|
| 42 | from core import utils
|
|---|
| 43 | from core.gcmd import CommandThread, GMessage, GError, GException, EncodeString
|
|---|
| 44 | from gui_core.forms import GUI
|
|---|
| 45 | from gui_core.prompt import GPromptSTC
|
|---|
| 46 | from core.debug import Debug
|
|---|
| 47 | from core.settings import UserSettings
|
|---|
| 48 | from gui_core.ghelp import SearchModuleWindow
|
|---|
| 49 |
|
|---|
| 50 | wxCmdOutput, EVT_CMD_OUTPUT = NewEvent()
|
|---|
| 51 | wxCmdProgress, EVT_CMD_PROGRESS = NewEvent()
|
|---|
| 52 | wxCmdRun, EVT_CMD_RUN = NewEvent()
|
|---|
| 53 | wxCmdDone, EVT_CMD_DONE = NewEvent()
|
|---|
| 54 | wxCmdAbort, EVT_CMD_ABORT = NewEvent()
|
|---|
| 55 | wxCmdPrepare, EVT_CMD_PREPARE = NewEvent()
|
|---|
| 56 |
|
|---|
| 57 | def GrassCmd(cmd, env = None, stdout = None, stderr = None):
|
|---|
| 58 | """!Return GRASS command thread"""
|
|---|
| 59 | return CommandThread(cmd, env = env,
|
|---|
| 60 | stdout = stdout, stderr = stderr)
|
|---|
| 61 |
|
|---|
| 62 | class CmdThread(threading.Thread):
|
|---|
| 63 | """!Thread for GRASS commands"""
|
|---|
| 64 | requestId = 0
|
|---|
| 65 | def __init__(self, parent, requestQ, resultQ, **kwds):
|
|---|
| 66 | threading.Thread.__init__(self, **kwds)
|
|---|
| 67 |
|
|---|
| 68 | self.setDaemon(True)
|
|---|
| 69 |
|
|---|
| 70 | self.parent = parent # GMConsole
|
|---|
| 71 | self._want_abort_all = False
|
|---|
| 72 |
|
|---|
| 73 | self.requestQ = requestQ
|
|---|
| 74 | self.resultQ = resultQ
|
|---|
| 75 |
|
|---|
| 76 | self.start()
|
|---|
| 77 |
|
|---|
| 78 | def RunCmd(self, *args, **kwds):
|
|---|
| 79 | CmdThread.requestId += 1
|
|---|
| 80 |
|
|---|
| 81 | self.requestCmd = None
|
|---|
| 82 | self.requestQ.put((CmdThread.requestId, args, kwds))
|
|---|
| 83 |
|
|---|
| 84 | return CmdThread.requestId
|
|---|
| 85 |
|
|---|
| 86 | def SetId(self, id):
|
|---|
| 87 | """!Set starting id"""
|
|---|
| 88 | CmdThread.requestId = id
|
|---|
| 89 |
|
|---|
| 90 | def run(self):
|
|---|
| 91 | os.environ['GRASS_MESSAGE_FORMAT'] = 'gui'
|
|---|
| 92 | while True:
|
|---|
| 93 | requestId, args, kwds = self.requestQ.get()
|
|---|
| 94 | for key in ('callable', 'onDone', 'onPrepare', 'userData'):
|
|---|
| 95 | if key in kwds:
|
|---|
| 96 | vars()[key] = kwds[key]
|
|---|
| 97 | del kwds[key]
|
|---|
| 98 | else:
|
|---|
| 99 | vars()[key] = None
|
|---|
| 100 |
|
|---|
| 101 | if not vars()['callable']:
|
|---|
| 102 | vars()['callable'] = GrassCmd
|
|---|
| 103 |
|
|---|
| 104 | requestTime = time.time()
|
|---|
| 105 |
|
|---|
| 106 | # prepare
|
|---|
| 107 | event = wxCmdPrepare(cmd = args[0],
|
|---|
| 108 | time = requestTime,
|
|---|
| 109 | pid = requestId,
|
|---|
| 110 | onPrepare = vars()['onPrepare'],
|
|---|
| 111 | userData = vars()['userData'])
|
|---|
| 112 | wx.PostEvent(self.parent, event)
|
|---|
| 113 |
|
|---|
| 114 | # run command
|
|---|
| 115 | event = wxCmdRun(cmd = args[0],
|
|---|
| 116 | pid = requestId)
|
|---|
| 117 | wx.PostEvent(self.parent, event)
|
|---|
| 118 |
|
|---|
| 119 | time.sleep(.1)
|
|---|
| 120 | self.requestCmd = vars()['callable'](*args, **kwds)
|
|---|
| 121 | if self._want_abort_all:
|
|---|
| 122 | self.requestCmd.abort()
|
|---|
| 123 | if self.requestQ.empty():
|
|---|
| 124 | self._want_abort_all = False
|
|---|
| 125 |
|
|---|
| 126 | self.resultQ.put((requestId, self.requestCmd.run()))
|
|---|
| 127 |
|
|---|
| 128 | try:
|
|---|
| 129 | returncode = self.requestCmd.module.returncode
|
|---|
| 130 | except AttributeError:
|
|---|
| 131 | returncode = 0 # being optimistic
|
|---|
| 132 |
|
|---|
| 133 | try:
|
|---|
| 134 | aborted = self.requestCmd.aborted
|
|---|
| 135 | except AttributeError:
|
|---|
| 136 | aborted = False
|
|---|
| 137 |
|
|---|
| 138 | time.sleep(.1)
|
|---|
| 139 |
|
|---|
| 140 | # set default color table for raster data
|
|---|
| 141 | if UserSettings.Get(group = 'cmd', key = 'rasterColorTable', subkey = 'enabled') and \
|
|---|
| 142 | args[0][0][:2] == 'r.':
|
|---|
| 143 | colorTable = UserSettings.Get(group = 'cmd', key = 'rasterColorTable', subkey = 'selection')
|
|---|
| 144 | mapName = None
|
|---|
| 145 | if args[0][0] == 'r.mapcalc':
|
|---|
| 146 | try:
|
|---|
| 147 | mapName = args[0][1].split('=', 1)[0].strip()
|
|---|
| 148 | except KeyError:
|
|---|
| 149 | pass
|
|---|
| 150 | else:
|
|---|
| 151 | moduleInterface = GUI(show = None).ParseCommand(args[0])
|
|---|
| 152 | outputParam = moduleInterface.get_param(value = 'output', raiseError = False)
|
|---|
| 153 | if outputParam and outputParam['prompt'] == 'raster':
|
|---|
| 154 | mapName = outputParam['value']
|
|---|
| 155 |
|
|---|
| 156 | if mapName:
|
|---|
| 157 | argsColor = list(args)
|
|---|
| 158 | argsColor[0] = [ 'r.colors',
|
|---|
| 159 | 'map=%s' % mapName,
|
|---|
| 160 | 'color=%s' % colorTable ]
|
|---|
| 161 | self.requestCmdColor = vars()['callable'](*argsColor, **kwds)
|
|---|
| 162 | self.resultQ.put((requestId, self.requestCmdColor.run()))
|
|---|
| 163 |
|
|---|
| 164 | event = wxCmdDone(cmd = args[0],
|
|---|
| 165 | aborted = aborted,
|
|---|
| 166 | returncode = returncode,
|
|---|
| 167 | time = requestTime,
|
|---|
| 168 | pid = requestId,
|
|---|
| 169 | onDone = vars()['onDone'],
|
|---|
| 170 | userData = vars()['userData'])
|
|---|
| 171 |
|
|---|
| 172 | # send event
|
|---|
| 173 | wx.PostEvent(self.parent, event)
|
|---|
| 174 |
|
|---|
| 175 | def abort(self, abortall = True):
|
|---|
| 176 | """!Abort command(s)"""
|
|---|
| 177 | if abortall:
|
|---|
| 178 | self._want_abort_all = True
|
|---|
| 179 | self.requestCmd.abort()
|
|---|
| 180 | if self.requestQ.empty():
|
|---|
| 181 | self._want_abort_all = False
|
|---|
| 182 |
|
|---|
| 183 | class GMConsole(wx.SplitterWindow):
|
|---|
| 184 | """!Create and manage output console for commands run by GUI.
|
|---|
| 185 | """
|
|---|
| 186 | def __init__(self, parent, id = wx.ID_ANY, margin = False,
|
|---|
| 187 | notebook = None,
|
|---|
| 188 | style = wx.TAB_TRAVERSAL | wx.FULL_REPAINT_ON_RESIZE,
|
|---|
| 189 | **kwargs):
|
|---|
| 190 | wx.SplitterWindow.__init__(self, parent, id, style = style, *kwargs)
|
|---|
| 191 | self.SetName("GMConsole")
|
|---|
| 192 |
|
|---|
| 193 | self.panelOutput = wx.Panel(parent = self, id = wx.ID_ANY)
|
|---|
| 194 | self.panelPrompt = wx.Panel(parent = self, id = wx.ID_ANY)
|
|---|
| 195 |
|
|---|
| 196 | # initialize variables
|
|---|
| 197 | self.parent = parent # GMFrame | CmdPanel | ?
|
|---|
| 198 | if notebook:
|
|---|
| 199 | self._notebook = notebook
|
|---|
| 200 | else:
|
|---|
| 201 | self._notebook = self.parent.notebook
|
|---|
| 202 | self.lineWidth = 80
|
|---|
| 203 |
|
|---|
| 204 | # remember position of line begining (used for '\r')
|
|---|
| 205 | self.linePos = -1
|
|---|
| 206 |
|
|---|
| 207 | # create queues
|
|---|
| 208 | self.requestQ = Queue.Queue()
|
|---|
| 209 | self.resultQ = Queue.Queue()
|
|---|
| 210 |
|
|---|
| 211 | # progress bar
|
|---|
| 212 | self.progressbar = wx.Gauge(parent = self.panelOutput, id = wx.ID_ANY,
|
|---|
| 213 | range = 100, pos = (110, 50), size = (-1, 25),
|
|---|
| 214 | style = wx.GA_HORIZONTAL)
|
|---|
| 215 | self.progressbar.Bind(EVT_CMD_PROGRESS, self.OnCmdProgress)
|
|---|
| 216 |
|
|---|
| 217 | # text control for command output
|
|---|
| 218 | self.cmdOutput = GMStc(parent = self.panelOutput, id = wx.ID_ANY, margin = margin,
|
|---|
| 219 | wrap = None)
|
|---|
| 220 | self.cmdOutputTimer = wx.Timer(self.cmdOutput, id = wx.ID_ANY)
|
|---|
| 221 | self.cmdOutput.Bind(EVT_CMD_OUTPUT, self.OnCmdOutput)
|
|---|
| 222 | self.cmdOutput.Bind(wx.EVT_TIMER, self.OnProcessPendingOutputWindowEvents)
|
|---|
| 223 | self.Bind(EVT_CMD_RUN, self.OnCmdRun)
|
|---|
| 224 | self.Bind(EVT_CMD_DONE, self.OnCmdDone)
|
|---|
| 225 | self.Bind(EVT_CMD_PREPARE, self.OnCmdPrepare)
|
|---|
| 226 |
|
|---|
| 227 | # search & command prompt
|
|---|
| 228 | self.cmdPrompt = GPromptSTC(parent = self)
|
|---|
| 229 |
|
|---|
| 230 | if self.parent.GetName() != 'LayerManager':
|
|---|
| 231 | self.search = None
|
|---|
| 232 | self.cmdPrompt.Hide()
|
|---|
| 233 | else:
|
|---|
| 234 | self.infoCollapseLabelExp = _("Click here to show search module engine")
|
|---|
| 235 | self.infoCollapseLabelCol = _("Click here to hide search module engine")
|
|---|
| 236 | self.searchPane = wx.CollapsiblePane(parent = self.panelOutput,
|
|---|
| 237 | label = self.infoCollapseLabelExp,
|
|---|
| 238 | style = wx.CP_DEFAULT_STYLE |
|
|---|
| 239 | wx.CP_NO_TLW_RESIZE | wx.EXPAND)
|
|---|
| 240 | self.MakeSearchPaneContent(self.searchPane.GetPane())
|
|---|
| 241 | self.searchPane.Collapse(True)
|
|---|
| 242 | self.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self.OnSearchPaneChanged, self.searchPane)
|
|---|
| 243 | self.search.Bind(wx.EVT_TEXT, self.OnUpdateStatusBar)
|
|---|
| 244 |
|
|---|
| 245 | # stream redirection
|
|---|
| 246 | self.cmdStdOut = GMStdout(self)
|
|---|
| 247 | self.cmdStdErr = GMStderr(self)
|
|---|
| 248 |
|
|---|
| 249 | # thread
|
|---|
| 250 | self.cmdThread = CmdThread(self, self.requestQ, self.resultQ)
|
|---|
| 251 |
|
|---|
| 252 | self.outputBox = wx.StaticBox(parent = self.panelOutput, id = wx.ID_ANY,
|
|---|
| 253 | label = " %s " % _("Output window"))
|
|---|
| 254 | self.cmdBox = wx.StaticBox(parent = self.panelOutput, id = wx.ID_ANY,
|
|---|
| 255 | label = " %s " % _("Command prompt"))
|
|---|
| 256 |
|
|---|
| 257 | # buttons
|
|---|
| 258 | self.btnOutputClear = wx.Button(parent = self.panelOutput, id = wx.ID_CLEAR)
|
|---|
| 259 | self.btnOutputClear.SetToolTipString(_("Clear output window content"))
|
|---|
| 260 | self.btnCmdClear = wx.Button(parent = self.panelOutput, id = wx.ID_CLEAR)
|
|---|
| 261 | self.btnCmdClear.SetToolTipString(_("Clear command prompt content"))
|
|---|
| 262 | self.btnOutputSave = wx.Button(parent = self.panelOutput, id = wx.ID_SAVE)
|
|---|
| 263 | self.btnOutputSave.SetToolTipString(_("Save output window content to the file"))
|
|---|
| 264 | self.btnCmdAbort = wx.Button(parent = self.panelOutput, id = wx.ID_STOP)
|
|---|
| 265 | self.btnCmdAbort.SetToolTipString(_("Abort running command"))
|
|---|
| 266 | self.btnCmdAbort.Enable(False)
|
|---|
| 267 | self.btnCmdProtocol = wx.ToggleButton(parent = self.panelOutput, id = wx.ID_ANY,
|
|---|
| 268 | label = _("&Log file"),
|
|---|
| 269 | size = self.btnCmdClear.GetSize())
|
|---|
| 270 | self.btnCmdProtocol.SetToolTipString(_("Toggle to save list of executed commands into "
|
|---|
| 271 | "a file; content saved when switching off."))
|
|---|
| 272 |
|
|---|
| 273 | if self.parent.GetName() != 'LayerManager':
|
|---|
| 274 | self.btnCmdClear.Hide()
|
|---|
| 275 | self.btnCmdProtocol.Hide()
|
|---|
| 276 |
|
|---|
| 277 | self.btnCmdClear.Bind(wx.EVT_BUTTON, self.cmdPrompt.OnCmdErase)
|
|---|
| 278 | self.btnOutputClear.Bind(wx.EVT_BUTTON, self.OnOutputClear)
|
|---|
| 279 | self.btnOutputSave.Bind(wx.EVT_BUTTON, self.OnOutputSave)
|
|---|
| 280 | self.btnCmdAbort.Bind(wx.EVT_BUTTON, self.OnCmdAbort)
|
|---|
| 281 | self.Bind(EVT_CMD_ABORT, self.OnCmdAbort)
|
|---|
| 282 | self.btnCmdProtocol.Bind(wx.EVT_TOGGLEBUTTON, self.OnCmdProtocol)
|
|---|
| 283 |
|
|---|
| 284 | self._layout()
|
|---|
| 285 |
|
|---|
| 286 | def _layout(self):
|
|---|
| 287 | """!Do layout"""
|
|---|
| 288 | outputSizer = wx.BoxSizer(wx.VERTICAL)
|
|---|
| 289 | btnSizer = wx.BoxSizer(wx.HORIZONTAL)
|
|---|
| 290 | outBtnSizer = wx.StaticBoxSizer(self.outputBox, wx.HORIZONTAL)
|
|---|
| 291 | cmdBtnSizer = wx.StaticBoxSizer(self.cmdBox, wx.HORIZONTAL)
|
|---|
| 292 |
|
|---|
| 293 | if self.cmdPrompt.IsShown():
|
|---|
| 294 | promptSizer = wx.BoxSizer(wx.VERTICAL)
|
|---|
| 295 | promptSizer.Add(item = self.cmdPrompt, proportion = 1,
|
|---|
| 296 | flag = wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, border = 3)
|
|---|
| 297 |
|
|---|
| 298 | if self.search and self.search.IsShown():
|
|---|
| 299 | outputSizer.Add(item = self.searchPane, proportion = 0,
|
|---|
| 300 | flag = wx.EXPAND | wx.ALL, border = 3)
|
|---|
| 301 | outputSizer.Add(item = self.cmdOutput, proportion = 1,
|
|---|
| 302 | flag = wx.EXPAND | wx.ALL, border = 3)
|
|---|
| 303 | outputSizer.Add(item = self.progressbar, proportion = 0,
|
|---|
| 304 | flag = wx.EXPAND | wx.LEFT | wx.RIGHT, border = 3)
|
|---|
| 305 | outBtnSizer.Add(item = self.btnOutputClear, proportion = 1,
|
|---|
| 306 | flag = wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT, border = 5)
|
|---|
| 307 | outBtnSizer.Add(item = self.btnOutputSave, proportion = 1,
|
|---|
| 308 | flag = wx.ALIGN_RIGHT | wx.RIGHT, border = 5)
|
|---|
| 309 |
|
|---|
| 310 | cmdBtnSizer.Add(item = self.btnCmdProtocol, proportion = 1,
|
|---|
| 311 | flag = wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT, border = 5)
|
|---|
| 312 | cmdBtnSizer.Add(item = self.btnCmdClear, proportion = 1,
|
|---|
| 313 | flag = wx.ALIGN_CENTER | wx.RIGHT, border = 5)
|
|---|
| 314 | cmdBtnSizer.Add(item = self.btnCmdAbort, proportion = 1,
|
|---|
| 315 | flag = wx.ALIGN_CENTER | wx.RIGHT, border = 5)
|
|---|
| 316 |
|
|---|
| 317 | if self.parent.GetName() != 'LayerManager':
|
|---|
| 318 | proportion = (1, 1)
|
|---|
| 319 | else:
|
|---|
| 320 | proportion = (2, 3)
|
|---|
| 321 |
|
|---|
| 322 | btnSizer.Add(item = outBtnSizer, proportion = proportion[0],
|
|---|
| 323 | flag = wx.ALL | wx.ALIGN_CENTER, border = 5)
|
|---|
| 324 | btnSizer.Add(item = cmdBtnSizer, proportion = proportion[1],
|
|---|
| 325 | flag = wx.ALIGN_CENTER | wx.TOP | wx.BOTTOM | wx.RIGHT, border = 5)
|
|---|
| 326 | outputSizer.Add(item = btnSizer, proportion = 0,
|
|---|
| 327 | flag = wx.EXPAND)
|
|---|
| 328 |
|
|---|
| 329 | outputSizer.Fit(self)
|
|---|
| 330 | outputSizer.SetSizeHints(self)
|
|---|
| 331 | self.panelOutput.SetSizer(outputSizer)
|
|---|
| 332 | # eliminate gtk_widget_size_allocate() warnings
|
|---|
| 333 | outputSizer.SetVirtualSizeHints(self.panelOutput)
|
|---|
| 334 |
|
|---|
| 335 | if self.cmdPrompt.IsShown():
|
|---|
| 336 | promptSizer.Fit(self)
|
|---|
| 337 | promptSizer.SetSizeHints(self)
|
|---|
| 338 | self.panelPrompt.SetSizer(promptSizer)
|
|---|
| 339 |
|
|---|
| 340 | # split window
|
|---|
| 341 | if self.cmdPrompt.IsShown():
|
|---|
| 342 | self.SplitHorizontally(self.panelOutput, self.panelPrompt, -50)
|
|---|
| 343 | else:
|
|---|
| 344 | self.SplitHorizontally(self.panelOutput, self.panelPrompt, -45)
|
|---|
| 345 | self.Unsplit()
|
|---|
| 346 | self.SetMinimumPaneSize(self.btnCmdClear.GetSize()[1] + 25)
|
|---|
| 347 |
|
|---|
| 348 | self.SetSashGravity(1.0)
|
|---|
| 349 |
|
|---|
| 350 | # layout
|
|---|
| 351 | self.SetAutoLayout(True)
|
|---|
| 352 | self.Layout()
|
|---|
| 353 |
|
|---|
| 354 | def MakeSearchPaneContent(self, pane):
|
|---|
| 355 | """!Create search pane"""
|
|---|
| 356 | border = wx.BoxSizer(wx.VERTICAL)
|
|---|
| 357 |
|
|---|
| 358 | self.search = SearchModuleWindow(parent = pane, cmdPrompt = self.cmdPrompt)
|
|---|
| 359 |
|
|---|
| 360 | border.Add(item = self.search, proportion = 0,
|
|---|
| 361 | flag = wx.EXPAND | wx.ALL, border = 1)
|
|---|
| 362 |
|
|---|
| 363 | pane.SetSizer(border)
|
|---|
| 364 | border.Fit(pane)
|
|---|
| 365 |
|
|---|
| 366 | def OnSearchPaneChanged(self, event):
|
|---|
| 367 | """!Collapse search module box"""
|
|---|
| 368 | if self.searchPane.IsExpanded():
|
|---|
| 369 | self.searchPane.SetLabel(self.infoCollapseLabelCol)
|
|---|
| 370 | else:
|
|---|
| 371 | self.searchPane.SetLabel(self.infoCollapseLabelExp)
|
|---|
| 372 |
|
|---|
| 373 | self.panelOutput.Layout()
|
|---|
| 374 | self.panelOutput.SendSizeEvent()
|
|---|
| 375 |
|
|---|
| 376 | def GetPanel(self, prompt = True):
|
|---|
| 377 | """!Get panel
|
|---|
| 378 |
|
|---|
| 379 | @param prompt get prompt / output panel
|
|---|
| 380 |
|
|---|
| 381 | @return wx.Panel reference
|
|---|
| 382 | """
|
|---|
| 383 | if prompt:
|
|---|
| 384 | return self.panelPrompt
|
|---|
| 385 |
|
|---|
| 386 | return self.panelOutput
|
|---|
| 387 |
|
|---|
| 388 | def Redirect(self):
|
|---|
| 389 | """!Redirect stdout/stderr
|
|---|
| 390 | """
|
|---|
| 391 | if Debug.GetLevel() == 0 and int(grass.gisenv().get('DEBUG', 0)) == 0:
|
|---|
| 392 | # don't redirect when debugging is enabled
|
|---|
| 393 | sys.stdout = self.cmdStdOut
|
|---|
| 394 | sys.stderr = self.cmdStdErr
|
|---|
| 395 | else:
|
|---|
| 396 | enc = locale.getdefaultlocale()[1]
|
|---|
| 397 | if enc:
|
|---|
| 398 | sys.stdout = codecs.getwriter(enc)(sys.__stdout__)
|
|---|
| 399 | sys.stderr = codecs.getwriter(enc)(sys.__stderr__)
|
|---|
| 400 | else:
|
|---|
| 401 | sys.stdout = sys.__stdout__
|
|---|
| 402 | sys.stderr = sys.__stderr__
|
|---|
| 403 |
|
|---|
| 404 | def WriteLog(self, text, style = None, wrap = None,
|
|---|
| 405 | switchPage = False):
|
|---|
| 406 | """!Generic method for writing log message in
|
|---|
| 407 | given style
|
|---|
| 408 |
|
|---|
| 409 | @param line text line
|
|---|
| 410 | @param style text style (see GMStc)
|
|---|
| 411 | @param stdout write to stdout or stderr
|
|---|
| 412 | """
|
|---|
| 413 |
|
|---|
| 414 | self.cmdOutput.SetStyle()
|
|---|
| 415 |
|
|---|
| 416 | if switchPage:
|
|---|
| 417 | self._notebook.SetSelectionByName('output')
|
|---|
| 418 |
|
|---|
| 419 | if not style:
|
|---|
| 420 | style = self.cmdOutput.StyleDefault
|
|---|
| 421 |
|
|---|
| 422 | # p1 = self.cmdOutput.GetCurrentPos()
|
|---|
| 423 | p1 = self.cmdOutput.GetEndStyled()
|
|---|
| 424 | # self.cmdOutput.GotoPos(p1)
|
|---|
| 425 | self.cmdOutput.DocumentEnd()
|
|---|
| 426 |
|
|---|
| 427 | for line in text.splitlines():
|
|---|
| 428 | # fill space
|
|---|
| 429 | if len(line) < self.lineWidth:
|
|---|
| 430 | diff = self.lineWidth - len(line)
|
|---|
| 431 | line += diff * ' '
|
|---|
| 432 |
|
|---|
| 433 | self.cmdOutput.AddTextWrapped(line, wrap = wrap) # adds '\n'
|
|---|
| 434 |
|
|---|
| 435 | p2 = self.cmdOutput.GetCurrentPos()
|
|---|
| 436 |
|
|---|
| 437 | self.cmdOutput.StartStyling(p1, 0xff)
|
|---|
| 438 | self.cmdOutput.SetStyling(p2 - p1, style)
|
|---|
| 439 |
|
|---|
| 440 | self.cmdOutput.EnsureCaretVisible()
|
|---|
| 441 |
|
|---|
| 442 | def WriteCmdLog(self, line, pid = None, switchPage = True):
|
|---|
| 443 | """!Write message in selected style"""
|
|---|
| 444 | if pid:
|
|---|
| 445 | line = '(' + str(pid) + ') ' + line
|
|---|
| 446 |
|
|---|
| 447 | self.WriteLog(line, style = self.cmdOutput.StyleCommand, switchPage = switchPage)
|
|---|
| 448 |
|
|---|
| 449 | def WriteWarning(self, line):
|
|---|
| 450 | """!Write message in warning style"""
|
|---|
| 451 | self.WriteLog(line, style = self.cmdOutput.StyleWarning, switchPage = True)
|
|---|
| 452 |
|
|---|
| 453 | def WriteError(self, line):
|
|---|
| 454 | """!Write message in error style"""
|
|---|
| 455 | self.WriteLog(line, style = self.cmdOutput.StyleError, switchPage = True)
|
|---|
| 456 |
|
|---|
| 457 | def RunCmd(self, command, compReg = True, switchPage = False, skipInterface = False,
|
|---|
| 458 | onDone = None, onPrepare = None, userData = None):
|
|---|
| 459 | """!Run command typed into console command prompt (GPrompt).
|
|---|
| 460 |
|
|---|
| 461 | @todo Display commands (*.d) are captured and processed
|
|---|
| 462 | separately by mapdisp.py. Display commands are rendered in map
|
|---|
| 463 | display widget that currently has the focus (as indicted by
|
|---|
| 464 | mdidx).
|
|---|
| 465 |
|
|---|
| 466 | @param command command given as a list (produced e.g. by utils.split())
|
|---|
| 467 | @param compReg True use computation region
|
|---|
| 468 | @param switchPage switch to output page
|
|---|
| 469 | @param skipInterface True to do not launch GRASS interface
|
|---|
| 470 | parser when command has no arguments given
|
|---|
| 471 | @param onDone function to be called when command is finished
|
|---|
| 472 | @param onPrepare function to be called before command is launched
|
|---|
| 473 | @param userData data defined for the command
|
|---|
| 474 | """
|
|---|
| 475 | if len(command) == 0:
|
|---|
| 476 | Debug.msg(2, "GPrompt:RunCmd(): empty command")
|
|---|
| 477 | return
|
|---|
| 478 |
|
|---|
| 479 | # update history file
|
|---|
| 480 | env = grass.gisenv()
|
|---|
| 481 | try:
|
|---|
| 482 | filePath = os.path.join(env['GISDBASE'],
|
|---|
| 483 | env['LOCATION_NAME'],
|
|---|
| 484 | env['MAPSET'],
|
|---|
| 485 | '.bash_history')
|
|---|
| 486 | fileHistory = codecs.open(filePath, encoding = 'utf-8', mode = 'a')
|
|---|
| 487 | except IOError, e:
|
|---|
| 488 | GError(_("Unable to write file '%(filePath)s'.\n\nDetails: %(error)s") %
|
|---|
| 489 | {'filePath': filePath, 'error' : e },
|
|---|
| 490 | parent = self.parent)
|
|---|
| 491 | fileHistory = None
|
|---|
| 492 |
|
|---|
| 493 | if fileHistory:
|
|---|
| 494 | try:
|
|---|
| 495 | fileHistory.write(' '.join(command) + os.linesep)
|
|---|
| 496 | finally:
|
|---|
| 497 | fileHistory.close()
|
|---|
| 498 |
|
|---|
| 499 | if command[0] in globalvar.grassCmd:
|
|---|
| 500 | # send GRASS command without arguments to GUI command interface
|
|---|
| 501 | # except display commands (they are handled differently)
|
|---|
| 502 | if self.parent.GetName() == "LayerManager" and \
|
|---|
| 503 | command[0][0:2] == "d." and \
|
|---|
| 504 | 'help' not in ' '.join(command[1:]):
|
|---|
| 505 | # display GRASS commands
|
|---|
| 506 | try:
|
|---|
| 507 | layertype = {'d.rast' : 'raster',
|
|---|
| 508 | 'd.rast3d' : '3d-raster',
|
|---|
| 509 | 'd.rgb' : 'rgb',
|
|---|
| 510 | 'd.his' : 'his',
|
|---|
| 511 | 'd.shadedmap' : 'shaded',
|
|---|
| 512 | 'd.legend' : 'rastleg',
|
|---|
| 513 | 'd.rast.arrow' : 'rastarrow',
|
|---|
| 514 | 'd.rast.num' : 'rastnum',
|
|---|
| 515 | 'd.vect' : 'vector',
|
|---|
| 516 | 'd.vect.thematic': 'thememap',
|
|---|
| 517 | 'd.vect.chart' : 'themechart',
|
|---|
| 518 | 'd.grid' : 'grid',
|
|---|
| 519 | 'd.geodesic' : 'geodesic',
|
|---|
| 520 | 'd.rhumbline' : 'rhumb',
|
|---|
| 521 | 'd.labels' : 'labels',
|
|---|
| 522 | 'd.barscale' : 'barscale',
|
|---|
| 523 | 'd.redraw' : 'redraw'}[command[0]]
|
|---|
| 524 | except KeyError:
|
|---|
| 525 | GMessage(parent = self.parent,
|
|---|
| 526 | message = _("Command '%s' not yet implemented in the WxGUI. "
|
|---|
| 527 | "Try adding it as a command layer instead.") % command[0])
|
|---|
| 528 | return
|
|---|
| 529 |
|
|---|
| 530 | mapdisp = self.parent.GetMapDisplay()
|
|---|
| 531 | if layertype == 'barscale':
|
|---|
| 532 | mapdisp.OnAddBarscale(None)
|
|---|
| 533 | elif layertype == 'rastleg':
|
|---|
| 534 | mapdisp.OnAddLegend(None)
|
|---|
| 535 | elif layertype == 'redraw':
|
|---|
| 536 | mapdisp.OnRender(None)
|
|---|
| 537 | else:
|
|---|
| 538 | # add layer into layer tree
|
|---|
| 539 | lname, found = utils.GetLayerNameFromCmd(command, fullyQualified = True,
|
|---|
| 540 | layerType = layertype)
|
|---|
| 541 | if self.parent.GetName() == "LayerManager":
|
|---|
| 542 | self.parent.GetLayerTree().AddLayer(ltype = layertype,
|
|---|
| 543 | lname = lname,
|
|---|
| 544 | lcmd = command)
|
|---|
| 545 |
|
|---|
| 546 | else:
|
|---|
| 547 | # other GRASS commands (r|v|g|...)
|
|---|
| 548 | hasParams = False
|
|---|
| 549 | if command[0] not in ('r.mapcalc', 'r3.mapcalc'):
|
|---|
| 550 | try:
|
|---|
| 551 | task = GUI(show = None).ParseCommand(command)
|
|---|
| 552 | except GException, e:
|
|---|
| 553 | GError(parent = self,
|
|---|
| 554 | message = unicode(e),
|
|---|
| 555 | showTraceback = False)
|
|---|
| 556 | return
|
|---|
| 557 |
|
|---|
| 558 | if task:
|
|---|
| 559 | options = task.get_options()
|
|---|
| 560 | hasParams = options['params'] and options['flags']
|
|---|
| 561 | # check for <input>=-
|
|---|
| 562 | for p in options['params']:
|
|---|
| 563 | if p.get('prompt', '') == 'input' and \
|
|---|
| 564 | p.get('element', '') == 'file' and \
|
|---|
| 565 | p.get('age', 'new') == 'old' and \
|
|---|
| 566 | p.get('value', '') == '-':
|
|---|
| 567 | GError(parent = self,
|
|---|
| 568 | message = _("Unable to run command:\n%(cmd)s\n\n"
|
|---|
| 569 | "Option <%(opt)s>: read from standard input is not "
|
|---|
| 570 | "supported by wxGUI") % { 'cmd': ' '.join(command),
|
|---|
| 571 | 'opt': p.get('name', '') })
|
|---|
| 572 | return
|
|---|
| 573 | else:
|
|---|
| 574 | task = None
|
|---|
| 575 |
|
|---|
| 576 | if len(command) == 1 and hasParams and \
|
|---|
| 577 | command[0] != 'v.krige.py':
|
|---|
| 578 | # no arguments given
|
|---|
| 579 | try:
|
|---|
| 580 | GUI(parent = self).ParseCommand(command)
|
|---|
| 581 | except GException, e:
|
|---|
| 582 | print >> sys.stderr, e
|
|---|
| 583 | return
|
|---|
| 584 |
|
|---|
| 585 | # switch to 'Command output' if required
|
|---|
| 586 | if switchPage:
|
|---|
| 587 | self._notebook.SetSelectionByName('output')
|
|---|
| 588 |
|
|---|
| 589 | self.parent.SetFocus()
|
|---|
| 590 | self.parent.Raise()
|
|---|
| 591 |
|
|---|
| 592 | # activate computational region (set with g.region)
|
|---|
| 593 | # for all non-display commands.
|
|---|
| 594 | if compReg:
|
|---|
| 595 | tmpreg = os.getenv("GRASS_REGION")
|
|---|
| 596 | if "GRASS_REGION" in os.environ:
|
|---|
| 597 | del os.environ["GRASS_REGION"]
|
|---|
| 598 |
|
|---|
| 599 | # process GRASS command with argument
|
|---|
| 600 | self.cmdThread.RunCmd(command, stdout = self.cmdStdOut, stderr = self.cmdStdErr,
|
|---|
| 601 | onDone = onDone, onPrepare = onPrepare, userData = userData,
|
|---|
| 602 | env = os.environ.copy())
|
|---|
| 603 | self.cmdOutputTimer.Start(50)
|
|---|
| 604 |
|
|---|
| 605 | # deactivate computational region and return to display settings
|
|---|
| 606 | if compReg and tmpreg:
|
|---|
| 607 | os.environ["GRASS_REGION"] = tmpreg
|
|---|
| 608 | else:
|
|---|
| 609 | # Send any other command to the shell. Send output to
|
|---|
| 610 | # console output window
|
|---|
| 611 | if len(command) == 1 and not skipInterface:
|
|---|
| 612 | try:
|
|---|
| 613 | task = gtask.parse_interface(command[0])
|
|---|
| 614 | except:
|
|---|
| 615 | task = None
|
|---|
| 616 | else:
|
|---|
| 617 | task = None
|
|---|
| 618 |
|
|---|
| 619 | if task:
|
|---|
| 620 | # process GRASS command without argument
|
|---|
| 621 | GUI(parent = self).ParseCommand(command)
|
|---|
| 622 | else:
|
|---|
| 623 | self.cmdThread.RunCmd(command, stdout = self.cmdStdOut, stderr = self.cmdStdErr,
|
|---|
| 624 | onDone = onDone, onPrepare = onPrepare, userData = userData)
|
|---|
| 625 | self.cmdOutputTimer.Start(50)
|
|---|
| 626 |
|
|---|
| 627 | def OnOutputClear(self, event):
|
|---|
| 628 | """!Clear content of output window"""
|
|---|
| 629 | self.cmdOutput.SetReadOnly(False)
|
|---|
| 630 | self.cmdOutput.ClearAll()
|
|---|
| 631 | self.cmdOutput.SetReadOnly(True)
|
|---|
| 632 | self.progressbar.SetValue(0)
|
|---|
| 633 |
|
|---|
| 634 | def GetProgressBar(self):
|
|---|
| 635 | """!Return progress bar widget"""
|
|---|
| 636 | return self.progressbar
|
|---|
| 637 |
|
|---|
| 638 | def GetLog(self, err = False):
|
|---|
| 639 | """!Get widget used for logging
|
|---|
| 640 |
|
|---|
| 641 | @param err True to get stderr widget
|
|---|
| 642 | """
|
|---|
| 643 | if err:
|
|---|
| 644 | return self.cmdStdErr
|
|---|
| 645 |
|
|---|
| 646 | return self.cmdStdOut
|
|---|
| 647 |
|
|---|
| 648 | def OnOutputSave(self, event):
|
|---|
| 649 | """!Save (selected) text from output window to the file"""
|
|---|
| 650 | text = self.cmdOutput.GetSelectedText()
|
|---|
| 651 | if not text:
|
|---|
| 652 | text = self.cmdOutput.GetText()
|
|---|
| 653 |
|
|---|
| 654 | # add newline if needed
|
|---|
| 655 | if len(text) > 0 and text[-1] != '\n':
|
|---|
| 656 | text += '\n'
|
|---|
| 657 |
|
|---|
| 658 | dlg = wx.FileDialog(self, message = _("Save file as..."),
|
|---|
| 659 | defaultFile = "grass_cmd_output.txt",
|
|---|
| 660 | wildcard = _("%(txt)s (*.txt)|*.txt|%(files)s (*)|*") %
|
|---|
| 661 | {'txt': _("Text files"), 'files': _("Files")},
|
|---|
| 662 | style = wx.SAVE | wx.FD_OVERWRITE_PROMPT)
|
|---|
| 663 |
|
|---|
| 664 | # Show the dialog and retrieve the user response. If it is the OK response,
|
|---|
| 665 | # process the data.
|
|---|
| 666 | if dlg.ShowModal() == wx.ID_OK:
|
|---|
| 667 | path = dlg.GetPath()
|
|---|
| 668 |
|
|---|
| 669 | try:
|
|---|
| 670 | output = open(path, "w")
|
|---|
| 671 | output.write(text)
|
|---|
| 672 | except IOError, e:
|
|---|
| 673 | GError(_("Unable to write file '%(path)s'.\n\nDetails: %(error)s") % {'path': path, 'error': e})
|
|---|
| 674 | finally:
|
|---|
| 675 | output.close()
|
|---|
| 676 | message = _("Command output saved into '%s'") % path
|
|---|
| 677 | if hasattr(self.parent, 'SetStatusText'):
|
|---|
| 678 | self.parent.SetStatusText(message)
|
|---|
| 679 | else:
|
|---|
| 680 | self.parent.parent.SetStatusText(message)
|
|---|
| 681 |
|
|---|
| 682 | dlg.Destroy()
|
|---|
| 683 |
|
|---|
| 684 | def GetCmd(self):
|
|---|
| 685 | """!Get running command or None"""
|
|---|
| 686 | return self.requestQ.get()
|
|---|
| 687 |
|
|---|
| 688 | def SetCopyingOfSelectedText(self, copy):
|
|---|
| 689 | """!Enable or disable copying of selected text in to clipboard.
|
|---|
| 690 | Effects prompt and output.
|
|---|
| 691 |
|
|---|
| 692 | @param copy True for enable, False for disable
|
|---|
| 693 | """
|
|---|
| 694 | if copy:
|
|---|
| 695 | self.cmdPrompt.Bind(stc.EVT_STC_PAINTED, self.cmdPrompt.OnTextSelectionChanged)
|
|---|
| 696 | self.cmdOutput.Bind(stc.EVT_STC_PAINTED, self.cmdOutput.OnTextSelectionChanged)
|
|---|
| 697 | else:
|
|---|
| 698 | self.cmdPrompt.Unbind(stc.EVT_STC_PAINTED)
|
|---|
| 699 | self.cmdOutput.Unbind(stc.EVT_STC_PAINTED)
|
|---|
| 700 |
|
|---|
| 701 | def OnUpdateStatusBar(self, event):
|
|---|
| 702 | """!Update statusbar text"""
|
|---|
| 703 | if event.GetString():
|
|---|
| 704 | nItems = len(self.cmdPrompt.GetCommandItems())
|
|---|
| 705 | self.parent.SetStatusText(_('%d modules match') % nItems, 0)
|
|---|
| 706 | else:
|
|---|
| 707 | self.parent.SetStatusText('', 0)
|
|---|
| 708 |
|
|---|
| 709 | event.Skip()
|
|---|
| 710 |
|
|---|
| 711 | def OnCmdOutput(self, event):
|
|---|
| 712 | """!Print command output"""
|
|---|
| 713 | message = event.text
|
|---|
| 714 | type = event.type
|
|---|
| 715 | if self._notebook.GetSelection() != self._notebook.GetPageIndexByName('output'):
|
|---|
| 716 | page = self._notebook.GetPageIndexByName('output')
|
|---|
| 717 | textP = self._notebook.GetPageText(page)
|
|---|
| 718 | if textP[-1] != ')':
|
|---|
| 719 | textP += ' (...)'
|
|---|
| 720 | self._notebook.SetPageText(page, textP)
|
|---|
| 721 |
|
|---|
| 722 | # message prefix
|
|---|
| 723 | if type == 'warning':
|
|---|
| 724 | message = 'WARNING: ' + message
|
|---|
| 725 | elif type == 'error':
|
|---|
| 726 | message = 'ERROR: ' + message
|
|---|
| 727 |
|
|---|
| 728 | p1 = self.cmdOutput.GetEndStyled()
|
|---|
| 729 | self.cmdOutput.GotoPos(p1)
|
|---|
| 730 |
|
|---|
| 731 | if '\b' in message:
|
|---|
| 732 | if self.linePos < 0:
|
|---|
| 733 | self.linePos = p1
|
|---|
| 734 | last_c = ''
|
|---|
| 735 | for c in message:
|
|---|
| 736 | if c == '\b':
|
|---|
| 737 | self.linePos -= 1
|
|---|
| 738 | else:
|
|---|
| 739 | if c == '\r':
|
|---|
| 740 | pos = self.cmdOutput.GetCurLine()[1]
|
|---|
| 741 | # self.cmdOutput.SetCurrentPos(pos)
|
|---|
| 742 | else:
|
|---|
| 743 | self.cmdOutput.SetCurrentPos(self.linePos)
|
|---|
| 744 | self.cmdOutput.ReplaceSelection(c)
|
|---|
| 745 | self.linePos = self.cmdOutput.GetCurrentPos()
|
|---|
| 746 | if c != ' ':
|
|---|
| 747 | last_c = c
|
|---|
| 748 | if last_c not in ('0123456789'):
|
|---|
| 749 | self.cmdOutput.AddTextWrapped('\n', wrap = None)
|
|---|
| 750 | self.linePos = -1
|
|---|
| 751 | else:
|
|---|
| 752 | self.linePos = -1 # don't force position
|
|---|
| 753 | if '\n' not in message:
|
|---|
| 754 | self.cmdOutput.AddTextWrapped(message, wrap = 60)
|
|---|
| 755 | else:
|
|---|
| 756 | self.cmdOutput.AddTextWrapped(message, wrap = None)
|
|---|
| 757 |
|
|---|
| 758 | p2 = self.cmdOutput.GetCurrentPos()
|
|---|
| 759 |
|
|---|
| 760 | if p2 >= p1:
|
|---|
| 761 | self.cmdOutput.StartStyling(p1, 0xff)
|
|---|
| 762 |
|
|---|
| 763 | if type == 'error':
|
|---|
| 764 | self.cmdOutput.SetStyling(p2 - p1, self.cmdOutput.StyleError)
|
|---|
| 765 | elif type == 'warning':
|
|---|
| 766 | self.cmdOutput.SetStyling(p2 - p1, self.cmdOutput.StyleWarning)
|
|---|
| 767 | elif type == 'message':
|
|---|
| 768 | self.cmdOutput.SetStyling(p2 - p1, self.cmdOutput.StyleMessage)
|
|---|
| 769 | else: # unknown
|
|---|
| 770 | self.cmdOutput.SetStyling(p2 - p1, self.cmdOutput.StyleUnknown)
|
|---|
| 771 |
|
|---|
| 772 | self.cmdOutput.EnsureCaretVisible()
|
|---|
| 773 |
|
|---|
| 774 | def OnCmdProgress(self, event):
|
|---|
| 775 | """!Update progress message info"""
|
|---|
| 776 | self.progressbar.SetValue(event.value)
|
|---|
| 777 |
|
|---|
| 778 | def CmdProtocolSave(self):
|
|---|
| 779 | """Save list of manually entered commands into a text log file"""
|
|---|
| 780 | if not hasattr(self, 'cmdFileProtocol'):
|
|---|
| 781 | return # it should not happen
|
|---|
| 782 |
|
|---|
| 783 | try:
|
|---|
| 784 | output = open(self.cmdFileProtocol, "a")
|
|---|
| 785 | cmds = self.cmdPrompt.GetCommands()
|
|---|
| 786 | output.write('\n'.join(cmds))
|
|---|
| 787 | if len(cmds) > 0:
|
|---|
| 788 | output.write('\n')
|
|---|
| 789 | except IOError, e:
|
|---|
| 790 | GError(_("Unable to write file '%(filePath)s'.\n\nDetails: %(error)s") %
|
|---|
| 791 | {'filePath': self.cmdFileProtocol, 'error': e})
|
|---|
| 792 | finally:
|
|---|
| 793 | output.close()
|
|---|
| 794 |
|
|---|
| 795 | self.parent.SetStatusText(_("Command log saved to '%s'") % self.cmdFileProtocol)
|
|---|
| 796 | del self.cmdFileProtocol
|
|---|
| 797 |
|
|---|
| 798 | def OnCmdProtocol(self, event = None):
|
|---|
| 799 | """!Save commands into file"""
|
|---|
| 800 | if not event.IsChecked():
|
|---|
| 801 | # stop capturing commands, save list of commands to the
|
|---|
| 802 | # protocol file
|
|---|
| 803 | self.CmdProtocolSave()
|
|---|
| 804 | else:
|
|---|
| 805 | # start capturing commands
|
|---|
| 806 | self.cmdPrompt.ClearCommands()
|
|---|
| 807 | # ask for the file
|
|---|
| 808 | dlg = wx.FileDialog(self, message = _("Save file as..."),
|
|---|
| 809 | defaultFile = "grass_cmd_log.txt",
|
|---|
| 810 | wildcard = _("%(txt)s (*.txt)|*.txt|%(files)s (*)|*") %
|
|---|
| 811 | {'txt': _("Text files"), 'files': _("Files")},
|
|---|
| 812 | style = wx.SAVE)
|
|---|
| 813 | if dlg.ShowModal() == wx.ID_OK:
|
|---|
| 814 | self.cmdFileProtocol = dlg.GetPath()
|
|---|
| 815 | else:
|
|---|
| 816 | wx.CallAfter(self.btnCmdProtocol.SetValue, False)
|
|---|
| 817 |
|
|---|
| 818 | dlg.Destroy()
|
|---|
| 819 |
|
|---|
| 820 | event.Skip()
|
|---|
| 821 |
|
|---|
| 822 | def OnCmdAbort(self, event):
|
|---|
| 823 | """!Abort running command"""
|
|---|
| 824 | self.cmdThread.abort()
|
|---|
| 825 |
|
|---|
| 826 | def OnCmdRun(self, event):
|
|---|
| 827 | """!Run command"""
|
|---|
| 828 | if self.parent.GetName() == 'Modeler':
|
|---|
| 829 | self.parent.OnCmdRun(event)
|
|---|
| 830 |
|
|---|
| 831 | self.WriteCmdLog('(%s)\n%s' % (str(time.ctime()), ' '.join(event.cmd)))
|
|---|
| 832 | self.btnCmdAbort.Enable()
|
|---|
| 833 |
|
|---|
| 834 | def OnCmdPrepare(self, event):
|
|---|
| 835 | """!Prepare for running command"""
|
|---|
| 836 | if self.parent.GetName() == 'Modeler':
|
|---|
| 837 | self.parent.OnCmdPrepare(event)
|
|---|
| 838 |
|
|---|
| 839 | event.Skip()
|
|---|
| 840 |
|
|---|
| 841 | def OnCmdDone(self, event):
|
|---|
| 842 | """!Command done (or aborted)"""
|
|---|
| 843 | if self.parent.GetName() == 'Modeler':
|
|---|
| 844 | self.parent.OnCmdDone(event)
|
|---|
| 845 |
|
|---|
| 846 | # Process results here
|
|---|
| 847 | try:
|
|---|
| 848 | ctime = time.time() - event.time
|
|---|
| 849 | if ctime < 60:
|
|---|
| 850 | stime = _("%d sec") % int(ctime)
|
|---|
| 851 | else:
|
|---|
| 852 | mtime = int(ctime / 60)
|
|---|
| 853 | stime = _("%(min)d min %(sec)d sec") % { 'min' : mtime,
|
|---|
| 854 | 'sec' : int(ctime - (mtime * 60)) }
|
|---|
| 855 | except KeyError:
|
|---|
| 856 | # stopped deamon
|
|---|
| 857 | stime = _("unknown")
|
|---|
| 858 |
|
|---|
| 859 | if event.aborted:
|
|---|
| 860 | # Thread aborted (using our convention of None return)
|
|---|
| 861 | self.WriteLog(_('Please note that the data are left in inconsistent state '
|
|---|
| 862 | 'and may be corrupted'), self.cmdOutput.StyleWarning)
|
|---|
| 863 | msg = _('Command aborted')
|
|---|
| 864 | else:
|
|---|
| 865 | msg = _('Command finished')
|
|---|
| 866 |
|
|---|
| 867 | self.WriteCmdLog('(%s) %s (%s)' % (str(time.ctime()), msg, stime))
|
|---|
| 868 | self.btnCmdAbort.Enable(False)
|
|---|
| 869 |
|
|---|
| 870 | if event.onDone:
|
|---|
| 871 | event.onDone(cmd = event.cmd, returncode = event.returncode)
|
|---|
| 872 |
|
|---|
| 873 | self.progressbar.SetValue(0) # reset progress bar on '0%'
|
|---|
| 874 |
|
|---|
| 875 | self.cmdOutputTimer.Stop()
|
|---|
| 876 |
|
|---|
| 877 | if event.cmd[0] == 'g.gisenv':
|
|---|
| 878 | Debug.SetLevel()
|
|---|
| 879 | self.Redirect()
|
|---|
| 880 |
|
|---|
| 881 | if self.parent.GetName() == "LayerManager":
|
|---|
| 882 | self.btnCmdAbort.Enable(False)
|
|---|
| 883 | if event.cmd[0] not in globalvar.grassCmd or \
|
|---|
| 884 | event.cmd[0] in ('r.mapcalc', 'r3.mapcalc'):
|
|---|
| 885 | return
|
|---|
| 886 |
|
|---|
| 887 | tree = self.parent.GetLayerTree()
|
|---|
| 888 | display = None
|
|---|
| 889 | if tree:
|
|---|
| 890 | display = tree.GetMapDisplay()
|
|---|
| 891 | if not display or not display.IsAutoRendered():
|
|---|
| 892 | return
|
|---|
| 893 | mapLayers = map(lambda x: x.GetName(),
|
|---|
| 894 | display.GetMap().GetListOfLayers(l_type = 'raster') +
|
|---|
| 895 | display.GetMap().GetListOfLayers(l_type = 'vector'))
|
|---|
| 896 |
|
|---|
| 897 | try:
|
|---|
| 898 | task = GUI(show = None).ParseCommand(event.cmd)
|
|---|
| 899 | except GException, e:
|
|---|
| 900 | print >> sys.stderr, e
|
|---|
| 901 | task = None
|
|---|
| 902 | return
|
|---|
| 903 |
|
|---|
| 904 | for p in task.get_options()['params']:
|
|---|
| 905 | if p.get('prompt', '') not in ('raster', 'vector'):
|
|---|
| 906 | continue
|
|---|
| 907 | mapName = p.get('value', '')
|
|---|
| 908 | if '@' not in mapName:
|
|---|
| 909 | mapName = mapName + '@' + grass.gisenv()['MAPSET']
|
|---|
| 910 | if mapName in mapLayers:
|
|---|
| 911 | display.GetWindow().UpdateMap(render = True)
|
|---|
| 912 | return
|
|---|
| 913 | elif self.parent.GetName() == 'Modeler':
|
|---|
| 914 | pass
|
|---|
| 915 | else: # standalone dialogs
|
|---|
| 916 | dialog = self.parent.parent
|
|---|
| 917 | if hasattr(self.parent.parent, "btn_abort"):
|
|---|
| 918 | dialog.btn_abort.Enable(False)
|
|---|
| 919 |
|
|---|
| 920 | if hasattr(self.parent.parent, "btn_cancel"):
|
|---|
| 921 | dialog.btn_cancel.Enable(True)
|
|---|
| 922 |
|
|---|
| 923 | if hasattr(self.parent.parent, "btn_clipboard"):
|
|---|
| 924 | dialog.btn_clipboard.Enable(True)
|
|---|
| 925 |
|
|---|
| 926 | if hasattr(self.parent.parent, "btn_help"):
|
|---|
| 927 | dialog.btn_help.Enable(True)
|
|---|
| 928 |
|
|---|
| 929 | if hasattr(self.parent.parent, "btn_run"):
|
|---|
| 930 | dialog.btn_run.Enable(True)
|
|---|
| 931 |
|
|---|
| 932 | if event.returncode == 0 and not event.aborted:
|
|---|
| 933 | try:
|
|---|
| 934 | winName = self.parent.parent.parent.GetName()
|
|---|
| 935 | except AttributeError:
|
|---|
| 936 | winName = ''
|
|---|
| 937 |
|
|---|
| 938 | if winName == 'LayerManager':
|
|---|
| 939 | mapTree = self.parent.parent.parent.GetLayerTree()
|
|---|
| 940 | elif winName == 'LayerTree':
|
|---|
| 941 | mapTree = self.parent.parent.parent
|
|---|
| 942 | elif winName: # GMConsole
|
|---|
| 943 | mapTree = self.parent.parent.parent.parent.GetLayerTree()
|
|---|
| 944 | else:
|
|---|
| 945 | mapTree = None
|
|---|
| 946 |
|
|---|
| 947 | cmd = dialog.notebookpanel.createCmd(ignoreErrors = True)
|
|---|
| 948 | if mapTree and hasattr(dialog, "addbox") and dialog.addbox.IsChecked():
|
|---|
| 949 | # add created maps into layer tree
|
|---|
| 950 | for p in dialog.task.get_options()['params']:
|
|---|
| 951 | prompt = p.get('prompt', '')
|
|---|
| 952 | if prompt in ('raster', 'vector', '3d-raster') and \
|
|---|
| 953 | p.get('age', 'old') == 'new' and \
|
|---|
| 954 | p.get('value', None):
|
|---|
| 955 | name, found = utils.GetLayerNameFromCmd(cmd, fullyQualified = True,
|
|---|
| 956 | param = p.get('name', ''))
|
|---|
| 957 |
|
|---|
| 958 | if mapTree.GetMap().GetListOfLayers(l_name = name):
|
|---|
| 959 | display = mapTree.GetMapDisplay()
|
|---|
| 960 | if display and display.IsAutoRendered():
|
|---|
| 961 | display.GetWindow().UpdateMap(render = True)
|
|---|
| 962 | continue
|
|---|
| 963 |
|
|---|
| 964 | if prompt == 'raster':
|
|---|
| 965 | lcmd = ['d.rast',
|
|---|
| 966 | 'map=%s' % name]
|
|---|
| 967 | elif prompt == '3d-raster':
|
|---|
| 968 | lcmd = ['d.rast3d',
|
|---|
| 969 | 'map=%s' % name]
|
|---|
| 970 | else:
|
|---|
| 971 | lcmd = ['d.vect',
|
|---|
| 972 | 'map=%s' % name]
|
|---|
| 973 | mapTree.AddLayer(ltype = prompt,
|
|---|
| 974 | lcmd = lcmd,
|
|---|
| 975 | lname = name)
|
|---|
| 976 |
|
|---|
| 977 | if hasattr(dialog, "get_dcmd") and \
|
|---|
| 978 | dialog.get_dcmd is None and \
|
|---|
| 979 | hasattr(dialog, "closebox") and \
|
|---|
| 980 | dialog.closebox.IsChecked() and \
|
|---|
| 981 | (event.returncode == 0 or event.aborted):
|
|---|
| 982 | self.cmdOutput.Update()
|
|---|
| 983 | time.sleep(2)
|
|---|
| 984 | dialog.Close()
|
|---|
| 985 |
|
|---|
| 986 | def OnProcessPendingOutputWindowEvents(self, event):
|
|---|
| 987 | wx.GetApp().ProcessPendingEvents()
|
|---|
| 988 |
|
|---|
| 989 | def ResetFocus(self):
|
|---|
| 990 | """!Reset focus"""
|
|---|
| 991 | self.cmdPrompt.SetFocus()
|
|---|
| 992 |
|
|---|
| 993 | def GetPrompt(self):
|
|---|
| 994 | """!Get prompt"""
|
|---|
| 995 | return self.cmdPrompt
|
|---|
| 996 |
|
|---|
| 997 | class GMStdout:
|
|---|
| 998 | """!GMConsole standard output
|
|---|
| 999 |
|
|---|
| 1000 | Based on FrameOutErr.py
|
|---|
| 1001 |
|
|---|
| 1002 | Name: FrameOutErr.py
|
|---|
| 1003 | Purpose: Redirecting stdout / stderr
|
|---|
| 1004 | Author: Jean-Michel Fauth, Switzerland
|
|---|
| 1005 | Copyright: (c) 2005-2007 Jean-Michel Fauth
|
|---|
| 1006 | Licence: GPL
|
|---|
| 1007 | """
|
|---|
| 1008 | def __init__(self, parent):
|
|---|
| 1009 | self.parent = parent # GMConsole
|
|---|
| 1010 |
|
|---|
| 1011 | def write(self, s):
|
|---|
| 1012 | if len(s) == 0 or s == '\n':
|
|---|
| 1013 | return
|
|---|
| 1014 |
|
|---|
| 1015 | for line in s.splitlines():
|
|---|
| 1016 | if len(line) == 0:
|
|---|
| 1017 | continue
|
|---|
| 1018 |
|
|---|
| 1019 | evt = wxCmdOutput(text = line + '\n',
|
|---|
| 1020 | type = '')
|
|---|
| 1021 | wx.PostEvent(self.parent.cmdOutput, evt)
|
|---|
| 1022 |
|
|---|
| 1023 | class GMStderr:
|
|---|
| 1024 | """!GMConsole standard error output
|
|---|
| 1025 |
|
|---|
| 1026 | Based on FrameOutErr.py
|
|---|
| 1027 |
|
|---|
| 1028 | Name: FrameOutErr.py
|
|---|
| 1029 | Purpose: Redirecting stdout / stderr
|
|---|
| 1030 | Author: Jean-Michel Fauth, Switzerland
|
|---|
| 1031 | Copyright: (c) 2005-2007 Jean-Michel Fauth
|
|---|
| 1032 | Licence: GPL
|
|---|
| 1033 | """
|
|---|
| 1034 | def __init__(self, parent):
|
|---|
| 1035 | self.parent = parent # GMConsole
|
|---|
| 1036 |
|
|---|
| 1037 | self.type = ''
|
|---|
| 1038 | self.message = ''
|
|---|
| 1039 | self.printMessage = False
|
|---|
| 1040 |
|
|---|
| 1041 | def flush(self):
|
|---|
| 1042 | pass
|
|---|
| 1043 |
|
|---|
| 1044 | def write(self, s):
|
|---|
| 1045 | if "GtkPizza" in s:
|
|---|
| 1046 | return
|
|---|
| 1047 |
|
|---|
| 1048 | # remove/replace escape sequences '\b' or '\r' from stream
|
|---|
| 1049 | progressValue = -1
|
|---|
| 1050 |
|
|---|
| 1051 | for line in s.splitlines():
|
|---|
| 1052 | if len(line) == 0:
|
|---|
| 1053 | continue
|
|---|
| 1054 |
|
|---|
| 1055 | if 'GRASS_INFO_PERCENT' in line:
|
|---|
| 1056 | value = int(line.rsplit(':', 1)[1].strip())
|
|---|
| 1057 | if value >= 0 and value < 100:
|
|---|
| 1058 | progressValue = value
|
|---|
| 1059 | else:
|
|---|
| 1060 | progressValue = 0
|
|---|
| 1061 | elif 'GRASS_INFO_MESSAGE' in line:
|
|---|
| 1062 | self.type = 'message'
|
|---|
| 1063 | self.message += line.split(':', 1)[1].strip() + '\n'
|
|---|
| 1064 | elif 'GRASS_INFO_WARNING' in line:
|
|---|
| 1065 | self.type = 'warning'
|
|---|
| 1066 | self.message += line.split(':', 1)[1].strip() + '\n'
|
|---|
| 1067 | elif 'GRASS_INFO_ERROR' in line:
|
|---|
| 1068 | self.type = 'error'
|
|---|
| 1069 | self.message += line.split(':', 1)[1].strip() + '\n'
|
|---|
| 1070 | elif 'GRASS_INFO_END' in line:
|
|---|
| 1071 | self.printMessage = True
|
|---|
| 1072 | elif self.type == '':
|
|---|
| 1073 | if len(line) == 0:
|
|---|
| 1074 | continue
|
|---|
| 1075 | evt = wxCmdOutput(text = line,
|
|---|
| 1076 | type = '')
|
|---|
| 1077 | wx.PostEvent(self.parent.cmdOutput, evt)
|
|---|
| 1078 | elif len(line) > 0:
|
|---|
| 1079 | self.message += line.strip() + '\n'
|
|---|
| 1080 |
|
|---|
| 1081 | if self.printMessage and len(self.message) > 0:
|
|---|
| 1082 | evt = wxCmdOutput(text = self.message,
|
|---|
| 1083 | type = self.type)
|
|---|
| 1084 | wx.PostEvent(self.parent.cmdOutput, evt)
|
|---|
| 1085 |
|
|---|
| 1086 | self.type = ''
|
|---|
| 1087 | self.message = ''
|
|---|
| 1088 | self.printMessage = False
|
|---|
| 1089 |
|
|---|
| 1090 | # update progress message
|
|---|
| 1091 | if progressValue > -1:
|
|---|
| 1092 | # self.gmgauge.SetValue(progressValue)
|
|---|
| 1093 | evt = wxCmdProgress(value = progressValue)
|
|---|
| 1094 | wx.PostEvent(self.parent.progressbar, evt)
|
|---|
| 1095 |
|
|---|
| 1096 | class GMStc(stc.StyledTextCtrl):
|
|---|
| 1097 | """!Styled GMConsole
|
|---|
| 1098 |
|
|---|
| 1099 | Based on FrameOutErr.py
|
|---|
| 1100 |
|
|---|
| 1101 | Name: FrameOutErr.py
|
|---|
| 1102 | Purpose: Redirecting stdout / stderr
|
|---|
| 1103 | Author: Jean-Michel Fauth, Switzerland
|
|---|
| 1104 | Copyright: (c) 2005-2007 Jean-Michel Fauth
|
|---|
| 1105 | Licence: GPL
|
|---|
| 1106 | """
|
|---|
| 1107 | def __init__(self, parent, id, margin = False, wrap = None):
|
|---|
| 1108 | stc.StyledTextCtrl.__init__(self, parent, id)
|
|---|
| 1109 | self.parent = parent
|
|---|
| 1110 | self.SetUndoCollection(True)
|
|---|
| 1111 | self.SetReadOnly(True)
|
|---|
| 1112 |
|
|---|
| 1113 | #
|
|---|
| 1114 | # styles
|
|---|
| 1115 | #
|
|---|
| 1116 | self.SetStyle()
|
|---|
| 1117 |
|
|---|
| 1118 | #
|
|---|
| 1119 | # line margins
|
|---|
| 1120 | #
|
|---|
| 1121 | # TODO print number only from cmdlog
|
|---|
| 1122 | self.SetMarginWidth(1, 0)
|
|---|
| 1123 | self.SetMarginWidth(2, 0)
|
|---|
| 1124 | if margin:
|
|---|
| 1125 | self.SetMarginType(0, stc.STC_MARGIN_NUMBER)
|
|---|
| 1126 | self.SetMarginWidth(0, 30)
|
|---|
| 1127 | else:
|
|---|
| 1128 | self.SetMarginWidth(0, 0)
|
|---|
| 1129 |
|
|---|
| 1130 | #
|
|---|
| 1131 | # miscellaneous
|
|---|
| 1132 | #
|
|---|
| 1133 | self.SetViewWhiteSpace(False)
|
|---|
| 1134 | self.SetTabWidth(4)
|
|---|
| 1135 | self.SetUseTabs(False)
|
|---|
| 1136 | self.UsePopUp(True)
|
|---|
| 1137 | self.SetSelBackground(True, "#FFFF00")
|
|---|
| 1138 | self.SetUseHorizontalScrollBar(True)
|
|---|
| 1139 |
|
|---|
| 1140 | #
|
|---|
| 1141 | # bindings
|
|---|
| 1142 | #
|
|---|
| 1143 | self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy)
|
|---|
| 1144 |
|
|---|
| 1145 | def OnTextSelectionChanged(self, event):
|
|---|
| 1146 | """!Copy selected text to clipboard and skip event.
|
|---|
| 1147 | The same function is in TextCtrlAutoComplete class (prompt.py).
|
|---|
| 1148 | """
|
|---|
| 1149 | self.Copy()
|
|---|
| 1150 | event.Skip()
|
|---|
| 1151 |
|
|---|
| 1152 | def SetStyle(self):
|
|---|
| 1153 | """!Set styles for styled text output windows with type face
|
|---|
| 1154 | and point size selected by user (Courier New 10 is default)"""
|
|---|
| 1155 |
|
|---|
| 1156 | typeface = UserSettings.Get(group = 'appearance', key = 'outputfont', subkey = 'type')
|
|---|
| 1157 | if typeface == "":
|
|---|
| 1158 | typeface = "Courier New"
|
|---|
| 1159 |
|
|---|
| 1160 | typesize = UserSettings.Get(group = 'appearance', key = 'outputfont', subkey = 'size')
|
|---|
| 1161 | if typesize == None or typesize <= 0:
|
|---|
| 1162 | typesize = 10
|
|---|
| 1163 | typesize = float(typesize)
|
|---|
| 1164 |
|
|---|
| 1165 | self.StyleDefault = 0
|
|---|
| 1166 | self.StyleDefaultSpec = "face:%s,size:%d,fore:#000000,back:#FFFFFF" % (typeface, typesize)
|
|---|
| 1167 | self.StyleCommand = 1
|
|---|
| 1168 | self.StyleCommandSpec = "face:%s,size:%d,,fore:#000000,back:#bcbcbc" % (typeface, typesize)
|
|---|
| 1169 | self.StyleOutput = 2
|
|---|
| 1170 | self.StyleOutputSpec = "face:%s,size:%d,,fore:#000000,back:#FFFFFF" % (typeface, typesize)
|
|---|
| 1171 | # fatal error
|
|---|
| 1172 | self.StyleError = 3
|
|---|
| 1173 | self.StyleErrorSpec = "face:%s,size:%d,,fore:#7F0000,back:#FFFFFF" % (typeface, typesize)
|
|---|
| 1174 | # warning
|
|---|
| 1175 | self.StyleWarning = 4
|
|---|
| 1176 | self.StyleWarningSpec = "face:%s,size:%d,,fore:#0000FF,back:#FFFFFF" % (typeface, typesize)
|
|---|
| 1177 | # message
|
|---|
| 1178 | self.StyleMessage = 5
|
|---|
| 1179 | self.StyleMessageSpec = "face:%s,size:%d,,fore:#000000,back:#FFFFFF" % (typeface, typesize)
|
|---|
| 1180 | # unknown
|
|---|
| 1181 | self.StyleUnknown = 6
|
|---|
| 1182 | self.StyleUnknownSpec = "face:%s,size:%d,,fore:#000000,back:#FFFFFF" % (typeface, typesize)
|
|---|
| 1183 |
|
|---|
| 1184 | # default and clear => init
|
|---|
| 1185 | self.StyleSetSpec(stc.STC_STYLE_DEFAULT, self.StyleDefaultSpec)
|
|---|
| 1186 | self.StyleClearAll()
|
|---|
| 1187 | self.StyleSetSpec(self.StyleCommand, self.StyleCommandSpec)
|
|---|
| 1188 | self.StyleSetSpec(self.StyleOutput, self.StyleOutputSpec)
|
|---|
| 1189 | self.StyleSetSpec(self.StyleError, self.StyleErrorSpec)
|
|---|
| 1190 | self.StyleSetSpec(self.StyleWarning, self.StyleWarningSpec)
|
|---|
| 1191 | self.StyleSetSpec(self.StyleMessage, self.StyleMessageSpec)
|
|---|
| 1192 | self.StyleSetSpec(self.StyleUnknown, self.StyleUnknownSpec)
|
|---|
| 1193 |
|
|---|
| 1194 | def OnDestroy(self, evt):
|
|---|
| 1195 | """!The clipboard contents can be preserved after
|
|---|
| 1196 | the app has exited"""
|
|---|
| 1197 |
|
|---|
| 1198 | wx.TheClipboard.Flush()
|
|---|
| 1199 | evt.Skip()
|
|---|
| 1200 |
|
|---|
| 1201 | def AddTextWrapped(self, txt, wrap = None):
|
|---|
| 1202 | """!Add string to text area.
|
|---|
| 1203 |
|
|---|
| 1204 | String is wrapped and linesep is also added to the end
|
|---|
| 1205 | of the string"""
|
|---|
| 1206 | # allow writing to output window
|
|---|
| 1207 | self.SetReadOnly(False)
|
|---|
| 1208 |
|
|---|
| 1209 | if wrap:
|
|---|
| 1210 | txt = textwrap.fill(txt, wrap) + '\n'
|
|---|
| 1211 | else:
|
|---|
| 1212 | if txt[-1] != '\n':
|
|---|
| 1213 | txt += '\n'
|
|---|
| 1214 |
|
|---|
| 1215 | if '\r' in txt:
|
|---|
| 1216 | self.parent.linePos = -1
|
|---|
| 1217 | for seg in txt.split('\r'):
|
|---|
| 1218 | if self.parent.linePos > -1:
|
|---|
| 1219 | self.SetCurrentPos(self.parent.linePos)
|
|---|
| 1220 | self.ReplaceSelection(seg)
|
|---|
| 1221 | else:
|
|---|
| 1222 | self.parent.linePos = self.GetCurrentPos()
|
|---|
| 1223 | self.AddText(seg)
|
|---|
| 1224 | else:
|
|---|
| 1225 | self.parent.linePos = self.GetCurrentPos()
|
|---|
| 1226 | try:
|
|---|
| 1227 | self.AddText(txt)
|
|---|
| 1228 | except UnicodeDecodeError:
|
|---|
| 1229 | enc = UserSettings.Get(group = 'atm', key = 'encoding', subkey = 'value')
|
|---|
| 1230 | if enc:
|
|---|
| 1231 | txt = unicode(txt, enc)
|
|---|
| 1232 | elif 'GRASS_DB_ENCODING' in os.environ:
|
|---|
| 1233 | txt = unicode(txt, os.environ['GRASS_DB_ENCODING'])
|
|---|
| 1234 | else:
|
|---|
| 1235 | txt = EncodeString(txt)
|
|---|
| 1236 |
|
|---|
| 1237 | self.AddText(txt)
|
|---|
| 1238 |
|
|---|
| 1239 | # reset output window to read only
|
|---|
| 1240 | self.SetReadOnly(True)
|
|---|
| 1241 |
|
|---|
| 1242 | class PyStc(stc.StyledTextCtrl):
|
|---|
| 1243 | """!Styled Python output (see gmodeler::frame::PythonPanel for
|
|---|
| 1244 | usage)
|
|---|
| 1245 |
|
|---|
| 1246 | Based on StyledTextCtrl_2 from wxPython demo
|
|---|
| 1247 | """
|
|---|
| 1248 | def __init__(self, parent, id = wx.ID_ANY, statusbar = None):
|
|---|
| 1249 | stc.StyledTextCtrl.__init__(self, parent, id)
|
|---|
| 1250 |
|
|---|
| 1251 | self.parent = parent
|
|---|
| 1252 | self.statusbar = statusbar
|
|---|
| 1253 |
|
|---|
| 1254 | self.modified = False # content modified ?
|
|---|
| 1255 |
|
|---|
| 1256 | self.faces = { 'times': 'Times New Roman',
|
|---|
| 1257 | 'mono' : 'Courier New',
|
|---|
| 1258 | 'helv' : 'Arial',
|
|---|
| 1259 | 'other': 'Comic Sans MS',
|
|---|
| 1260 | 'size' : 10,
|
|---|
| 1261 | 'size2': 8,
|
|---|
| 1262 | }
|
|---|
| 1263 |
|
|---|
| 1264 | self.CmdKeyAssign(ord('B'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
|
|---|
| 1265 | self.CmdKeyAssign(ord('N'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
|
|---|
| 1266 |
|
|---|
| 1267 | self.SetLexer(stc.STC_LEX_PYTHON)
|
|---|
| 1268 | self.SetKeyWords(0, " ".join(keyword.kwlist))
|
|---|
| 1269 |
|
|---|
| 1270 | self.SetProperty("fold", "1")
|
|---|
| 1271 | self.SetProperty("tab.timmy.whinge.level", "1")
|
|---|
| 1272 | self.SetMargins(0, 0)
|
|---|
| 1273 | self.SetTabWidth(4)
|
|---|
| 1274 | self.SetUseTabs(False)
|
|---|
| 1275 |
|
|---|
| 1276 | self.SetEdgeMode(stc.STC_EDGE_BACKGROUND)
|
|---|
| 1277 | self.SetEdgeColumn(78)
|
|---|
| 1278 |
|
|---|
| 1279 | # setup a margin to hold fold markers
|
|---|
| 1280 | self.SetMarginType(2, stc.STC_MARGIN_SYMBOL)
|
|---|
| 1281 | self.SetMarginMask(2, stc.STC_MASK_FOLDERS)
|
|---|
| 1282 | self.SetMarginSensitive(2, True)
|
|---|
| 1283 | self.SetMarginWidth(2, 12)
|
|---|
| 1284 |
|
|---|
| 1285 | # like a flattened tree control using square headers
|
|---|
| 1286 | self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_BOXMINUS, "white", "#808080")
|
|---|
| 1287 | self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_BOXPLUS, "white", "#808080")
|
|---|
| 1288 | self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_VLINE, "white", "#808080")
|
|---|
| 1289 | self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_LCORNER, "white", "#808080")
|
|---|
| 1290 | self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_BOXPLUSCONNECTED, "white", "#808080")
|
|---|
| 1291 | self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_BOXMINUSCONNECTED, "white", "#808080")
|
|---|
| 1292 | self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_TCORNER, "white", "#808080")
|
|---|
| 1293 |
|
|---|
| 1294 | self.Bind(stc.EVT_STC_UPDATEUI, self.OnUpdateUI)
|
|---|
| 1295 | self.Bind(stc.EVT_STC_MARGINCLICK, self.OnMarginClick)
|
|---|
| 1296 | self.Bind(wx.EVT_KEY_DOWN, self.OnKeyPressed)
|
|---|
| 1297 |
|
|---|
| 1298 | # Make some styles, the lexer defines what each style is used
|
|---|
| 1299 | # for, we just have to define what each style looks like.
|
|---|
| 1300 | # This set is adapted from Scintilla sample property files.
|
|---|
| 1301 |
|
|---|
| 1302 | # global default styles for all languages
|
|---|
| 1303 | self.StyleSetSpec(stc.STC_STYLE_DEFAULT, "face:%(helv)s,size:%(size)d" % self.faces)
|
|---|
| 1304 | self.StyleClearAll() # reset all to be like the default
|
|---|
| 1305 |
|
|---|
| 1306 | # global default styles for all languages
|
|---|
| 1307 | self.StyleSetSpec(stc.STC_STYLE_DEFAULT, "face:%(helv)s,size:%(size)d" % self.faces)
|
|---|
| 1308 | self.StyleSetSpec(stc.STC_STYLE_LINENUMBER, "back:#C0C0C0,face:%(helv)s,size:%(size2)d" % self.faces)
|
|---|
| 1309 | self.StyleSetSpec(stc.STC_STYLE_CONTROLCHAR, "face:%(other)s" % self.faces)
|
|---|
| 1310 | self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, "fore:#FFFFFF,back:#0000FF,bold")
|
|---|
| 1311 | self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, "fore:#000000,back:#FF0000,bold")
|
|---|
| 1312 |
|
|---|
| 1313 | # Python styles
|
|---|
| 1314 | # Default
|
|---|
| 1315 | self.StyleSetSpec(stc.STC_P_DEFAULT, "fore:#000000,face:%(helv)s,size:%(size)d" % self.faces)
|
|---|
| 1316 | # Comments
|
|---|
| 1317 | self.StyleSetSpec(stc.STC_P_COMMENTLINE, "fore:#007F00,face:%(other)s,size:%(size)d" % self.faces)
|
|---|
| 1318 | # Number
|
|---|
| 1319 | self.StyleSetSpec(stc.STC_P_NUMBER, "fore:#007F7F,size:%(size)d" % self.faces)
|
|---|
| 1320 | # String
|
|---|
| 1321 | self.StyleSetSpec(stc.STC_P_STRING, "fore:#7F007F,face:%(helv)s,size:%(size)d" % self.faces)
|
|---|
| 1322 | # Single quoted string
|
|---|
| 1323 | self.StyleSetSpec(stc.STC_P_CHARACTER, "fore:#7F007F,face:%(helv)s,size:%(size)d" % self.faces)
|
|---|
| 1324 | # Keyword
|
|---|
| 1325 | self.StyleSetSpec(stc.STC_P_WORD, "fore:#00007F,bold,size:%(size)d" % self.faces)
|
|---|
| 1326 | # Triple quotes
|
|---|
| 1327 | self.StyleSetSpec(stc.STC_P_TRIPLE, "fore:#7F0000,size:%(size)d" % self.faces)
|
|---|
| 1328 | # Triple double quotes
|
|---|
| 1329 | self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, "fore:#7F0000,size:%(size)d" % self.faces)
|
|---|
| 1330 | # Class name definition
|
|---|
| 1331 | self.StyleSetSpec(stc.STC_P_CLASSNAME, "fore:#0000FF,bold,underline,size:%(size)d" % self.faces)
|
|---|
| 1332 | # Function or method name definition
|
|---|
| 1333 | self.StyleSetSpec(stc.STC_P_DEFNAME, "fore:#007F7F,bold,size:%(size)d" % self.faces)
|
|---|
| 1334 | # Operators
|
|---|
| 1335 | self.StyleSetSpec(stc.STC_P_OPERATOR, "bold,size:%(size)d" % self.faces)
|
|---|
| 1336 | # Identifiers
|
|---|
| 1337 | self.StyleSetSpec(stc.STC_P_IDENTIFIER, "fore:#000000,face:%(helv)s,size:%(size)d" % self.faces)
|
|---|
| 1338 | # Comment-blocks
|
|---|
| 1339 | self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, "fore:#7F7F7F,size:%(size)d" % self.faces)
|
|---|
| 1340 | # End of line where string is not closed
|
|---|
| 1341 | self.StyleSetSpec(stc.STC_P_STRINGEOL, "fore:#000000,face:%(mono)s,back:#E0C0E0,eol,size:%(size)d" % self.faces)
|
|---|
| 1342 |
|
|---|
| 1343 | self.SetCaretForeground("BLUE")
|
|---|
| 1344 |
|
|---|
| 1345 | def OnKeyPressed(self, event):
|
|---|
| 1346 | """!Key pressed
|
|---|
| 1347 |
|
|---|
| 1348 | @todo implement code completion (see wxPython demo)
|
|---|
| 1349 | """
|
|---|
| 1350 | if not self.modified:
|
|---|
| 1351 | self.modified = True
|
|---|
| 1352 | if self.statusbar:
|
|---|
| 1353 | self.statusbar.SetStatusText(_('Python script contains local modifications'), 0)
|
|---|
| 1354 |
|
|---|
| 1355 | event.Skip()
|
|---|
| 1356 |
|
|---|
| 1357 | def OnUpdateUI(self, evt):
|
|---|
| 1358 | # check for matching braces
|
|---|
| 1359 | braceAtCaret = -1
|
|---|
| 1360 | braceOpposite = -1
|
|---|
| 1361 | charBefore = None
|
|---|
| 1362 | caretPos = self.GetCurrentPos()
|
|---|
| 1363 |
|
|---|
| 1364 | if caretPos > 0:
|
|---|
| 1365 | charBefore = self.GetCharAt(caretPos - 1)
|
|---|
| 1366 | styleBefore = self.GetStyleAt(caretPos - 1)
|
|---|
| 1367 |
|
|---|
| 1368 | # check before
|
|---|
| 1369 | if charBefore and chr(charBefore) in "[]{}()" and styleBefore == stc.STC_P_OPERATOR:
|
|---|
| 1370 | braceAtCaret = caretPos - 1
|
|---|
| 1371 |
|
|---|
| 1372 | # check after
|
|---|
| 1373 | if braceAtCaret < 0:
|
|---|
| 1374 | charAfter = self.GetCharAt(caretPos)
|
|---|
| 1375 | styleAfter = self.GetStyleAt(caretPos)
|
|---|
| 1376 |
|
|---|
| 1377 | if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR:
|
|---|
| 1378 | braceAtCaret = caretPos
|
|---|
| 1379 |
|
|---|
| 1380 | if braceAtCaret >= 0:
|
|---|
| 1381 | braceOpposite = self.BraceMatch(braceAtCaret)
|
|---|
| 1382 |
|
|---|
| 1383 | if braceAtCaret != -1 and braceOpposite == -1:
|
|---|
| 1384 | self.BraceBadLight(braceAtCaret)
|
|---|
| 1385 | else:
|
|---|
| 1386 | self.BraceHighlight(braceAtCaret, braceOpposite)
|
|---|
| 1387 |
|
|---|
| 1388 | def OnMarginClick(self, evt):
|
|---|
| 1389 | # fold and unfold as needed
|
|---|
| 1390 | if evt.GetMargin() == 2:
|
|---|
| 1391 | if evt.GetShift() and evt.GetControl():
|
|---|
| 1392 | self.FoldAll()
|
|---|
| 1393 | else:
|
|---|
| 1394 | lineClicked = self.LineFromPosition(evt.GetPosition())
|
|---|
| 1395 |
|
|---|
| 1396 | if self.GetFoldLevel(lineClicked) & stc.STC_FOLDLEVELHEADERFLAG:
|
|---|
| 1397 | if evt.GetShift():
|
|---|
| 1398 | self.SetFoldExpanded(lineClicked, True)
|
|---|
| 1399 | self.Expand(lineClicked, True, True, 1)
|
|---|
| 1400 | elif evt.GetControl():
|
|---|
| 1401 | if self.GetFoldExpanded(lineClicked):
|
|---|
| 1402 | self.SetFoldExpanded(lineClicked, False)
|
|---|
| 1403 | self.Expand(lineClicked, False, True, 0)
|
|---|
| 1404 | else:
|
|---|
| 1405 | self.SetFoldExpanded(lineClicked, True)
|
|---|
| 1406 | self.Expand(lineClicked, True, True, 100)
|
|---|
| 1407 | else:
|
|---|
| 1408 | self.ToggleFold(lineClicked)
|
|---|
| 1409 |
|
|---|
| 1410 | def FoldAll(self):
|
|---|
| 1411 | lineCount = self.GetLineCount()
|
|---|
| 1412 | expanding = True
|
|---|
| 1413 |
|
|---|
| 1414 | # find out if we are folding or unfolding
|
|---|
| 1415 | for lineNum in range(lineCount):
|
|---|
| 1416 | if self.GetFoldLevel(lineNum) & stc.STC_FOLDLEVELHEADERFLAG:
|
|---|
| 1417 | expanding = not self.GetFoldExpanded(lineNum)
|
|---|
| 1418 | break
|
|---|
| 1419 |
|
|---|
| 1420 | lineNum = 0
|
|---|
| 1421 | while lineNum < lineCount:
|
|---|
| 1422 | level = self.GetFoldLevel(lineNum)
|
|---|
| 1423 | if level & stc.STC_FOLDLEVELHEADERFLAG and \
|
|---|
| 1424 | (level & stc.STC_FOLDLEVELNUMBERMASK) == stc.STC_FOLDLEVELBASE:
|
|---|
| 1425 |
|
|---|
| 1426 | if expanding:
|
|---|
| 1427 | self.SetFoldExpanded(lineNum, True)
|
|---|
| 1428 | lineNum = self.Expand(lineNum, True)
|
|---|
| 1429 | lineNum = lineNum - 1
|
|---|
| 1430 | else:
|
|---|
| 1431 | lastChild = self.GetLastChild(lineNum, -1)
|
|---|
| 1432 | self.SetFoldExpanded(lineNum, False)
|
|---|
| 1433 |
|
|---|
| 1434 | if lastChild > lineNum:
|
|---|
| 1435 | self.HideLines(lineNum+1, lastChild)
|
|---|
| 1436 |
|
|---|
| 1437 | lineNum = lineNum + 1
|
|---|
| 1438 |
|
|---|
| 1439 | def Expand(self, line, doExpand, force=False, visLevels=0, level=-1):
|
|---|
| 1440 | lastChild = self.GetLastChild(line, level)
|
|---|
| 1441 | line = line + 1
|
|---|
| 1442 |
|
|---|
| 1443 | while line <= lastChild:
|
|---|
| 1444 | if force:
|
|---|
| 1445 | if visLevels > 0:
|
|---|
| 1446 | self.ShowLines(line, line)
|
|---|
| 1447 | else:
|
|---|
| 1448 | self.HideLines(line, line)
|
|---|
| 1449 | else:
|
|---|
| 1450 | if doExpand:
|
|---|
| 1451 | self.ShowLines(line, line)
|
|---|
| 1452 |
|
|---|
| 1453 | if level == -1:
|
|---|
| 1454 | level = self.GetFoldLevel(line)
|
|---|
| 1455 |
|
|---|
| 1456 | if level & stc.STC_FOLDLEVELHEADERFLAG:
|
|---|
| 1457 | if force:
|
|---|
| 1458 | if visLevels > 1:
|
|---|
| 1459 | self.SetFoldExpanded(line, True)
|
|---|
| 1460 | else:
|
|---|
| 1461 | self.SetFoldExpanded(line, False)
|
|---|
| 1462 |
|
|---|
| 1463 | line = self.Expand(line, doExpand, force, visLevels-1)
|
|---|
| 1464 | else:
|
|---|
| 1465 | if doExpand and self.GetFoldExpanded(line):
|
|---|
| 1466 | line = self.Expand(line, True, force, visLevels-1)
|
|---|
| 1467 | else:
|
|---|
| 1468 | line = self.Expand(line, False, force, visLevels-1)
|
|---|
| 1469 | else:
|
|---|
| 1470 | line = line + 1
|
|---|
| 1471 |
|
|---|
| 1472 | return line
|
|---|
| 1473 |
|
|---|