| 1 | """
|
|---|
| 2 | @package gui_core.pystc
|
|---|
| 3 |
|
|---|
| 4 | @brief Python styled text control widget
|
|---|
| 5 |
|
|---|
| 6 | Classes:
|
|---|
| 7 | - pystc::PyStc
|
|---|
| 8 |
|
|---|
| 9 | (C) 2012-2013 by the GRASS Development Team
|
|---|
| 10 |
|
|---|
| 11 | This program is free software under the GNU General Public License
|
|---|
| 12 | (>=v2). Read the file COPYING that comes with GRASS for details.
|
|---|
| 13 |
|
|---|
| 14 | @author Martin Landa <landa.martin gmail.com>
|
|---|
| 15 | """
|
|---|
| 16 |
|
|---|
| 17 |
|
|---|
| 18 | import keyword
|
|---|
| 19 |
|
|---|
| 20 | import wx
|
|---|
| 21 | from wx import stc
|
|---|
| 22 |
|
|---|
| 23 |
|
|---|
| 24 | class PyStc(stc.StyledTextCtrl):
|
|---|
| 25 | """Styled Python output (see gmodeler::frame::PythonPanel for
|
|---|
| 26 | usage)
|
|---|
| 27 |
|
|---|
| 28 | Based on StyledTextCtrl_2 from wxPython demo
|
|---|
| 29 | """
|
|---|
| 30 |
|
|---|
| 31 | def __init__(self, parent, id=wx.ID_ANY, statusbar=None):
|
|---|
| 32 | stc.StyledTextCtrl.__init__(self, parent, id)
|
|---|
| 33 |
|
|---|
| 34 | self.parent = parent
|
|---|
| 35 | self.statusbar = statusbar
|
|---|
| 36 |
|
|---|
| 37 | self.modified = False # content modified ?
|
|---|
| 38 |
|
|---|
| 39 | # this is supposed to get monospace
|
|---|
| 40 | font = wx.Font(
|
|---|
| 41 | 9,
|
|---|
| 42 | wx.FONTFAMILY_MODERN,
|
|---|
| 43 | wx.FONTSTYLE_NORMAL,
|
|---|
| 44 | wx.FONTWEIGHT_NORMAL)
|
|---|
| 45 | face = font.GetFaceName()
|
|---|
| 46 | size = font.GetPointSize()
|
|---|
| 47 |
|
|---|
| 48 | # setting the monospace here to not mess with the rest of the code
|
|---|
| 49 | # TODO: review the whole styling
|
|---|
| 50 | self.faces = {'times': face,
|
|---|
| 51 | 'mono': face,
|
|---|
| 52 | 'helv': face,
|
|---|
| 53 | 'other': face,
|
|---|
| 54 | 'size': 10,
|
|---|
| 55 | 'size2': 8,
|
|---|
| 56 | }
|
|---|
| 57 |
|
|---|
| 58 | self.CmdKeyAssign(ord('B'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
|
|---|
| 59 | self.CmdKeyAssign(ord('N'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
|
|---|
| 60 |
|
|---|
| 61 | self.SetLexer(stc.STC_LEX_PYTHON)
|
|---|
| 62 | self.SetKeyWords(0, " ".join(keyword.kwlist))
|
|---|
| 63 |
|
|---|
| 64 | self.SetProperty("fold", "1")
|
|---|
| 65 | self.SetProperty("tab.timmy.whinge.level", "1")
|
|---|
| 66 | self.SetMargins(0, 0)
|
|---|
| 67 | self.SetTabWidth(4)
|
|---|
| 68 | self.SetUseTabs(False)
|
|---|
| 69 |
|
|---|
| 70 | self.SetEdgeMode(stc.STC_EDGE_BACKGROUND)
|
|---|
| 71 | self.SetEdgeColumn(78)
|
|---|
| 72 |
|
|---|
| 73 | # setup a margin to hold fold markers
|
|---|
| 74 | self.SetMarginType(2, stc.STC_MARGIN_SYMBOL)
|
|---|
| 75 | self.SetMarginMask(2, stc.STC_MASK_FOLDERS)
|
|---|
| 76 | self.SetMarginSensitive(2, True)
|
|---|
| 77 | self.SetMarginWidth(2, 12)
|
|---|
| 78 |
|
|---|
| 79 | # like a flattened tree control using square headers
|
|---|
| 80 | self.MarkerDefine(
|
|---|
| 81 | stc.STC_MARKNUM_FOLDEROPEN,
|
|---|
| 82 | stc.STC_MARK_BOXMINUS,
|
|---|
| 83 | "white",
|
|---|
| 84 | "#808080")
|
|---|
| 85 | self.MarkerDefine(
|
|---|
| 86 | stc.STC_MARKNUM_FOLDER,
|
|---|
| 87 | stc.STC_MARK_BOXPLUS,
|
|---|
| 88 | "white",
|
|---|
| 89 | "#808080")
|
|---|
| 90 | self.MarkerDefine(
|
|---|
| 91 | stc.STC_MARKNUM_FOLDERSUB,
|
|---|
| 92 | stc.STC_MARK_VLINE,
|
|---|
| 93 | "white",
|
|---|
| 94 | "#808080")
|
|---|
| 95 | self.MarkerDefine(
|
|---|
| 96 | stc.STC_MARKNUM_FOLDERTAIL,
|
|---|
| 97 | stc.STC_MARK_LCORNER,
|
|---|
| 98 | "white",
|
|---|
| 99 | "#808080")
|
|---|
| 100 | self.MarkerDefine(
|
|---|
| 101 | stc.STC_MARKNUM_FOLDEREND,
|
|---|
| 102 | stc.STC_MARK_BOXPLUSCONNECTED,
|
|---|
| 103 | "white",
|
|---|
| 104 | "#808080")
|
|---|
| 105 | self.MarkerDefine(
|
|---|
| 106 | stc.STC_MARKNUM_FOLDEROPENMID,
|
|---|
| 107 | stc.STC_MARK_BOXMINUSCONNECTED,
|
|---|
| 108 | "white",
|
|---|
| 109 | "#808080")
|
|---|
| 110 | self.MarkerDefine(
|
|---|
| 111 | stc.STC_MARKNUM_FOLDERMIDTAIL,
|
|---|
| 112 | stc.STC_MARK_TCORNER,
|
|---|
| 113 | "white",
|
|---|
| 114 | "#808080")
|
|---|
| 115 |
|
|---|
| 116 | self.Bind(stc.EVT_STC_UPDATEUI, self.OnUpdateUI)
|
|---|
| 117 | self.Bind(stc.EVT_STC_MARGINCLICK, self.OnMarginClick)
|
|---|
| 118 | self.Bind(wx.EVT_KEY_DOWN, self.OnKeyPressed)
|
|---|
| 119 |
|
|---|
| 120 | # show whitespace
|
|---|
| 121 | self.SetViewWhiteSpace(1)
|
|---|
| 122 | # make the symbols very light gray to be less distracting
|
|---|
| 123 | self.SetWhitespaceForeground(True, wx.Colour(200, 200, 200))
|
|---|
| 124 |
|
|---|
| 125 | # Make some styles, the lexer defines what each style is used
|
|---|
| 126 | # for, we just have to define what each style looks like.
|
|---|
| 127 | # This set is adapted from Scintilla sample property files.
|
|---|
| 128 |
|
|---|
| 129 | # global default styles for all languages
|
|---|
| 130 | self.StyleSetSpec(
|
|---|
| 131 | stc.STC_STYLE_DEFAULT,
|
|---|
| 132 | "face:%(helv)s,size:%(size)d" %
|
|---|
| 133 | self.faces)
|
|---|
| 134 | self.StyleClearAll() # reset all to be like the default
|
|---|
| 135 |
|
|---|
| 136 | # global default styles for all languages
|
|---|
| 137 | self.StyleSetSpec(
|
|---|
| 138 | stc.STC_STYLE_DEFAULT,
|
|---|
| 139 | "face:%(helv)s,size:%(size)d" %
|
|---|
| 140 | self.faces)
|
|---|
| 141 | self.StyleSetSpec(
|
|---|
| 142 | stc.STC_STYLE_LINENUMBER,
|
|---|
| 143 | "back:#C0C0C0,face:%(helv)s,size:%(size2)d" %
|
|---|
| 144 | self.faces)
|
|---|
| 145 | self.StyleSetSpec(
|
|---|
| 146 | stc.STC_STYLE_CONTROLCHAR,
|
|---|
| 147 | "face:%(other)s" %
|
|---|
| 148 | self.faces)
|
|---|
| 149 | self.StyleSetSpec(
|
|---|
| 150 | stc.STC_STYLE_BRACELIGHT,
|
|---|
| 151 | "fore:#FFFFFF,back:#0000FF,bold")
|
|---|
| 152 | self.StyleSetSpec(
|
|---|
| 153 | stc.STC_STYLE_BRACEBAD,
|
|---|
| 154 | "fore:#000000,back:#FF0000,bold")
|
|---|
| 155 |
|
|---|
| 156 | # Python styles
|
|---|
| 157 | # Default
|
|---|
| 158 | self.StyleSetSpec(
|
|---|
| 159 | stc.STC_P_DEFAULT,
|
|---|
| 160 | "fore:#000000,face:%(helv)s,size:%(size)d" %
|
|---|
| 161 | self.faces)
|
|---|
| 162 | # Comments
|
|---|
| 163 | self.StyleSetSpec(
|
|---|
| 164 | stc.STC_P_COMMENTLINE,
|
|---|
| 165 | "fore:#007F00,face:%(other)s,size:%(size)d" %
|
|---|
| 166 | self.faces)
|
|---|
| 167 | # Number
|
|---|
| 168 | self.StyleSetSpec(
|
|---|
| 169 | stc.STC_P_NUMBER,
|
|---|
| 170 | "fore:#007F7F,size:%(size)d" %
|
|---|
| 171 | self.faces)
|
|---|
| 172 | # String
|
|---|
| 173 | self.StyleSetSpec(
|
|---|
| 174 | stc.STC_P_STRING,
|
|---|
| 175 | "fore:#7F007F,face:%(helv)s,size:%(size)d" %
|
|---|
| 176 | self.faces)
|
|---|
| 177 | # Single quoted string
|
|---|
| 178 | self.StyleSetSpec(
|
|---|
| 179 | stc.STC_P_CHARACTER,
|
|---|
| 180 | "fore:#7F007F,face:%(helv)s,size:%(size)d" %
|
|---|
| 181 | self.faces)
|
|---|
| 182 | # Keyword
|
|---|
| 183 | self.StyleSetSpec(
|
|---|
| 184 | stc.STC_P_WORD,
|
|---|
| 185 | "fore:#00007F,bold,size:%(size)d" %
|
|---|
| 186 | self.faces)
|
|---|
| 187 | # Triple quotes
|
|---|
| 188 | self.StyleSetSpec(
|
|---|
| 189 | stc.STC_P_TRIPLE,
|
|---|
| 190 | "fore:#7F0000,size:%(size)d" %
|
|---|
| 191 | self.faces)
|
|---|
| 192 | # Triple double quotes
|
|---|
| 193 | self.StyleSetSpec(
|
|---|
| 194 | stc.STC_P_TRIPLEDOUBLE,
|
|---|
| 195 | "fore:#7F0000,size:%(size)d" %
|
|---|
| 196 | self.faces)
|
|---|
| 197 | # Class name definition
|
|---|
| 198 | self.StyleSetSpec(
|
|---|
| 199 | stc.STC_P_CLASSNAME,
|
|---|
| 200 | "fore:#0000FF,bold,underline,size:%(size)d" %
|
|---|
| 201 | self.faces)
|
|---|
| 202 | # Function or method name definition
|
|---|
| 203 | self.StyleSetSpec(
|
|---|
| 204 | stc.STC_P_DEFNAME,
|
|---|
| 205 | "fore:#007F7F,bold,size:%(size)d" %
|
|---|
| 206 | self.faces)
|
|---|
| 207 | # Operators
|
|---|
| 208 | self.StyleSetSpec(
|
|---|
| 209 | stc.STC_P_OPERATOR,
|
|---|
| 210 | "bold,size:%(size)d" %
|
|---|
| 211 | self.faces)
|
|---|
| 212 | # Identifiers
|
|---|
| 213 | self.StyleSetSpec(
|
|---|
| 214 | stc.STC_P_IDENTIFIER,
|
|---|
| 215 | "fore:#000000,face:%(helv)s,size:%(size)d" %
|
|---|
| 216 | self.faces)
|
|---|
| 217 | # Comment-blocks
|
|---|
| 218 | self.StyleSetSpec(
|
|---|
| 219 | stc.STC_P_COMMENTBLOCK,
|
|---|
| 220 | "fore:#7F7F7F,size:%(size)d" %
|
|---|
| 221 | self.faces)
|
|---|
| 222 | # End of line where string is not closed
|
|---|
| 223 | self.StyleSetSpec(
|
|---|
| 224 | stc.STC_P_STRINGEOL,
|
|---|
| 225 | "fore:#000000,face:%(mono)s,back:#E0C0E0,eol,size:%(size)d" %
|
|---|
| 226 | self.faces)
|
|---|
| 227 |
|
|---|
| 228 | self.SetCaretForeground("BLUE")
|
|---|
| 229 |
|
|---|
| 230 | def OnKeyPressed(self, event):
|
|---|
| 231 | """Key pressed
|
|---|
| 232 |
|
|---|
| 233 | .. todo::
|
|---|
| 234 | implement code completion (see wxPython demo)
|
|---|
| 235 | """
|
|---|
| 236 | if not self.modified:
|
|---|
| 237 | self.modified = True
|
|---|
| 238 | if self.statusbar:
|
|---|
| 239 | self.statusbar.SetStatusText(
|
|---|
| 240 | _('Python script contains local modifications'), 0)
|
|---|
| 241 |
|
|---|
| 242 | event.Skip()
|
|---|
| 243 |
|
|---|
| 244 | def OnUpdateUI(self, evt):
|
|---|
| 245 | # check for matching braces
|
|---|
| 246 | braceAtCaret = -1
|
|---|
| 247 | braceOpposite = -1
|
|---|
| 248 | charBefore = None
|
|---|
| 249 | caretPos = self.GetCurrentPos()
|
|---|
| 250 |
|
|---|
| 251 | if caretPos > 0:
|
|---|
| 252 | charBefore = self.GetCharAt(caretPos - 1)
|
|---|
| 253 | styleBefore = self.GetStyleAt(caretPos - 1)
|
|---|
| 254 |
|
|---|
| 255 | # check before
|
|---|
| 256 | if charBefore and chr(
|
|---|
| 257 | charBefore) in "[]{}()" and styleBefore == stc.STC_P_OPERATOR:
|
|---|
| 258 | braceAtCaret = caretPos - 1
|
|---|
| 259 |
|
|---|
| 260 | # check after
|
|---|
| 261 | if braceAtCaret < 0:
|
|---|
| 262 | charAfter = self.GetCharAt(caretPos)
|
|---|
| 263 | styleAfter = self.GetStyleAt(caretPos)
|
|---|
| 264 |
|
|---|
| 265 | if charAfter and chr(
|
|---|
| 266 | charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR:
|
|---|
| 267 | braceAtCaret = caretPos
|
|---|
| 268 |
|
|---|
| 269 | if braceAtCaret >= 0:
|
|---|
| 270 | braceOpposite = self.BraceMatch(braceAtCaret)
|
|---|
| 271 |
|
|---|
| 272 | if braceAtCaret != -1 and braceOpposite == -1:
|
|---|
| 273 | self.BraceBadLight(braceAtCaret)
|
|---|
| 274 | else:
|
|---|
| 275 | self.BraceHighlight(braceAtCaret, braceOpposite)
|
|---|
| 276 |
|
|---|
| 277 | def OnMarginClick(self, evt):
|
|---|
| 278 | # fold and unfold as needed
|
|---|
| 279 | if evt.GetMargin() == 2:
|
|---|
| 280 | if evt.GetShift() and evt.GetControl():
|
|---|
| 281 | self.FoldAll()
|
|---|
| 282 | else:
|
|---|
| 283 | lineClicked = self.LineFromPosition(evt.GetPosition())
|
|---|
| 284 |
|
|---|
| 285 | if self.GetFoldLevel(
|
|---|
| 286 | lineClicked) & stc.STC_FOLDLEVELHEADERFLAG:
|
|---|
| 287 | if evt.GetShift():
|
|---|
| 288 | self.SetFoldExpanded(lineClicked, True)
|
|---|
| 289 | self.Expand(lineClicked, True, True, 1)
|
|---|
| 290 | elif evt.GetControl():
|
|---|
| 291 | if self.GetFoldExpanded(lineClicked):
|
|---|
| 292 | self.SetFoldExpanded(lineClicked, False)
|
|---|
| 293 | self.Expand(lineClicked, False, True, 0)
|
|---|
| 294 | else:
|
|---|
| 295 | self.SetFoldExpanded(lineClicked, True)
|
|---|
| 296 | self.Expand(lineClicked, True, True, 100)
|
|---|
| 297 | else:
|
|---|
| 298 | self.ToggleFold(lineClicked)
|
|---|
| 299 |
|
|---|
| 300 | def FoldAll(self):
|
|---|
| 301 | lineCount = self.GetLineCount()
|
|---|
| 302 | expanding = True
|
|---|
| 303 |
|
|---|
| 304 | # find out if we are folding or unfolding
|
|---|
| 305 | for lineNum in range(lineCount):
|
|---|
| 306 | if self.GetFoldLevel(lineNum) & stc.STC_FOLDLEVELHEADERFLAG:
|
|---|
| 307 | expanding = not self.GetFoldExpanded(lineNum)
|
|---|
| 308 | break
|
|---|
| 309 |
|
|---|
| 310 | lineNum = 0
|
|---|
| 311 | while lineNum < lineCount:
|
|---|
| 312 | level = self.GetFoldLevel(lineNum)
|
|---|
| 313 | if level & stc.STC_FOLDLEVELHEADERFLAG and \
|
|---|
| 314 | (level & stc.STC_FOLDLEVELNUMBERMASK) == stc.STC_FOLDLEVELBASE:
|
|---|
| 315 |
|
|---|
| 316 | if expanding:
|
|---|
| 317 | self.SetFoldExpanded(lineNum, True)
|
|---|
| 318 | lineNum = self.Expand(lineNum, True)
|
|---|
| 319 | lineNum = lineNum - 1
|
|---|
| 320 | else:
|
|---|
| 321 | lastChild = self.GetLastChild(lineNum, -1)
|
|---|
| 322 | self.SetFoldExpanded(lineNum, False)
|
|---|
| 323 |
|
|---|
| 324 | if lastChild > lineNum:
|
|---|
| 325 | self.HideLines(lineNum + 1, lastChild)
|
|---|
| 326 |
|
|---|
| 327 | lineNum = lineNum + 1
|
|---|
| 328 |
|
|---|
| 329 | def Expand(self, line, doExpand, force=False, visLevels=0, level=-1):
|
|---|
| 330 | lastChild = self.GetLastChild(line, level)
|
|---|
| 331 | line = line + 1
|
|---|
| 332 |
|
|---|
| 333 | while line <= lastChild:
|
|---|
| 334 | if force:
|
|---|
| 335 | if visLevels > 0:
|
|---|
| 336 | self.ShowLines(line, line)
|
|---|
| 337 | else:
|
|---|
| 338 | self.HideLines(line, line)
|
|---|
| 339 | else:
|
|---|
| 340 | if doExpand:
|
|---|
| 341 | self.ShowLines(line, line)
|
|---|
| 342 |
|
|---|
| 343 | if level == -1:
|
|---|
| 344 | level = self.GetFoldLevel(line)
|
|---|
| 345 |
|
|---|
| 346 | if level & stc.STC_FOLDLEVELHEADERFLAG:
|
|---|
| 347 | if force:
|
|---|
| 348 | if visLevels > 1:
|
|---|
| 349 | self.SetFoldExpanded(line, True)
|
|---|
| 350 | else:
|
|---|
| 351 | self.SetFoldExpanded(line, False)
|
|---|
| 352 |
|
|---|
| 353 | line = self.Expand(line, doExpand, force, visLevels - 1)
|
|---|
| 354 | else:
|
|---|
| 355 | if doExpand and self.GetFoldExpanded(line):
|
|---|
| 356 | line = self.Expand(line, True, force, visLevels - 1)
|
|---|
| 357 | else:
|
|---|
| 358 | line = self.Expand(line, False, force, visLevels - 1)
|
|---|
| 359 | else:
|
|---|
| 360 | line = line + 1
|
|---|
| 361 |
|
|---|
| 362 | return line
|
|---|