source: grass/branches/develbranch_6/gui/wxpython/gui_core/goutput.py

Last change on this file was 59069, checked in by annakrat, 10 years ago

wxGUI: fix r3.mapcalc in develbranch

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id
  • Property svn:mime-type set to text/x-python
File size: 58.2 KB
Line 
1"""!
2@package gui_core.goutput
3
4@brief Command output widgets
5
6Classes:
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
16This 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
24import os
25import sys
26import textwrap
27import time
28import threading
29import Queue
30import codecs
31import locale
32import keyword
33
34import wx
35from wx import stc
36from wx.lib.newevent import NewEvent
37
38import grass.script as grass
39from grass.script import task as gtask
40
41from core import globalvar
42from core import utils
43from core.gcmd import CommandThread, GMessage, GError, GException, EncodeString
44from gui_core.forms import GUI
45from gui_core.prompt import GPromptSTC
46from core.debug import Debug
47from core.settings import UserSettings
48from gui_core.ghelp import SearchModuleWindow
49
50wxCmdOutput, EVT_CMD_OUTPUT = NewEvent()
51wxCmdProgress, EVT_CMD_PROGRESS = NewEvent()
52wxCmdRun, EVT_CMD_RUN = NewEvent()
53wxCmdDone, EVT_CMD_DONE = NewEvent()
54wxCmdAbort, EVT_CMD_ABORT = NewEvent()
55wxCmdPrepare, EVT_CMD_PREPARE = NewEvent()
56
57def 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
62class 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
183class 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
997class 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
1023class 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
1096class 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
1242class 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
Note: See TracBrowser for help on using the repository browser.