plugins: binary ugens - correct zero, firstarg and secondarg
[supercollider.git] / editors / Psycollider / Psycollider.py
blobc595750894dd14ca3bacfe85fd8155271354d7bd
1 #!/usr/bin/python
2 # File: Psycollider.py
3 # Copyright (c) Benjamin Golinvaux
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License as
7 # published by the Free Software Foundation; either version 2 of the
8 # License, or (at your option) any later version.
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 # General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
18 # USA
20 # Originally started by
21 # Benjamin Golinvaux
22 # benjamin.golinvaux@euresys.com
23 # messenger: bgolinvaux@hotmail.com
25 # currently maintained by:
26 # Christopher Frauenberger - frauenberger@iem.at
27 # John Glover - glover.john@gmail.com
28 # Martin Victory - martin.victory@gmail.com
30 # Latest changes:
31 # Nov 2009 (Martin)
32 # - Help file navigation
33 # - Find and Replace dialogs
34 # - Text Formatting
35 # - About dialog box
37 # Jan 2008 (John)
38 # - SDI model
39 # - Remembers the size and position of the post window
40 # - Allows you to save the contents of the post window (file > save/save as)
41 # - Automatically stops sc server and swing osc when closing the main (post) window
42 # - Allows you to change the default window size
43 # - Double clicking on brackets selects the block of text they surround (update by Christopher)
44 # - Like the GEdit SC plugin (linux), you can execute a block of text surrounded by round brackets
45 # by placing the cursor at the opening bracket and pressing evaluate (ctrl + enter).
46 # This only happens if the open bracket is the first character on the line (not including white space)
47 # - Disabled word wrap in the text editor
48 # - Can toggle displaying of line numbers on/off in code editor
49 # (effects all code windows and is saved to config)
50 # - added ability to clear the recent file list (file history)
51 # - added the option to set the tab size in code windows (saved to config)
53 # ---------------------------------------------------------------------
55 import PySCLang
56 import wx
57 import wx.stc as stc
58 import wx.html as html
59 import wx.richtext as richtext
60 import os, string, keyword, sys, time
62 if wx.Platform == '__WXMSW__':
63 faces = { 'times': 'Times New Roman', 'mono' : 'Courier New', 'helv' : 'Arial', 'other': 'Comic Sans MS', 'size' : 10, 'size2': 8, }
64 gAppHelpFolder = 'help_windows'
65 else:
66 faces = { 'times': 'Times', 'mono' : 'Courier', 'helv' : 'Helvetica', 'other': 'new century schoolbook', 'size' : 10, 'size2': 8, }
67 gAppHelpFolder = 'Help-windows'
69 gHelpFolder = 'Help'
70 gUserExtensionFolder = os.path.join(os.path.expanduser("~"), "SuperCollider\\Extensions")
72 MAX_HISTORY_FILES = 9
73 DEFAULT_SIZEX = 500
74 DEFAULT_SIZEY = 300
75 DEFAULT_POSX = 100
76 DEFAULT_POSY = 100
78 #----------------------------------------------------------------------
79 # set SC3_KEYWORDS as a global variable.
80 try:
81 file = open("keywords.list","r")
82 SC3_KEYWORDS = string.split( file.read() )
83 file.close()
84 except IOError:
85 SC3_KEYWORDS = [ "var", "arg", "Server" ]
86 print "warning:"
87 print "SC3-keywords definition file \"keywords.list\" was not found."
88 print "so now, these following words are the KEYWORDS for the meantime."
89 print SC3_KEYWORDS
91 SC3_KEYWORDS.sort()
94 # ---------------------------------------------------------------------
95 # PsycolliderWindow
97 # Base class for all windows
98 # - creates the default menus
99 # - asks to save a modified file when closing
100 # - adds file history
101 # - holds an ID number for each window, for PsycolliderDocument to refer to self
102 nextPsycolliderWindowId = 1
103 class PsycolliderWindow(wx.Frame):
104 config = None # wx.FileConfig object
105 menubar = None # wx.MenuBar object
106 fileMenu = None # file menu (wx.Menu object)
107 editMenu = None # edit menu (wx.Menu)
108 langMenu = None # lang menu (wx.Menu)
109 optionsMenu = None # options menu (wx.Menu)
110 helpMenu = None # help menu (wx.Menu)
111 title = "" # the window title
112 isModified = False # whether or not window contents have been modified
113 filePath = "" # path to file being displayed
114 windowId = -99
116 def __init__(self, parent, id, title="", winStyle=wx.DEFAULT_FRAME_STYLE):
117 self.title = title
118 self.config = wx.GetApp().config
119 wx.Frame.__init__(self, parent, id, title, style=winStyle)
120 global nextPsycolliderWindowId
121 windowId = nextPsycolliderWindowId
122 nextPsycolliderWindowId = nextPsycolliderWindowId + 1
123 #sys.stdout.write("windowId in pythonland is ")
124 #sys.stdout.write(str(windowId))
125 #sys.stdout.write("\n")
127 self.config.SetPath("/WindowSettings")
128 sizeX = self.config.ReadInt('DefaultSizeX', DEFAULT_SIZEX)
129 sizeY = self.config.ReadInt('DefaultSizeY', DEFAULT_SIZEY)
130 self.SetSize(wx.Size(sizeX, sizeY))
132 self.CreateMenuBar()
133 self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
135 # file menu actions
136 def OnCloseWindow(self, event):
137 if not self.CanCloseWindow():
138 event.Veto()
140 wx.GetApp().fileHistory.RemoveMenu(self.openRecentMenu)
141 wx.GetApp().ClosedWindow(self)
142 self.Destroy()
144 def OnNewCodeWin(self, event):
145 wx.GetApp().NewCodeWindow()
147 def OnHtmlToCode(self, event):
148 wx.GetApp().HtmlToCode(self)
150 def OnOpenFile(self, event):
151 wx.GetApp().OpenFile()
153 def OnSaveFile(self, event):
154 self.SaveFile()
156 def OnSaveFileAs(self, event):
157 self.SaveFileAs()
159 # edit menu actions - need to be overriden by inheriting class
160 def OnUndo(self, event):
161 pass
163 def OnRedo(self, event):
164 pass
166 def OnCut(self, event):
167 pass
169 def OnCopy(self, event):
170 pass
172 def OnPaste(self, event):
173 pass
175 def OnDelete(self, event):
176 pass
178 def OnSelectAll(self, event):
179 pass
181 def OnShowFind(self, event):
182 findData = wx.FindReplaceData()
183 findDialog = wx.FindReplaceDialog(self, findData, "Find")
184 findDialog.findData = findData
185 findDialog.Show(True)
187 def OnShowFindReplace(self, event):
188 findReplaceData = wx.FindReplaceData()
189 findReplaceDialog = wx.FindReplaceDialog(self, findReplaceData, "Find & Replace", wx.FR_REPLACEDIALOG)
190 findReplaceDialog.findReplaceData = findReplaceData
191 findReplaceDialog.Show(True)
193 def OnFindClose(self, event):
194 event.GetDialog().Destroy()
196 def OnFind(self, event):
197 pass
199 # lang menu actions
200 def OnStopServer(self, event):
201 wx.GetApp().StopServer()
203 def OnRun(self, event):
204 wx.GetApp().Run()
206 def OnStop(self, event):
207 wx.GetApp().Stop()
209 def OnCompileLibrary(self, event):
210 wx.GetApp().CompileLibrary()
212 def OnOpenClassDef(self, event):
213 self.OpenClassDef()
215 def OnImpOf(self, event):
216 self.ImpOf()
218 def OnRefsTo(self, event):
219 self.RefsTo()
221 def OnEval(self, event):
222 self.Eval()
224 def OnClearPostWindow(self, event):
225 wx.GetApp().ClearPostWindow()
227 #help menu actions
228 def OnScHelp(self, event):
229 wx.GetApp().GoToHelpFile(self.GetSelectedTextOrLine())
231 def OnBrowseHelp(self, event):
232 wx.GetApp().Eval("Help.gui")
234 def OnBrowseClasses(self, event):
235 wx.GetApp().Eval("Help.browse")
237 def OnAbout(self, event):
239 description = 'a programming language and engine for real time audio synthesis.'
241 info = wx.AboutDialogInfo()
243 info.SetIcon(wx.Icon('Help/GUI/Cocoa-GUI/SCImage/icon.supercollider.png', wx.BITMAP_TYPE_PNG))
244 info.SetName('SuperCollider')
245 info.SetVersion('3.3.2')
246 info.SetDescription(description)
247 info.SetWebSite('http://supercollider.sourceforge.net')
249 wx.AboutBox(info)
251 #options menu actions
252 def OnSetDefaultWindowSize(self, event):
253 size = self.GetSize()
254 wx.GetApp().SetDefaultWindowSize(size.x, size.y)
256 def OnClearRecentFileList(self, event):
257 wx.GetApp().ClearRecentFileList()
259 # should be overwritten by inheriting classes
260 def SaveFile(self):
261 wx.MessageBox("Error: Saving not implemented for this type of window")
263 # should be overwritten by inheriting classes
264 def SaveFileAs(self):
265 wx.MessageBox("Error: Saving not implemented for this type of window")
267 def OpenClassDef(self):
268 wx.GetApp().OpenClassDef(self.GetSelectedTextOrLine())
270 def ImpOf(self):
271 wx.GetApp().ImpOf(self.GetSelectedTextOrLine())
273 def RefsTo(self):
274 wx.GetApp().RefsTo(self.GetSelectedTextOrLine())
276 def Eval(self):
277 wx.GetApp().Eval(self.GetSelectedTextOrLine())
278 self.LineDown()
280 # needs to be overwritten by inheriting classes
281 def LineDown(self):
282 pass
284 # should be overwritten by inheriting classes
285 def GetSelectedTextOrLine(self):
286 return ""
288 def CanCloseWindow(self):
289 if self.isModified:
290 if self.filePath == "":
291 dlg = wx.MessageDialog(self,"Do you want to save %s ? " % self.title,"SuperCollider",wx.CANCEL | wx.YES_NO)
292 reply = dlg.ShowModal()
293 if reply == wx.ID_YES:
294 self.SaveFileAs()
295 return True
296 elif reply == wx.ID_NO:
297 return True
298 elif reply == wx.ID_CANCEL:
299 return False
300 else:
301 dlg = wx.MessageDialog(self,"Do you want to save %s ?" % self.filePath,"SuperCollider",wx.CANCEL | wx.YES_NO)
302 reply = dlg.ShowModal()
303 if reply == wx.ID_YES:
304 self.SaveFile()
305 return True
306 elif reply == wx.ID_NO:
307 return True
308 elif reply == wx.ID_CANCEL:
309 return False
310 else:
311 return True
313 def CreateMenuBar(self):
314 self.fileMenu = wx.Menu()
315 self.openRecentMenu = wx.Menu()
316 self.editMenu = wx.Menu()
317 self.langMenu = wx.Menu()
318 self.optionsMenu = wx.Menu()
319 self.helpMenu = wx.Menu()
320 self.menubar = wx.MenuBar()
321 self.menubar.Append(self.fileMenu, "&File")
322 self.menubar.Append(self.editMenu, "&Edit")
323 self.menubar.Append(self.langMenu, "&Lang")
324 self.menubar.Append(self.optionsMenu, "&Options")
325 self.menubar.Append(self.helpMenu, "&Help")
326 self.SetMenuBar(self.menubar)
328 self.newCodeWin = wx.MenuItem(self.fileMenu, -1, '&New\tCtrl+N')
329 self.htmlToCode = wx.MenuItem(self.fileMenu, -1, 'H&TML to Code\tCtrl+T')
330 self.openFile = wx.MenuItem(self.fileMenu, -1, '&Open...\tCtrl+O')
331 self.saveFile = wx.MenuItem(self.fileMenu, -1, '&Save\tCtrl+S')
332 self.saveFileAs = wx.MenuItem(self.fileMenu, -1, 'Save &As...\tCtrl+Shift+S')
333 self.closeWindow = wx.MenuItem(self.fileMenu, -1, 'Close\tCtrl+W')
335 self.undo = wx.MenuItem(self.editMenu, -1, '&Undo\tCtrl+Z')
336 self.redo = wx.MenuItem(self.editMenu, -1, '&Redo\tCtrl+Y')
337 self.cut = wx.MenuItem(self.editMenu, -1, '&Cut\tCtrl+X')
338 self.copy = wx.MenuItem(self.editMenu, -1, 'C&opy\tCtrl+C')
339 self.paste = wx.MenuItem(self.editMenu, -1, '&Paste\tCtrl+V')
340 self.delete = wx.MenuItem(self.editMenu, -1, '&Delete\tDel')
341 self.selectAll = wx.MenuItem(self.editMenu, -1, '&Select All\tCtrl+A')
342 self.find = wx.MenuItem(self.editMenu, -1, '&Find\tCtrl+F')
343 self.replace = wx.MenuItem(self.editMenu, -1, '&Replace\tCtrl+H')
345 self.stopServer = wx.MenuItem(self.langMenu, -1, 'Stop Server')
346 self.run = wx.MenuItem(self.langMenu, -1, 'Run\tAlt+R')
347 self.stop = wx.MenuItem(self.langMenu, -1, '&Stop\tAlt+.')
348 self.compileLibrary = wx.MenuItem(self.langMenu, -1, 'Compile Library\tAlt+K')
349 self.openClassDef = wx.MenuItem(self.langMenu, -1, 'Open Class Def\tAlt+J')
350 self.impOf = wx.MenuItem(self.langMenu, -1, 'Implementations of\tAlt+Y')
351 self.refsTo = wx.MenuItem(self.langMenu, -1, 'References to\tShift+Alt+Y')
352 self.eval = wx.MenuItem(self.langMenu, -1, '&Evaluate Selection\tCtrl+Enter')
353 self.clearPostWindow = wx.MenuItem(self.langMenu, -1, '&Clear Post Window\tAlt+P')
355 self.setDefaultWindowSize = wx.MenuItem(self.optionsMenu, -1, '&Set This Window Size As Default')
356 self.clearRecentFileList = wx.MenuItem(self.optionsMenu, -1, '&Clear Recent File List')
358 self.scHelp = wx.MenuItem(self.helpMenu, -1, '&SuperCollider Help\tF1')
359 self.helpBrowser = wx.MenuItem(self.helpMenu, -1, '&Browse and Search Documentation\t')
360 self.classBrowser = wx.MenuItem(self.helpMenu, -1, '&Class Browser\t')
361 self.about = wx.MenuItem(self.helpMenu, -1, 'About\t')
364 self.fileMenu.AppendItem(self.newCodeWin)
365 self.fileMenu.AppendItem(self.openFile)
366 self.fileMenu.AppendSubMenu(self.openRecentMenu, "Open Recent")
367 self.fileMenu.AppendSeparator()
368 self.fileMenu.AppendItem(self.saveFile)
369 self.fileMenu.AppendItem(self.saveFileAs)
370 self.fileMenu.AppendItem(self.htmlToCode)
371 self.fileMenu.AppendSeparator()
372 self.fileMenu.AppendItem(self.closeWindow)
374 self.editMenu.AppendItem(self.undo)
375 self.editMenu.AppendItem(self.redo)
376 self.editMenu.AppendSeparator()
377 self.editMenu.AppendItem(self.cut)
378 self.editMenu.AppendItem(self.copy)
379 self.editMenu.AppendItem(self.paste)
380 self.editMenu.AppendItem(self.delete)
381 self.editMenu.AppendItem(self.selectAll)
382 self.editMenu.AppendSeparator()
383 self.editMenu.AppendItem(self.find)
384 self.editMenu.AppendItem(self.replace)
386 self.langMenu.AppendItem(self.stopServer)
387 self.langMenu.AppendItem(self.run)
388 self.langMenu.AppendItem(self.stop)
389 self.langMenu.AppendItem(self.compileLibrary)
390 self.langMenu.AppendItem(self.openClassDef)
391 self.langMenu.AppendItem(self.impOf)
392 self.langMenu.AppendItem(self.refsTo)
393 self.langMenu.AppendItem(self.eval)
394 self.langMenu.AppendItem(self.clearPostWindow)
396 self.optionsMenu.AppendItem(self.setDefaultWindowSize)
397 self.optionsMenu.AppendItem(self.clearRecentFileList)
399 self.helpMenu.AppendItem(self.scHelp)
400 self.helpMenu.AppendItem(self.helpBrowser)
401 self.helpMenu.AppendItem(self.classBrowser)
402 self.helpMenu.AppendSeparator()
403 self.helpMenu.AppendItem(self.about)
405 self.Bind(wx.EVT_MENU, self.OnNewCodeWin, id=self.newCodeWin.GetId())
406 self.Bind(wx.EVT_MENU, self.OnHtmlToCode, id=self.htmlToCode.GetId())
407 self.Bind(wx.EVT_MENU, self.OnOpenFile, id=self.openFile.GetId())
408 self.Bind(wx.EVT_MENU, self.OnSaveFile, id=self.saveFile.GetId())
409 self.Bind(wx.EVT_MENU, self.OnSaveFileAs, id=self.saveFileAs.GetId())
410 self.Bind(wx.EVT_MENU, self.OnCloseWindow, id=self.closeWindow.GetId())
412 self.Bind(wx.EVT_MENU, self.OnUndo, id=self.undo.GetId())
413 self.Bind(wx.EVT_MENU, self.OnRedo, id=self.redo.GetId())
414 self.Bind(wx.EVT_MENU, self.OnCut, id=self.cut.GetId())
415 self.Bind(wx.EVT_MENU, self.OnCopy, id=self.copy.GetId())
416 self.Bind(wx.EVT_MENU, self.OnPaste, id=self.paste.GetId())
417 self.Bind(wx.EVT_MENU, self.OnDelete, id=self.delete.GetId())
418 self.Bind(wx.EVT_MENU, self.OnSelectAll, id=self.selectAll.GetId())
419 self.Bind(wx.EVT_MENU, self.OnShowFind, id=self.find.GetId())
420 self.Bind(wx.EVT_MENU, self.OnShowFindReplace, id=self.replace.GetId())
422 self.Bind(wx.EVT_FIND, self.OnFind)
423 self.Bind(wx.EVT_FIND_NEXT, self.OnFind)
424 self.Bind(wx.EVT_FIND_REPLACE, self.OnFind)
425 self.Bind(wx.EVT_FIND_REPLACE_ALL, self.OnFind)
426 self.Bind(wx.EVT_FIND_CLOSE, self.OnFindClose)
428 self.Bind(wx.EVT_MENU, self.OnStopServer, id=self.stopServer.GetId())
429 self.Bind(wx.EVT_MENU, self.OnRun, id=self.run.GetId())
430 self.Bind(wx.EVT_MENU, self.OnStop, id=self.stop.GetId())
431 self.Bind(wx.EVT_MENU, self.OnCompileLibrary, id=self.compileLibrary.GetId())
432 self.Bind(wx.EVT_MENU, self.OnOpenClassDef, id=self.openClassDef.GetId())
433 self.Bind(wx.EVT_MENU, self.OnImpOf, id=self.impOf.GetId())
434 self.Bind(wx.EVT_MENU, self.OnRefsTo, id=self.refsTo.GetId())
435 self.Bind(wx.EVT_MENU, self.OnEval, id=self.eval.GetId())
436 self.Bind(wx.EVT_MENU, self.OnClearPostWindow, id=self.clearPostWindow.GetId())
438 self.Bind(wx.EVT_MENU, self.OnSetDefaultWindowSize, id=self.setDefaultWindowSize.GetId())
439 self.Bind(wx.EVT_MENU, self.OnClearRecentFileList, id=self.clearRecentFileList.GetId())
441 self.Bind(wx.EVT_MENU, self.OnScHelp, id=self.scHelp.GetId())
442 self.Bind(wx.EVT_MENU, self.OnBrowseHelp, id=self.helpBrowser.GetId())
443 self.Bind(wx.EVT_MENU, self.OnBrowseClasses, id=self.classBrowser.GetId())
444 self.Bind(wx.EVT_MENU, self.OnAbout, id=self.about.GetId())
446 wx.GetApp().fileHistory.UseMenu(self.openRecentMenu)
447 wx.GetApp().fileHistory.AddFilesToThisMenu(self.openRecentMenu)
448 self.Bind(wx.EVT_MENU_RANGE, wx.GetApp().doFileHistory, id=wx.ID_FILE1, id2=wx.ID_FILE9)
451 # ---------------------------------------------------------------------
452 # PsycolliderCodeSubWin - plain text window for code
453 class PsycolliderCodeSubWin(wx.stc.StyledTextCtrl):
455 fold_symbols = 3
457 def __init__ (self,parent):
458 stc.StyledTextCtrl.__init__(self,parent)
459 self.SetModEventMask(wx.stc.STC_MOD_INSERTTEXT | wx.stc.STC_MOD_DELETETEXT | wx.stc.STC_PERFORMED_USER)
461 # bindings
462 self.Bind(stc.EVT_STC_CHANGE, self.OnStcChange)
463 self.Bind(stc.EVT_STC_UPDATEUI, self.OnUpdateUI)
464 self.Bind(stc.EVT_STC_MARGINCLICK, self.OnMarginClick)
465 self.Bind(wx.EVT_KEY_DOWN, self.OnKeyPressed)
466 self.Bind(wx.EVT_CHAR, self.OnChar) # this hack is to enable the alt+. shortcut
467 # to stop playback, which doesn't seem to work otherwise
468 # bug in wx?
469 self.Bind(wx.EVT_LEFT_DCLICK, self.OnDoubleClick)
471 self.SetLexer(wx.stc.STC_LEX_CPP) # yssr
472 self.SetKeyWords(0, " ".join(SC3_KEYWORDS)) # yssr
474 self.SetProperty("fold", "1")
475 self.SetProperty("tab.timmy.whinge.level", "1")
476 self.SetMargins(1,0) # yssr
478 # set end-of-line character to LF
479 self.SetEOLMode(wx.stc.STC_EOL_LF);
481 # some settings for appearance
482 self.SetViewWhiteSpace(False)
483 self.SetViewEOL(False)
484 self.SetUseAntiAliasing(True)
485 self.SetWrapMode(False)
486 self.SetEdgeMode(stc.STC_EDGE_NONE)
488 # Setup a margin to hold fold markers
489 self.SetMarginType(2, stc.STC_MARGIN_SYMBOL)
490 self.SetMarginMask(2, stc.STC_MASK_FOLDERS)
491 self.SetMarginSensitive(2, True)
492 self.SetMarginWidth(2, 12)
494 # Like a flattened tree control using square headers
495 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_BOXMINUS, "white", "#808080")
496 self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_BOXPLUS, "white", "#808080")
497 self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_VLINE, "white", "#808080")
498 self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_LCORNER, "white", "#808080")
499 self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_BOXPLUSCONNECTED, "white", "#808080")
500 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_BOXMINUSCONNECTED, "white", "#808080")
501 self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_TCORNER, "white", "#808080")
504 def OnChar(self, event):
505 if event.GetKeyCode() == 0x2e and event.AltDown():
506 self.GetParent().OnStop(None)
507 else:
508 event.Skip()
510 def OnStcChange(self, event):
511 self.GetParent().OnStcChange(event)
513 def OnKeyPressed(self, event):
514 #if self.CallTipActive():
515 # self.CallTipCancel()
516 key = event.GetKeyCode()
518 if key == 32 and event.ControlDown():
519 pos = self.GetCurrentPos()
521 # Tips
522 if event.ShiftDown():
523 self.CallTipSetBackground("yellow")
524 self.CallTipShow(pos, 'we can display tips here')
525 # Code completion
526 else:
527 #lst = []
528 #for x in range(50000):
529 # lst.append('%05d' % x)
530 #st = " ".join(lst)
531 #print len(st)
532 #self.AutoCompShow(0, st)
534 kw = keyword.kwlist[:]
535 #kw.append("zzzzzz?2")
537 kw.sort() # Python sorts are case sensitive
538 self.AutoCompSetIgnoreCase(False) # so this needs to match
540 # Images are specified with a appended "?type"
541 for i in range(len(kw)):
542 if kw[i] in keyword.kwlist:
543 kw[i] = kw[i] + "?1"
544 self.AutoCompShow(0, " ".join(kw))
545 else:
546 event.Skip()
548 def OnDoubleClick(self, evt):
549 braceAtCaret, braceOpposite = self.CheckForMatchingBraces()
551 if braceAtCaret != -1 and braceOpposite != -1:
552 if braceAtCaret < braceOpposite:
553 self.SetSelection(braceAtCaret+1, braceOpposite)
554 else:
555 self.SetSelection(braceOpposite+1, braceAtCaret)
556 else:
557 evt.Skip()
559 def OnUpdateUI(self, evt):
560 braceAtCaret, braceOpposite = self.CheckForMatchingBraces()
562 if braceAtCaret != -1 and braceOpposite == -1:
563 self.BraceBadLight(braceAtCaret)
564 else:
565 self.BraceHighlight(braceAtCaret, braceOpposite)
567 def OnMarginClick(self, evt):
568 # fold and unfold as needed
569 if evt.GetMargin() == 2:
570 if evt.GetShift() and evt.GetControl():
571 self.FoldAll()
572 else:
573 lineClicked = self.LineFromPosition(evt.GetPosition())
575 if self.GetFoldLevel(lineClicked) & stc.STC_FOLDLEVELHEADERFLAG:
576 if evt.GetShift():
577 self.SetFoldExpanded(lineClicked, True)
578 self.Expand(lineClicked, True, True, 1)
579 elif evt.GetControl():
580 if self.GetFoldExpanded(lineClicked):
581 self.SetFoldExpanded(lineClicked, False)
582 self.Expand(lineClicked, False, True, 0)
583 else:
584 self.SetFoldExpanded(lineClicked, True)
585 self.Expand(lineClicked, True, True, 100)
586 else:
587 self.ToggleFold(lineClicked)
589 def CheckForMatchingBraces(self):
590 braceAtCaret = -1
591 braceOpposite = -1
592 charAfter = None
593 charBefore = None
594 caretPos = self.GetCurrentPos()
596 if caretPos > 0:
597 charBefore = self.GetCharAt(caretPos - 1)
598 styleBefore = self.GetStyleAt(caretPos - 1)
600 # check before
601 if charBefore and chr(charBefore) in "[]{}()" and styleBefore == stc.STC_C_OPERATOR:
602 braceAtCaret = caretPos - 1
604 # check after
605 if braceAtCaret < 0:
606 charAfter = self.GetCharAt(caretPos)
607 styleAfter = self.GetStyleAt(caretPos)
609 if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_C_OPERATOR:
610 braceAtCaret = caretPos
612 if braceAtCaret >= 0:
613 braceOpposite = self.BraceMatch(braceAtCaret)
615 return braceAtCaret, braceOpposite
617 def FoldAll(self):
618 lineCount = self.GetLineCount()
619 expanding = True
621 # find out if we are folding or unfolding
622 for lineNum in range(lineCount):
623 if self.GetFoldLevel(lineNum) & stc.STC_FOLDLEVELHEADERFLAG:
624 expanding = not self.GetFoldExpanded(lineNum)
625 break;
627 lineNum = 0
629 while lineNum < lineCount:
630 level = self.GetFoldLevel(lineNum)
631 if level & stc.STC_FOLDLEVELHEADERFLAG and \
632 (level & stc.STC_FOLDLEVELNUMBERMASK) == stc.STC_FOLDLEVELBASE:
634 if expanding:
635 self.SetFoldExpanded(lineNum, True)
636 lineNum = self.Expand(lineNum, True)
637 lineNum = lineNum - 1
638 else:
639 lastChild = self.GetLastChild(lineNum, -1)
640 self.SetFoldExpanded(lineNum, False)
642 if lastChild > lineNum:
643 self.HideLines(lineNum+1, lastChild)
645 lineNum = lineNum + 1
648 def Expand(self, line, doExpand, force=False, visLevels=0, level=-1):
649 lastChild = self.GetLastChild(line, level)
650 line = line + 1
652 while line <= lastChild:
653 if force:
654 if visLevels > 0:
655 self.ShowLines(line, line)
656 else:
657 self.HideLines(line, line)
658 else:
659 if doExpand:
660 self.ShowLines(line, line)
662 if level == -1:
663 level = self.GetFoldLevel(line)
665 if level & stc.STC_FOLDLEVELHEADERFLAG:
666 if force:
667 if visLevels > 1:
668 self.SetFoldExpanded(line, True)
669 else:
670 self.SetFoldExpanded(line, False)
672 line = self.Expand(line, doExpand, force, visLevels-1)
674 else:
675 if doExpand and self.GetFoldExpanded(line):
676 line = self.Expand(line, True, force, visLevels-1)
677 else:
678 line = self.Expand(line, False, force, visLevels-1)
679 else:
680 line = line + 1;
682 return line
684 def SetShowLineNumbers(self, value):
685 if value:
686 self.SetMarginType(2, stc.STC_MARGIN_NUMBER)
687 else:
688 self.SetMarginType(2, stc.STC_MARGIN_SYMBOL)
690 def GetTabSize(self):
691 return self.GetTabWidth()
693 def SetTabSize(self, tabSize):
694 self.SetTabWidth(tabSize)
696 def GetText(self):
697 # append an extra space, as GetTextUTF8() seems to remove the last character, wx bug?
698 self.AppendTextUTF8(" ")
699 return self.GetTextUTF8()
702 # ---------------------------------------------------------------------
703 # Code Window
704 # accomodates the code sub window
705 class PsycolliderCodeWin(PsycolliderWindow):
707 SHOW_LINE_NUMBERS = False # default
708 TAB_SIZE = 8 # default tab size
710 def __init__ (self, parent, id, title, pos=wx.DefaultPosition, size=wx.DefaultSize):
711 PsycolliderWindow.__init__(self, parent, id, title)
712 self.fileMenu.Remove(self.htmlToCode.GetId()) # Remove unnecessary menu item
713 self.codeSubWin = PsycolliderCodeSubWin(self)
715 # this will be our default font
716 font = wx.Font(10, wx.MODERN, wx.NORMAL, wx.NORMAL, False, "Courier New")
717 self.fontSize = font.GetPointSize()
718 self.fontFace = font.GetFaceName()
720 self.ChangeFont(self.fontFace, self.fontSize)
722 # line numbers
723 self.config.SetPath("/CodeWindowOptions")
724 self.showLineNumbers.Check(self.config.ReadInt('ShowLineNumbers', self.SHOW_LINE_NUMBERS))
725 self.codeSubWin.SetShowLineNumbers(self.showLineNumbers.IsChecked())
727 # tab size
728 self.codeSubWin.SetTabSize(self.config.ReadInt('TabSize', self.TAB_SIZE))
730 def OnStcChange(self, event):
731 if not self.isModified:
732 self.SetTitle(self.GetTitle() + "*")
733 self.isModified = True
735 def OnShowLineNumbers(self, event):
736 for window in wx.GetApp().GetOpenWindows():
737 if type(window) == PsycolliderCodeWin:
738 window.SetShowLineNumbers(self.showLineNumbers.IsChecked())
740 self.config.SetPath("/CodeWindowOptions")
741 self.config.WriteInt('ShowLineNumbers', self.showLineNumbers.IsChecked())
743 def OnSetTabSize(self, event):
744 newTabSize = -1
745 getNewTabSize = wx.TextEntryDialog(self, 'Set tab size to:', 'Set Tab Size', str(self.TAB_SIZE))
746 getNewTabSize.SetValue(str(self.codeSubWin.GetTabSize()))
748 if getNewTabSize.ShowModal() == wx.ID_OK:
749 try:
750 newTabSize = int(getNewTabSize.GetValue())
751 if newTabSize <= 0:
752 raise
754 for window in wx.GetApp().GetOpenWindows():
755 if type(window) == PsycolliderCodeWin:
756 window.codeSubWin.SetTabSize(newTabSize)
758 self.config.SetPath("/CodeWindowOptions")
759 self.config.WriteInt('TabSize', newTabSize)
761 except:
762 WriteInLogWindow("Invalid tab size, ignoring. Please enter a positive integer\n")
763 return
765 getNewTabSize.Destroy()
767 def GetSelectedText(self):
768 return self.codeSubWin.GetSelectedTextUTF8()
770 def GetCurLineAsString(self):
771 return self.codeSubWin.GetCurLine()
773 def SetSubWinFocus(self):
774 self.codeSubWin.SetFocus()
776 def SelectRange(self,rangeStart,rangeSize):
777 self.codeSubWin.SetSelection(rangeStart,rangeStart+rangeSize)
779 def SetShowLineNumbers(self, value):
780 self.showLineNumbers.Check(value)
781 self.codeSubWin.SetShowLineNumbers(value)
783 def SaveFile(self):
784 if self.filePath == "":
785 self.SaveFileAs()
786 else:
787 try:
788 file = open(self.filePath, "w")
789 content = self.codeSubWin.GetText()
790 file.write(content)
791 self.SetTitle(self.filePath)
792 self.isModified = False
793 file.close()
794 except:
795 # Todo: better error handling? Just print error message for now
796 wx.MessageBox("Error: Could not save file " + self.filePath)
798 def SaveFileAs(self):
799 fileDlg = wx.FileDialog(self,style=wx.SAVE)
800 if fileDlg.ShowModal() == wx.ID_OK:
801 self.filePath = fileDlg.GetPath()
802 try:
803 file = open(self.filePath ,"w")
804 content = self.codeSubWin.GetText()
805 file.write(content)
806 file.close()
807 except:
808 # Todo: better error handling? Just print error message for now
809 wx.MessageBox("Error: Could not save file " + self.filePath)
810 return None
812 self.SetTitle(self.filePath)
813 self.isModified = False
814 wx.GetApp().AddFileToHistory(self.filePath)
816 def GetSelectedTextOrLine(self):
817 """Returns selected text if any. If not, returns the current line"""
818 selection = str(self.codeSubWin.GetSelectedTextUTF8())
820 if selection == "":
821 # get current line, return if not at an open '(' bracket
822 currentLine = self.codeSubWin.GetLineUTF8(self.codeSubWin.GetCurrentLine())
823 selection = str(currentLine)
825 # see if the cursor is at a matched bracket
826 x, y = self.codeSubWin.CheckForMatchingBraces()
827 if x != -1 and y != -1:
828 if chr(self.codeSubWin.GetCharAt(x)) == "(":
829 # make sure the open bracket is the first character in the line
830 if currentLine.strip().find('(') == 0:
831 # get all text up to and including the closing bracket
832 selection = str(self.codeSubWin.GetTextRangeUTF8(x, y+1))
834 return selection
836 def LineDown(self):
837 self.codeSubWin.LineDown()
839 def OnOpenFonts(self, event):
840 data = wx.FontData()
841 data.EnableEffects(False)
843 dlg = wx.FontDialog(self, data)
845 if dlg.ShowModal() == wx.ID_OK:
846 data = dlg.GetFontData()
847 font = data.GetChosenFont()
849 self.fontFace = font.GetFaceName()
850 self.fontSize = font.GetPointSize()
851 self.ChangeFont(self.fontFace, self.fontSize)
853 dlg.Destroy()
855 def ChangeFont(self, face, size):
856 self.codeSubWin.StyleClearAll() # Reset all to be like the default
857 self.codeSubWin.StyleSetSpec(stc.STC_STYLE_DEFAULT,
858 "face:%s, size:%d" % (face, size))
860 self.codeSubWin.StyleSetSpec(stc.STC_STYLE_LINENUMBER,
861 "back:#C0C0C0,face:%(helv)s,size:%(size2)d" % faces)
863 self.codeSubWin.StyleSetSpec(
864 stc.STC_STYLE_CONTROLCHAR, "face:%s, size:%d" % (face, size))
866 self.codeSubWin.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
867 "fore:#FFFFFF,back:#00FFFF,bold, face:%s, size:%d" % (face, size))
869 self.codeSubWin.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
870 "fore:#000000,back:#FF3333,bold,face:%s, size:%d" % (face, size))
872 # Default face
873 self.codeSubWin.StyleSetSpec(stc.STC_C_DEFAULT,
874 "fore:#000000,face:%s,size:%d" % (face, size))
875 # Comments
876 self.codeSubWin.StyleSetSpec(stc.STC_C_COMMENTLINE,
877 "fore:#bf0000,face:%s,size:%d" % (face, size))
878 self.codeSubWin.StyleSetSpec(stc.STC_C_COMMENT,
879 "fore:#bf0000,face:%s,size:%d" % (face, size))
880 # Number
881 self.codeSubWin.StyleSetSpec(stc.STC_C_NUMBER,
882 "fore:#333333,face:%s,size:%d" % (face, size))
883 # String
884 self.codeSubWin.StyleSetSpec(stc.STC_C_STRING,
885 "italic,fore:#606060,face:%s,size:%d" % (face, size))
886 # Single quoted string
887 self.codeSubWin.StyleSetSpec(stc.STC_C_CHARACTER,
888 "fore:#007300,face:%s,size:%d" % (face, size))
889 # Keyword
890 self.codeSubWin.StyleSetSpec(stc.STC_C_WORD,
891 "fore:#0000bf,face:%s,size:%d" % (face, size))
892 self.codeSubWin.StyleSetSpec(stc.STC_C_WORD2,
893 "fore:#0000bf,face:%s,size:%d" % (face, size))
894 # Operators
895 self.codeSubWin.StyleSetSpec(stc.STC_C_OPERATOR,
896 "face:%s,size:%d" % (face, size))
897 # Identifiers
898 self.codeSubWin.StyleSetSpec(stc.STC_C_IDENTIFIER,
899 "fore:#000000,face:%s,size:%d" % (face, size))
900 self.codeSubWin.StyleSetSpec(stc.STC_C_UUID,
901 "fore:#000000,face:%s,size:%d" % (face, size))
902 # End of line where string is not closed
903 self.codeSubWin.StyleSetSpec(stc.STC_C_STRINGEOL,
904 "fore:#000000,face:%s,back:#ffffff,eol,size:%d" % (face, size))
906 self.codeSubWin.SetCaretForeground("BLACK")
909 def OnBiggerFont(self, event):
910 self.fontSize += 1
911 self.ChangeFont(self.fontFace, self.fontSize)
913 def OnSmallerFont(self, event):
914 self.fontSize -= 1
915 self.ChangeFont(self.fontFace, self.fontSize)
917 def CreateMenuBar(self):
918 PsycolliderWindow.CreateMenuBar(self)
920 self.showLineNumbers = wx.MenuItem(self.optionsMenu, -1, 'S&how Line Numbers', kind=wx.ITEM_CHECK)
921 self.setTabSize = wx.MenuItem(self.optionsMenu, -1, 'S&et Tab Size')
922 self.optionsMenu.AppendSeparator()
923 self.optionsMenu.AppendItem(self.showLineNumbers)
924 self.optionsMenu.AppendItem(self.setTabSize)
925 self.Bind(wx.EVT_MENU, self.OnShowLineNumbers, id=self.showLineNumbers.GetId())
926 self.Bind(wx.EVT_MENU, self.OnSetTabSize, id=self.setTabSize.GetId())
928 # format menu
929 formatMenu = wx.Menu()
930 self.menubar.Insert(4, formatMenu, "Fo&rmat")
931 self.openFonts = wx.MenuItem(formatMenu, -1, '&Show Fonts\tAlt+T')
932 self.biggerFont = wx.MenuItem(formatMenu, -1, '&Bigger Font\tCtrl++')
933 self.smallerFont = wx.MenuItem(formatMenu, -1, '&Smaller Font\tCtrl+-')
934 formatMenu.AppendItem(self.openFonts)
935 formatMenu.AppendSeparator()
936 formatMenu.AppendItem(self.biggerFont)
937 formatMenu.AppendItem(self.smallerFont)
938 self.Bind(wx.EVT_MENU, self.OnOpenFonts, id=self.openFonts.GetId())
939 self.Bind(wx.EVT_MENU, self.OnBiggerFont, id=self.biggerFont.GetId())
940 self.Bind(wx.EVT_MENU, self.OnSmallerFont, id=self.smallerFont.GetId())
942 # edit menu actions
943 def OnUndo(self, event):
944 self.codeSubWin.Undo()
946 def OnRedo(self, event):
947 self.codeSubWin.Redo()
949 def OnCut(self, event):
950 self.codeSubWin.Cut()
952 def OnCopy(self, event):
953 self.codeSubWin.Copy()
955 def OnPaste(self, event):
956 self.codeSubWin.Paste()
958 def OnDelete(self, event):
959 self.codeSubWin.Clear()
961 def OnSelectAll(self, event):
962 self.codeSubWin.SelectAll()
964 def OnFind(self, event):
965 map = {
966 wx.wxEVT_COMMAND_FIND : "FIND",
967 wx.wxEVT_COMMAND_FIND_NEXT : "FIND_NEXT",
968 wx.wxEVT_COMMAND_FIND_REPLACE : "REPLACE",
969 wx.wxEVT_COMMAND_FIND_REPLACE_ALL : "REPLACE_ALL",
972 et = event.GetEventType()
974 length = self.codeSubWin.GetTextLength()
975 str = event.GetFindString()
977 # find/find next
978 if et == wx.wxEVT_COMMAND_FIND or et == wx.wxEVT_COMMAND_FIND_NEXT:
979 flags = 0
980 if event.GetFlags() & wx.FR_MATCHCASE:
981 flags = flags | stc.STC_FIND_MATCHCASE
982 print "matching case"
984 if event.GetFlags() & wx.FR_WHOLEWORD:
985 flags = flags | stc.STC_FIND_WHOLEWORD
986 print "searching wholeword"
988 if event.GetFlags() & wx.FR_DOWN:
989 if et == wx.wxEVT_COMMAND_FIND_NEXT:
990 startPos = self.codeSubWin.GetCurrentPos()
991 endPos = self.codeSubWin.GetTextLength()
992 else:
993 startPos = 0
994 endPos = self.codeSubWin.GetTextLength()
995 else:
996 if et == wx.wxEVT_COMMAND_FIND_NEXT:
997 startPos = self.codeSubWin.GetCurrentPos()-1
998 endPos = 0
999 else:
1000 startPos = self.codeSubWin.GetTextLength()
1001 endPos = 0
1003 pos = self.codeSubWin.FindText(startPos, endPos, str, flags)
1005 if pos >= 0:
1006 self.codeSubWin.SetSelection(pos, pos+len(str))
1007 self.codeSubWin.EnsureCaretVisible()
1008 else:
1009 wx.MessageBox("Reached end of document", "Find", wx.ICON_EXCLAMATION | wx.OK, self.codeSubWin)
1011 # replace
1012 elif et == wx.wxEVT_COMMAND_FIND_REPLACE:
1013 flags = 0
1014 if event.GetFlags() & wx.FR_MATCHCASE:
1015 flags = flags | stc.STC_FIND_MATCHCASE
1017 if event.GetFlags() & wx.FR_WHOLEWORD:
1018 flags = flags | stc.STC_FIND_WHOLEWORD
1020 startPos = 0
1021 endPos = self.codeSubWin.GetTextLength()
1023 pos = self.codeSubWin.FindText(startPos, endPos, str, flags)
1024 if pos >= 0:
1025 self.codeSubWin.SetSelection(pos, pos+len(str))
1026 self.codeSubWin.ReplaceSelection(event.GetReplaceString())
1027 self.codeSubWin.EnsureCaretVisible()
1028 else:
1029 wx.MessageBox("Reached end of document", "Replace", wx.ICON_EXCLAMATION | wx.OK, self.codeSubWin)
1031 # replace all
1032 elif et == wx.wxEVT_COMMAND_FIND_REPLACE_ALL:
1033 flags = 0
1034 if event.GetFlags() & wx.FR_MATCHCASE:
1035 flags = flags | stc.STC_FIND_MATCHCASE
1037 if event.GetFlags() & wx.FR_WHOLEWORD:
1038 flags = flags | stc.STC_FIND_WHOLEWORD
1040 initPos = self.codeSubWin.GetCurrentPos()
1041 startPos = 0
1042 endPos = self.codeSubWin.GetTextLength()
1044 numTokens = 0
1045 pos = self.codeSubWin.FindText(startPos, endPos, str, flags)
1046 while pos >= 0:
1047 numTokens = numTokens+1
1048 self.codeSubWin.SetSelection(pos, pos+len(str))
1049 self.codeSubWin.ReplaceSelection(event.GetReplaceString())
1050 self.codeSubWin.EnsureCaretVisible()
1051 pos = self.codeSubWin.FindText(pos+len(str), endPos, str, flags)
1053 self.codeSubWin.GotoPos(initPos)
1055 wx.MessageBox("%d instance(s) replaced" % (numTokens), "Replace All", wx.ICON_EXCLAMATION | wx.OK, self.codeSubWin)
1057 # ---------------------------------------------------------------------
1058 # HTML Sub Window
1060 class PsycolliderHTMLSubWin(wx.html.HtmlWindow):
1062 def __init__ (self,parent):
1063 wx.html.HtmlWindow.__init__(self,parent)
1064 self.parent = parent
1065 self.Bind(wx.EVT_CHAR, self.OnChar) # this hack is to enable the alt+. shortcut
1066 self.Bind(html.EVT_HTML_LINK_CLICKED, self.OnClicked)
1067 self.titles = [parent.GetTitle()]
1068 self.titlePos = 0
1070 def OnChar(self, event):
1071 if event.GetKeyCode() == 0x2e and event.AltDown():
1072 self.GetParent().OnStop(None)
1073 elif event.GetKeyCode() == wx.WXK_LEFT and event.AltDown():
1074 self.HistoryBack()
1075 elif event.GetKeyCode() == wx.WXK_RIGHT and event.AltDown():
1076 self.HistoryForward()
1077 else:
1078 event.Skip()
1080 # this allows us to correctly set the title of the parent window
1081 def OnClicked(self, event):
1083 # clicking on a link effectively removes forward history
1084 self.titles = self.titles[:self.titlePos+1]
1086 info = event.GetLinkInfo()
1087 href = info.GetHref()
1088 self.LoadPage(href)
1090 pageTitle = os.path.splitext(os.path.basename(href))[0]
1091 self.parent.SetTitle(pageTitle)
1092 self.titles.append(pageTitle)
1093 self.titlePos += 1
1095 def GoForward(self, event):
1096 if self.HistoryCanForward():
1097 self.HistoryForward()
1098 self.titlePos += 1
1099 self.parent.SetTitle(self.titles[self.titlePos])
1101 def GoBack(self, event):
1102 if self.HistoryCanBack():
1103 self.HistoryBack()
1104 self.titlePos -= 1
1105 self.parent.SetTitle(self.titles[self.titlePos])
1107 def GoHome(self, event):
1108 filePath = os.path.join(gHelpFolder,"Help.html")
1109 self.parent.SetTitle("Help")
1110 self.LoadPage(filePath)
1111 self.titles.append("Help")
1112 self.titlePos += 1
1114 # ---------------------------------------------------------------------
1115 # HTML Window
1116 # accomodates HTML sub window
1117 class PsycolliderHTMLWin(PsycolliderWindow):
1119 def __init__ (self,parent,id,title,pos=wx.DefaultPosition,size=wx.DefaultSize):
1120 PsycolliderWindow.__init__(self, parent, id, title)
1121 self.fileMenu.Remove(self.saveFile.GetId()) # Remove unnecessary menu items
1122 self.fileMenu.Remove(self.saveFileAs.GetId())
1123 self.htmlSubWin = PsycolliderHTMLSubWin(self)
1125 # Remove edit menu
1126 self.menubar.Remove(1)
1128 # Add navigation menu to HTML windows
1129 self.navMenu = wx.Menu()
1130 self.menubar.Insert(3, self.navMenu, "&Navigation")
1131 self.home = wx.MenuItem(self.navMenu, -1, '&Home')
1132 self.forward = wx.MenuItem(self.navMenu, -1, '&Forward\tAlt+Right')
1133 self.back = wx.MenuItem(self.navMenu, -1, '&Back\tAlt+Left')
1135 self.navMenu.AppendItem(self.home)
1136 self.navMenu.AppendItem(self.forward)
1137 self.navMenu.AppendItem(self.back)
1138 self.SetMenuBar(self.menubar)
1140 self.Bind(wx.EVT_MENU, self.htmlSubWin.GoHome, id=self.home.GetId())
1141 self.Bind(wx.EVT_MENU, self.htmlSubWin.GoForward, id=self.forward.GetId())
1142 self.Bind(wx.EVT_MENU, self.htmlSubWin.GoBack, id=self.back.GetId())
1144 def GetSelectedText(self):
1145 return self.htmlSubWin.SelectionToText()
1147 def GetCurLineAsString(self):
1148 posInText = self.htmlSubWin.GetInsertionPoint()
1149 (x,y) = self.htmlSubWin.PositionToXY(posInText)
1150 return self.htmlSubWin.SelectLine(y)
1152 def GetSelectedTextOrLine(self):
1153 """Returns selected text"""
1154 return str(self.GetSelectedText())
1156 def SetSubWinFocus(self):
1157 self.htmlSubWin.SetFocus()
1160 # ---------------------------------------------------------------------
1161 # Psycollider Post Window
1162 class PsycolliderPostWindow(PsycolliderWindow):
1163 log = None # The wx.TextCtrl object that displays post info
1165 def __init__(self, parent, id, title):
1166 # init (no maximise button)
1167 PsycolliderWindow.__init__(self, parent, id, title, wx.MINIMIZE_BOX | wx.CLOSE_BOX |wx.RESIZE_BORDER | wx.SYSTEM_MENU | wx.CAPTION)
1169 self.config.SetPath("/WindowSettings")
1170 sizeX = self.config.ReadInt('PostWindow-sizeX', DEFAULT_SIZEX)
1171 sizeY = self.config.ReadInt('PostWindow-sizeY', DEFAULT_SIZEY)
1172 posX = self.config.ReadInt('PostWindow-posX', DEFAULT_POSX)
1173 posY = self.config.ReadInt('PostWindow-posY', DEFAULT_POSY)
1175 # check that position is > 0
1176 if posX < 0:
1177 posX = DEFAULT_POSX
1178 if posY < 0:
1179 posY = DEFAULT_POSY
1181 self.SetSize(wx.Size(sizeX, sizeY))
1182 self.SetPosition(wx.Point(posX, posY))
1184 self.fileMenu.Remove(self.htmlToCode.GetId()) # Remove unnecessary menu items
1185 self.langMenu.Remove(self.run.GetId())
1186 self.langMenu.Remove(self.stop.GetId())
1187 self.langMenu.Remove(self.openClassDef.GetId())
1188 self.langMenu.Remove(self.impOf.GetId())
1189 self.langMenu.Remove(self.refsTo.GetId())
1190 self.langMenu.Remove(self.eval.GetId())
1191 self.menubar.Remove(1)
1193 mainPanel = wx.Panel(self, -1)
1194 mainSizer = wx.BoxSizer(wx.VERTICAL)
1195 mainPanel.SetSizer(mainSizer)
1197 self.log = wx.TextCtrl(mainPanel, -1, style = wx.TE_MULTILINE | wx.TE_READONLY)
1198 mainSizer.Add(self.log, proportion = 1, flag = wx.EXPAND)
1200 self.Show(True)
1202 def OnCloseWindow(self, event):
1203 dlg = wx.MessageDialog(self, "This will shut down SuperCollider, stop all servers and close all code windows.\n Do you want to quit?")
1204 reply = dlg.ShowModal()
1205 dlg.Destroy()
1206 if reply == wx.ID_OK:
1207 PsycolliderWindow.OnCloseWindow(self, event)
1208 size = self.GetSize()
1209 pos = self.GetPosition()
1210 self.config.SetPath("/WindowSettings")
1211 self.config.WriteInt('PostWindow-sizeX', size.x)
1212 self.config.WriteInt('PostWindow-sizeY', size.y)
1213 self.config.WriteInt('PostWindow-posX', pos.x)
1214 self.config.WriteInt('PostWindow-posY', pos.y)
1216 wx.GetApp().Shutdown()
1217 else:
1218 # No need? wx.MessageBox("Canceled");
1219 pass
1221 def SaveFile(self):
1222 if self.filePath == "":
1223 self.SaveFileAs()
1224 else:
1225 try:
1226 file = open(self.filePath, "w")
1227 content = self.log.GetRange(0, self.log.GetLastPosition())
1228 file.write(content)
1229 file.close()
1230 except:
1231 # Todo: better error handling? Just print error message for now
1232 wx.MessageBox("Error: Could not save file " + filePath)
1234 def SaveFileAs(self):
1235 fileDlg = wx.FileDialog(self,style=wx.SAVE)
1236 if fileDlg.ShowModal() == wx.ID_OK:
1237 self.filePath = fileDlg.GetPath()
1238 try:
1239 file = open(self.filePath ,"w")
1240 content = self.log.GetRange(0, self.log.GetLastPosition())
1241 file.write(content)
1242 file.close()
1243 except:
1244 # Todo: better error handling? Just print error message for now
1245 wx.MessageBox("Error: Could not save file " + self.filePath)
1248 # ---------------------------------------------------------------------
1249 # Psycollider
1250 class Psycollider(wx.App):
1251 theMainFrame = None # Points to the post window object
1252 openWindows = [] # List of all windows currently open
1253 config = None # Main wx.Config object, used by all windows
1254 fileHistory = None # wx.FileHistory object
1256 def OnInit(self):
1257 self.config = wx.Config()
1259 # File history
1260 self.fileHistory = wx.FileHistory()
1261 self.config.SetPath("/RecentFiles")
1262 self.fileHistory.Load(self.config)
1264 # Create post window
1265 self.theMainFrame = PsycolliderPostWindow(None, -1, "SuperCollider (Post Window)")
1266 self.theMainFrame.Show(True)
1267 self.SetTopWindow(self.theMainFrame)
1269 # enable images for html
1270 wx.InitAllImageHandlers()
1272 # Set the log sink function (writes to post window)
1273 # On windows, we can write directly to it, and using the PostText function
1274 # causes the post window to be updated slightly later which doesn't look too nice.
1276 # Can't do this on Linux, as gtk is not thread safe, so must use PostText.
1277 if(os.name == 'nt'):
1278 PySCLang.setSCLogSink(WriteInLogWindow)
1279 else:
1280 PySCLang.setSCLogSink(PostText)
1282 PySCLang.setPyPrOpenWinTextFile(OpenTextFile)
1284 if not self.ChangeDirToSCClassLibraryFolder():
1285 return False
1287 PySCLang.start()
1289 (addr, port) = self.GetServerAddressAndPort()
1291 return True
1293 def doFileHistory(self, event):
1294 """Open a file from file history"""
1295 fileNumber = event.GetId() - wx.ID_FILE1
1296 filename = self.fileHistory.GetHistoryFile(fileNumber)
1297 newWindow = self.OpenFile(filename)
1298 if newWindow != None:
1299 self.openWindows.append(newWindow)
1300 self.AddFileToHistory(filename) # add it back to the history so it will be moved up the list
1302 def GetServerAddressAndPort(self):
1303 return ("127.1.0.1", "57110")
1305 def ChangeDirToSCClassLibraryFolder(self):
1306 # first, we check if the current working folder
1307 # contains an item called 'SCClassLibrary'
1308 curPath = os.getcwd()
1309 listOfFolders = os.listdir(curPath)
1311 # if the cwd contains 'SCClassLibrary', we're done
1312 if 'SCClassLibrary' in listOfFolders:
1313 return True
1315 # uniqueName is what gets returned from config.Read(...)
1316 # if nothing was stored in the config yet
1317 uniqueName = "{1FB0EC09-A883-4684-AD73-1D49A98A89DE}"
1318 self.config.SetPath("/GeneralSettings")
1319 classLibPath = self.config.Read("SCClassLibraryPath", uniqueName)
1320 leafName = (os.path.split(classLibPath))[1]
1322 # if the folder stored in the config is actually an existing
1323 # folder called 'SCClassLibrary', we change cwd to that
1324 # folder and we're done
1325 if os.path.isdir(classLibPath) and leafName == 'SCClassLibrary':
1326 classLibPath_split = os.path.split(classLibPath)
1327 classLibParentFolder = classLibPath_split[0]
1328 os.chdir(classLibParentFolder)
1329 return True
1331 # if something was stored in the config, but does not exist
1332 # anymore or is not correct, let's warn the user
1333 if classLibPath != uniqueName:
1334 wx.MessageBox("The path stored in the application preferences is not a valid SCClassLibrary folder. You will now be requested to select an existing SCClassLibrary folder","Error", wx.OK | wx.ICON_ERROR)
1336 # ask the user to locate the folder
1337 continueLookingForFolder = True
1338 classLibFolderFound = False
1339 while continueLookingForFolder:
1340 dlg = wx.DirDialog(None, "Please locate the SCClassLibrary")
1341 if dlg.ShowModal() == wx.ID_CANCEL:
1342 wx.MessageBox("Sorry. No class library available. SuperCollider will not work correctly","Error", wx.OK | wx.ICON_ERROR)
1343 continueLookingForFolder = False
1344 else:
1345 classLibPath = dlg.GetPath()
1346 leafName = (os.path.split(classLibPath))[1]
1347 if leafName != 'SCClassLibrary':
1348 wx.MessageBox("The folder needs to be called SCClassLibrary for SuperCollider to work correctly", "Error", wx.OK | wx.ICON_ERROR)
1349 else:
1350 continueLookingForFolder = False
1351 classLibFolderFound = True
1353 # only if a valid SCClassLibrary folder was found, then
1354 # set the current folder as its parent
1355 if classLibFolderFound:
1356 self.config.Write("SCClassLibraryPath",classLibPath)
1357 classLibPath_split = os.path.split(classLibPath)
1358 classLibParentFolder = classLibPath_split[0]
1359 os.chdir(classLibParentFolder)
1360 return True
1361 else:
1362 return False
1364 def NewCodeWindow(self):
1365 window = PsycolliderCodeWin(self.theMainFrame, -1, "Untitled %d" % (len(self.openWindows)+1))
1366 self.openWindows.append(window)
1367 window.Show(True)
1368 window.SetSubWinFocus()
1369 return window
1371 def NewHTMLWindow(self, filepath):
1372 window = PsycolliderHTMLWin(self.theMainFrame, -1,
1373 os.path.splitext(os.path.basename(filepath))[0])
1374 self.openWindows.append(window)
1375 window.Show(True)
1376 window.SetSubWinFocus()
1377 return window
1379 def ClosedWindow(self, window):
1380 try:
1381 self.openWindows.remove(window)
1382 except:
1383 pass
1385 def HtmlToCode(self, window):
1386 if type(window) == PsycolliderHTMLWin:
1387 text = window.htmlSubWin.ToText()
1388 window.Destroy()
1389 newWindow = PsycolliderCodeWin(self.theMainFrame, -1, "Untitled %d" % (len(self.openWindows)+1))
1390 self.openWindows.append(newWindow)
1391 newWindow.codeSubWin.AddText(text)
1392 newWindow.Show(True)
1393 newWindow.SetSubWinFocus()
1394 return newWindow
1396 def OpenFile(self, path=''):
1397 if path == '':
1398 fileDlg = wx.FileDialog(self.theMainFrame, style=wx.OPEN)
1399 if not fileDlg.ShowModal() == wx.ID_OK:
1400 return
1402 path = fileDlg.GetPath()
1403 self.AddFileToHistory(path)
1405 try:
1406 file = open(path ,"r")
1407 textContent = file.read()
1408 file.close()
1409 except:
1410 # Todo: better error handling? Just print error message for now
1411 wx.MessageBox("Psycollider Error: Could not open file " + path)
1412 return None
1414 if textContent[0:5] == '{\\rtf':
1415 win = self.NewCodeWindow()
1416 win.codeSubWin.AddTextUTF8('Sorry, still no RTF support, wxRichTextControl does not yet support reading RTF files...')
1417 win.isModified = False
1418 return win
1420 elif (textContent.find('<html') >= 0 or textContent.find('<HTML') >= 0):
1421 win = self.NewHTMLWindow(path)
1422 win.htmlSubWin.LoadPage(path)
1423 return win
1425 else:
1426 # everything else is plain text code window
1427 win = self.NewCodeWindow()
1428 win.codeSubWin.AddTextUTF8(textContent)
1429 win.filePath = path
1430 win.SetTitle(path)
1431 win.isModified = 0
1432 return win
1434 def StopServer(self):
1435 if PySCLang.compiledOK():
1436 PySCLang.setCmdLine('s.sendMsg("/quit");')
1437 PySCLang.sendMain("interpretPrintCmdLine")
1439 def StopSwingOSC(self):
1440 if PySCLang.compiledOK():
1441 PySCLang.setCmdLine('SwingOSC.default.sendMsg("/quit");')
1442 PySCLang.sendMain("interpretPrintCmdLine")
1444 def Run(self):
1445 PySCLang.sendMain("run");
1447 def Stop(self):
1448 PySCLang.sendMain("stop");
1450 def CompileLibrary(self):
1451 self.StopServer()
1452 self.StopSwingOSC()
1453 time.sleep(1)
1454 PySCLang.compileLibrary()
1456 def OpenClassDef(self, text):
1457 PySCLang.setCmdLine(text)
1458 PySCLang.sendMain("openWinCodeFile")
1460 def ImpOf(self, text):
1461 PySCLang.setCmdLine(text)
1462 PySCLang.sendMain("methodTemplates")
1464 def RefsTo(self, text):
1465 PySCLang.setCmdLine(text)
1466 PySCLang.sendMain("methodReferences")
1468 def Eval(self, text):
1469 PySCLang.setCmdLine(text)
1470 PySCLang.sendMain("interpretPrintCmdLine")
1472 def GoToHelpFile(self, sel=""):
1473 # TODO : test this : most help files don't open. remove trailing and leading spaces from sel, too, since wxTextCtrl is behaving strangely
1474 foundFilePath = ""
1475 filePath = ""
1476 if sel == "-" : sel = "subtraction" # "subtraction.rtf"
1477 elif sel == "/" : sel = "division" # "division.rtf"
1478 elif sel == "*" : sel = "multiplication" # from "*.rtf"
1479 elif sel == "**": sel = "exponentiation" # from "**.rtf"
1480 elif sel == "<" : sel = "lessthan" # from "<.rtf"
1481 elif sel == "<=": sel = "lessorequalthan" # from "<=.rtf"
1482 elif sel == ">" : sel = "greaterthan" # from ">.rtf"
1483 elif sel == ">=": sel = "greaterorequalthan" # from ">=.rtf"
1484 elif sel == "%" : sel = "modulo" # from "%.rtf"
1486 if sel != "":
1487 for helpFolder in [gHelpFolder, gUserExtensionFolder]:
1488 for folderPath, foldersInPath, fileNamesInFolder in os.walk(helpFolder):
1489 # don't visit CVS directories
1490 if 'CVS' in foldersInPath:
1491 foldersInPath.remove('CVS')
1492 # don't visit .svn directories
1493 if '.svn' in foldersInPath:
1494 foldersInPath.remove('.svn')
1495 for fileName in fileNamesInFolder:
1496 filePath = os.path.join(folderPath, fileName)
1497 if fileName == sel + ".help.html":
1498 foundFilePath = filePath
1499 break
1500 if fileName == sel + ".html":
1501 foundFilePath = filePath
1502 break
1503 # for fileName
1504 # if file is found, let's break
1505 if foundFilePath != "":
1506 break
1507 # for folderPath, ....
1508 # if file is found, let's break
1509 if foundFilePath != "":
1510 break
1512 if foundFilePath == "":
1513 foundFilePath = os.path.join(gHelpFolder,"Help.html")
1514 self.OpenFile(foundFilePath)
1516 def ClearPostWindow(self):
1517 self.theMainFrame.log.Clear()
1519 def SetDefaultWindowSize(self, sizeX, sizeY):
1520 self.config.SetPath("/WindowSettings")
1521 self.config.WriteInt('DefaultSizeX', sizeX)
1522 self.config.WriteInt('DefaultSizeY', sizeY)
1523 WriteInLogWindow("Set default window size to " + str(sizeX) + " x " + str(sizeY) + "\n")
1525 def ClearRecentFileList(self):
1526 numFiles = self.fileHistory.GetCount()
1527 for i in range(numFiles):
1528 self.fileHistory.RemoveFileFromHistory(0) # remove the first file every time
1530 def AddFileToHistory(self, path):
1531 self.fileHistory.AddFileToHistory(path)
1533 def GetOpenWindows(self):
1534 return self.openWindows
1536 def Shutdown(self):
1537 # Recent file list
1538 self.config.SetPath("/RecentFiles")
1539 self.fileHistory.Save(self.config)
1540 del self.fileHistory
1542 # stop scsynth and swingosc
1543 self.StopServer()
1544 self.StopSwingOSC()
1547 # ---------------------------------------------------------------------
1548 # WriteInLogWindow
1549 def WriteInLogWindow(text):
1550 if wx.GetApp().theMainFrame == None:
1551 sys.stdout.write(text)
1552 else:
1553 wx.GetApp().theMainFrame.log.AppendText(text)
1555 def PostText(text):
1556 wx.CallAfter(WriteInLogWindow, text)
1558 # ---------------------------------------------------------------------
1559 def OpenTextFile(path, rangeStart, rangeSize):
1560 if wx.GetApp().theMainFrame == None:
1561 wx.MessageBox("Cannot open %s since the main window is not created yet","Error",wx.OK | wx.ICON_ERROR)
1562 else:
1563 codeWin = wx.GetApp().OpenFile(path)
1564 #codeWin.SelectRange(rangeStart,rangeSize)
1565 return codeWin
1567 # ---------------------------------------------------------------------
1568 # Main
1569 app = Psycollider(0)
1570 app.MainLoop()