Seth Hillbrand e87fdf06c1 Wrap GetNewId() for older wxPythons
18.04 and earlier use wxPython 4.0.1 that doesn't have the new Id call.
This wraps the id to use the earlier version if the newer one is not
available

Fixes https://gitlab.com/kicad/code/kicad/issues/8887
2021-07-30 10:36:15 -07:00

1077 lines
40 KiB
Python

'''
Provides the backend for a basic python editor in KiCad.
This takes most code from PyShell/PyCrust but adapts it to the KiCad
environment where the Python doesn't create a frame but instead hooks
into the existing KIWAY_PLAYER
Original PyCrust code used from
https://github.com/wxWidgets/Phoenix/tree/master/wx/py
'''
import wx
from wx.py import crust, version, dispatcher
from wx.py.editor import Editor
from wx.py.buffer import Buffer
def KiNewId():
try:
wx.NewIdRef
except NameError:
return wx.NewId()
else:
return wx.NewIdRef()
ID_NEW = wx.ID_NEW
ID_OPEN = wx.ID_OPEN
ID_REVERT = wx.ID_REVERT
ID_CLOSE = wx.ID_CLOSE
ID_SAVE = wx.ID_SAVE
ID_SAVEAS = wx.ID_SAVEAS
ID_PRINT = wx.ID_PRINT
ID_EXIT = wx.ID_EXIT
ID_UNDO = wx.ID_UNDO
ID_REDO = wx.ID_REDO
ID_CUT = wx.ID_CUT
ID_COPY = wx.ID_COPY
ID_PASTE = wx.ID_PASTE
ID_CLEAR = wx.ID_CLEAR
ID_SELECTALL = wx.ID_SELECTALL
ID_EMPTYBUFFER = KiNewId()
ID_ABOUT = wx.ID_ABOUT
ID_HELP = KiNewId()
ID_AUTOCOMP_SHOW = KiNewId()
ID_AUTOCOMP_MAGIC = KiNewId()
ID_AUTOCOMP_SINGLE = KiNewId()
ID_AUTOCOMP_DOUBLE = KiNewId()
ID_CALLTIPS_SHOW = KiNewId()
ID_CALLTIPS_INSERT = KiNewId()
ID_COPY_PLUS = KiNewId()
ID_NAMESPACE = KiNewId()
ID_PASTE_PLUS = KiNewId()
ID_WRAP = KiNewId()
ID_TOGGLE_MAXIMIZE = KiNewId()
ID_SHOW_LINENUMBERS = KiNewId()
ID_ENABLESHELLMODE = KiNewId()
ID_ENABLEAUTOSYMPY = KiNewId()
ID_AUTO_SAVESETTINGS = KiNewId()
ID_SAVEACOPY = KiNewId()
ID_SAVEHISTORY = KiNewId()
ID_SAVEHISTORYNOW = KiNewId()
ID_CLEARHISTORY = KiNewId()
ID_SAVESETTINGS = KiNewId()
ID_DELSETTINGSFILE = KiNewId()
ID_EDITSTARTUPSCRIPT = KiNewId()
ID_EXECSTARTUPSCRIPT = KiNewId()
ID_SHOWPYSLICESTUTORIAL = KiNewId()
ID_FIND = wx.ID_FIND
ID_FINDNEXT = KiNewId()
ID_FINDPREVIOUS = KiNewId()
ID_SHOWTOOLS = KiNewId()
ID_HIDEFOLDINGMARGIN = KiNewId()
import pcbnew
INTRO = "KiCad - Python Shell"
class KiCadPyFrame():
def __init__(self, parent):
"""Create a Frame instance."""
self.parent = parent
self.parent.CreateStatusBar()
self.parent.SetStatusText('Frame')
self.shellName='KiCad Python Editor'
self.__createMenus()
self.iconized = False
self.findDlg = None
self.findData = wx.FindReplaceData()
self.findData.SetFlags(wx.FR_DOWN)
self.parent.Bind(wx.EVT_CLOSE, self.OnClose)
self.parent.Bind(wx.EVT_ICONIZE, self.OnIconize)
def OnIconize(self, event):
"""Event handler for Iconize."""
self.iconized = event.Iconized()
def OnClose(self, event):
"""Event handler for closing."""
self.parent.Destroy()
def __createMenus(self):
# File Menu
m = self.fileMenu = wx.Menu()
m.Append(ID_NEW, '&New \tCtrl+N',
'New file')
m.Append(ID_OPEN, '&Open... \tCtrl+O',
'Open file')
m.AppendSeparator()
m.Append(ID_REVERT, '&Revert \tCtrl+R',
'Revert to last saved version')
m.Append(ID_CLOSE, '&Close \tCtrl+W',
'Close file')
m.AppendSeparator()
m.Append(ID_SAVE, '&Save... \tCtrl+S',
'Save file')
m.Append(ID_SAVEAS, 'Save &As \tCtrl+Shift+S',
'Save file with new name')
m.AppendSeparator()
m.Append(ID_PRINT, '&Print... \tCtrl+P',
'Print file')
m.AppendSeparator()
m.Append(ID_NAMESPACE, '&Update Namespace \tCtrl+Shift+N',
'Update namespace for autocompletion and calltips')
m.AppendSeparator()
m.Append(ID_EXIT, 'E&xit\tCtrl+Q', 'Exit Program')
# Edit
m = self.editMenu = wx.Menu()
m.Append(ID_UNDO, '&Undo \tCtrl+Z',
'Undo the last action')
m.Append(ID_REDO, '&Redo \tCtrl+Y',
'Redo the last undone action')
m.AppendSeparator()
m.Append(ID_CUT, 'Cu&t \tCtrl+X',
'Cut the selection')
m.Append(ID_COPY, '&Copy \tCtrl+C',
'Copy the selection')
m.Append(ID_COPY_PLUS, 'Cop&y Plus \tCtrl+Shift+C',
'Copy the selection - retaining prompts')
m.Append(ID_PASTE, '&Paste \tCtrl+V', 'Paste from clipboard')
m.Append(ID_PASTE_PLUS, 'Past&e Plus \tCtrl+Shift+V',
'Paste and run commands')
m.AppendSeparator()
m.Append(ID_CLEAR, 'Cle&ar',
'Delete the selection')
m.Append(ID_SELECTALL, 'Select A&ll \tCtrl+A',
'Select all text')
m.AppendSeparator()
m.Append(ID_EMPTYBUFFER, 'E&mpty Buffer...',
'Delete all the contents of the edit buffer')
m.Append(ID_FIND, '&Find Text... \tCtrl+F',
'Search for text in the edit buffer')
m.Append(ID_FINDNEXT, 'Find &Next \tCtrl+G',
'Find next instance of the search text')
m.Append(ID_FINDPREVIOUS, 'Find Pre&vious \tCtrl+Shift+G',
'Find previous instance of the search text')
# View
m = self.viewMenu = wx.Menu()
m.Append(ID_WRAP, '&Wrap Lines\tCtrl+Shift+W',
'Wrap lines at right edge', wx.ITEM_CHECK)
m.Append(ID_SHOW_LINENUMBERS, '&Show Line Numbers\tCtrl+Shift+L',
'Show Line Numbers', wx.ITEM_CHECK)
m.Append(ID_TOGGLE_MAXIMIZE, '&Toggle Maximize\tF11',
'Maximize/Restore Application')
# Options
m = self.optionsMenu = wx.Menu()
self.historyMenu = wx.Menu()
self.historyMenu.Append(ID_SAVEHISTORY, '&Autosave History',
'Automatically save history on close', wx.ITEM_CHECK)
self.historyMenu.Append(ID_SAVEHISTORYNOW, '&Save History Now',
'Save history')
self.historyMenu.Append(ID_CLEARHISTORY, '&Clear History ',
'Clear history')
m.AppendSubMenu(self.historyMenu, "&History", "History Options")
self.startupMenu = wx.Menu()
self.startupMenu.Append(ID_EXECSTARTUPSCRIPT,
'E&xecute Startup Script',
'Execute Startup Script', wx.ITEM_CHECK)
self.startupMenu.Append(ID_EDITSTARTUPSCRIPT,
'&Edit Startup Script...',
'Edit Startup Script')
m.AppendSubMenu(self.startupMenu, '&Startup', 'Startup Options')
self.settingsMenu = wx.Menu()
self.settingsMenu.Append(ID_AUTO_SAVESETTINGS,
'&Auto Save Settings',
'Automatically save settings on close', wx.ITEM_CHECK)
self.settingsMenu.Append(ID_SAVESETTINGS,
'&Save Settings',
'Save settings now')
self.settingsMenu.Append(ID_DELSETTINGSFILE,
'&Revert to default',
'Revert to the default settings')
m.AppendSubMenu(self.settingsMenu, '&Settings', 'Settings Options')
m = self.helpMenu = wx.Menu()
m.Append(ID_HELP, '&Help\tF1', 'Help!')
m.AppendSeparator()
m.Append(ID_ABOUT, '&About...', 'About this program')
b = self.menuBar = wx.MenuBar()
b.Append(self.fileMenu, '&File')
b.Append(self.editMenu, '&Edit')
b.Append(self.viewMenu, '&View')
b.Append(self.optionsMenu, '&Options')
b.Append(self.helpMenu, '&Help')
self.parent.SetMenuBar(b)
self.parent.Bind(wx.EVT_MENU, self.OnFileNew, id=ID_NEW)
self.parent.Bind(wx.EVT_MENU, self.OnFileOpen, id=ID_OPEN)
self.parent.Bind(wx.EVT_MENU, self.OnFileRevert, id=ID_REVERT)
self.parent.Bind(wx.EVT_MENU, self.OnFileClose, id=ID_CLOSE)
self.parent.Bind(wx.EVT_MENU, self.OnFileSave, id=ID_SAVE)
self.parent.Bind(wx.EVT_MENU, self.OnFileSaveAs, id=ID_SAVEAS)
self.parent.Bind(wx.EVT_MENU, self.OnFileSaveACopy, id=ID_SAVEACOPY)
self.parent.Bind(wx.EVT_MENU, self.OnFileUpdateNamespace, id=ID_NAMESPACE)
self.parent.Bind(wx.EVT_MENU, self.OnFilePrint, id=ID_PRINT)
self.parent.Bind(wx.EVT_MENU, self.OnExit, id=ID_EXIT)
self.parent.Bind(wx.EVT_MENU, self.OnUndo, id=ID_UNDO)
self.parent.Bind(wx.EVT_MENU, self.OnRedo, id=ID_REDO)
self.parent.Bind(wx.EVT_MENU, self.OnCut, id=ID_CUT)
self.parent.Bind(wx.EVT_MENU, self.OnCopy, id=ID_COPY)
self.parent.Bind(wx.EVT_MENU, self.OnCopyPlus, id=ID_COPY_PLUS)
self.parent.Bind(wx.EVT_MENU, self.OnPaste, id=ID_PASTE)
self.parent.Bind(wx.EVT_MENU, self.OnPastePlus, id=ID_PASTE_PLUS)
self.parent.Bind(wx.EVT_MENU, self.OnClear, id=ID_CLEAR)
self.parent.Bind(wx.EVT_MENU, self.OnSelectAll, id=ID_SELECTALL)
self.parent.Bind(wx.EVT_MENU, self.OnEmptyBuffer, id=ID_EMPTYBUFFER)
self.parent.Bind(wx.EVT_MENU, self.OnAbout, id=ID_ABOUT)
self.parent.Bind(wx.EVT_MENU, self.OnHelp, id=ID_HELP)
self.parent.Bind(wx.EVT_MENU, self.OnAutoCompleteShow, id=ID_AUTOCOMP_SHOW)
self.parent.Bind(wx.EVT_MENU, self.OnAutoCompleteMagic, id=ID_AUTOCOMP_MAGIC)
self.parent.Bind(wx.EVT_MENU, self.OnAutoCompleteSingle, id=ID_AUTOCOMP_SINGLE)
self.parent.Bind(wx.EVT_MENU, self.OnAutoCompleteDouble, id=ID_AUTOCOMP_DOUBLE)
self.parent.Bind(wx.EVT_MENU, self.OnCallTipsShow, id=ID_CALLTIPS_SHOW)
self.parent.Bind(wx.EVT_MENU, self.OnCallTipsInsert, id=ID_CALLTIPS_INSERT)
self.parent.Bind(wx.EVT_MENU, self.OnWrap, id=ID_WRAP)
self.parent.Bind(wx.EVT_MENU, self.OnToggleMaximize, id=ID_TOGGLE_MAXIMIZE)
self.parent.Bind(wx.EVT_MENU, self.OnShowLineNumbers, id=ID_SHOW_LINENUMBERS)
self.parent.Bind(wx.EVT_MENU, self.OnEnableShellMode, id=ID_ENABLESHELLMODE)
self.parent.Bind(wx.EVT_MENU, self.OnEnableAutoSympy, id=ID_ENABLEAUTOSYMPY)
self.parent.Bind(wx.EVT_MENU, self.OnAutoSaveSettings, id=ID_AUTO_SAVESETTINGS)
self.parent.Bind(wx.EVT_MENU, self.OnSaveHistory, id=ID_SAVEHISTORY)
self.parent.Bind(wx.EVT_MENU, self.OnSaveHistoryNow, id=ID_SAVEHISTORYNOW)
self.parent.Bind(wx.EVT_MENU, self.OnClearHistory, id=ID_CLEARHISTORY)
self.parent.Bind(wx.EVT_MENU, self.OnSaveSettings, id=ID_SAVESETTINGS)
self.parent.Bind(wx.EVT_MENU, self.OnDelSettingsFile, id=ID_DELSETTINGSFILE)
self.parent.Bind(wx.EVT_MENU, self.OnEditStartupScript, id=ID_EDITSTARTUPSCRIPT)
self.parent.Bind(wx.EVT_MENU, self.OnExecStartupScript, id=ID_EXECSTARTUPSCRIPT)
self.parent.Bind(wx.EVT_MENU, self.OnShowPySlicesTutorial, id=ID_SHOWPYSLICESTUTORIAL)
self.parent.Bind(wx.EVT_MENU, self.OnFindText, id=ID_FIND)
self.parent.Bind(wx.EVT_MENU, self.OnFindNext, id=ID_FINDNEXT)
self.parent.Bind(wx.EVT_MENU, self.OnFindPrevious, id=ID_FINDPREVIOUS)
self.parent.Bind(wx.EVT_MENU, self.OnToggleTools, id=ID_SHOWTOOLS)
self.parent.Bind(wx.EVT_MENU, self.OnHideFoldingMargin, id=ID_HIDEFOLDINGMARGIN)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_NEW)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_OPEN)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_REVERT)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_CLOSE)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_SAVE)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_SAVEAS)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_NAMESPACE)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_PRINT)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_UNDO)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_REDO)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_CUT)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_COPY)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_COPY_PLUS)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_PASTE)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_PASTE_PLUS)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_CLEAR)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_SELECTALL)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_EMPTYBUFFER)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_AUTOCOMP_SHOW)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_AUTOCOMP_MAGIC)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_AUTOCOMP_SINGLE)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_AUTOCOMP_DOUBLE)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_CALLTIPS_SHOW)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_CALLTIPS_INSERT)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_WRAP)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_SHOW_LINENUMBERS)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_ENABLESHELLMODE)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_ENABLEAUTOSYMPY)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_AUTO_SAVESETTINGS)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_SAVESETTINGS)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_DELSETTINGSFILE)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_EXECSTARTUPSCRIPT)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_SHOWPYSLICESTUTORIAL)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_SAVEHISTORY)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_SAVEHISTORYNOW)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_CLEARHISTORY)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_EDITSTARTUPSCRIPT)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_FIND)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_FINDNEXT)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_FINDPREVIOUS)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_SHOWTOOLS)
self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_HIDEFOLDINGMARGIN)
self.parent.Bind(wx.EVT_ACTIVATE, self.OnActivate)
self.parent.Bind(wx.EVT_FIND, self.OnFindNext)
self.parent.Bind(wx.EVT_FIND_NEXT, self.OnFindNext)
self.parent.Bind(wx.EVT_FIND_CLOSE, self.OnFindClose)
def OnShowLineNumbers(self, event):
win = wx.Window.FindFocus()
if hasattr(win, 'lineNumbers'):
win.lineNumbers = event.IsChecked()
win.setDisplayLineNumbers(win.lineNumbers)
def OnFileNew(self, event):
self.bufferNew()
def OnFileOpen(self, event):
self.bufferOpen()
def OnFileRevert(self, event):
self.bufferRevert()
def OnFileClose(self, event):
self.bufferClose()
def OnFileSave(self, event):
self.bufferSave()
def OnFileSaveAs(self, event):
self.bufferSaveAs()
def OnFileSaveACopy(self, event):
self.bufferSaveACopy()
def OnFileUpdateNamespace(self, event):
self.updateNamespace()
def OnFilePrint(self, event):
self.bufferPrint()
def OnExit(self, event):
self.parent.Close(False)
def OnUndo(self, event):
win = wx.Window.FindFocus()
win.Undo()
def OnRedo(self, event):
win = wx.Window.FindFocus()
win.Redo()
def OnCut(self, event):
win = wx.Window.FindFocus()
win.Cut()
def OnCopy(self, event):
win = wx.Window.FindFocus()
win.Copy()
def OnCopyPlus(self, event):
win = wx.Window.FindFocus()
win.CopyWithPrompts()
def OnPaste(self, event):
win = wx.Window.FindFocus()
win.Paste()
def OnPastePlus(self, event):
win = wx.Window.FindFocus()
win.PasteAndRun()
def OnClear(self, event):
win = wx.Window.FindFocus()
win.Clear()
def OnEmptyBuffer(self, event):
win = wx.Window.FindFocus()
d = wx.MessageDialog(self,
"Are you sure you want to clear the edit buffer,\n"
"deleting all the text?",
"Empty Buffer", wx.OK | wx.CANCEL | wx.ICON_QUESTION)
answer = d.ShowModal()
d.Destroy()
if (answer == wx.ID_OK):
win.ClearAll()
if hasattr(win,'prompt'):
win.prompt()
def OnSelectAll(self, event):
win = wx.Window.FindFocus()
win.SelectAll()
def OnAbout(self, event):
"""Display an About window."""
title = 'About'
text = 'Your message here.'
dialog = wx.MessageDialog(self.parent, text, title,
wx.OK | wx.ICON_INFORMATION)
dialog.ShowModal()
dialog.Destroy()
def OnHelp(self, event):
"""Display a Help window."""
title = 'Help'
text = "Type 'shell.help()' in the shell window."
dialog = wx.MessageDialog(self.parent, text, title,
wx.OK | wx.ICON_INFORMATION)
dialog.ShowModal()
dialog.Destroy()
def OnAutoCompleteShow(self, event):
win = wx.Window.FindFocus()
win.autoComplete = event.IsChecked()
def OnAutoCompleteMagic(self, event):
win = wx.Window.FindFocus()
win.autoCompleteIncludeMagic = event.IsChecked()
def OnAutoCompleteSingle(self, event):
win = wx.Window.FindFocus()
win.autoCompleteIncludeSingle = event.IsChecked()
def OnAutoCompleteDouble(self, event):
win = wx.Window.FindFocus()
win.autoCompleteIncludeDouble = event.IsChecked()
def OnCallTipsShow(self, event):
win = wx.Window.FindFocus()
win.autoCallTip = event.IsChecked()
def OnCallTipsInsert(self, event):
win = wx.Window.FindFocus()
win.callTipInsert = event.IsChecked()
def OnWrap(self, event):
win = wx.Window.FindFocus()
win.SetWrapMode(event.IsChecked())
wx.CallLater(1, self.shell.EnsureCaretVisible)
def OnToggleMaximize(self, event):
self.parent.Maximize(not self.parent.IsMaximized())
def OnSaveHistory(self, event):
self.autoSaveHistory = event.IsChecked()
def OnSaveHistoryNow(self, event):
self.SaveHistory()
def OnClearHistory(self, event):
self.shell.clearHistory()
def OnEnableShellMode(self, event):
self.enableShellMode = event.IsChecked()
def OnEnableAutoSympy(self, event):
self.enableAutoSympy = event.IsChecked()
def OnHideFoldingMargin(self, event):
self.hideFoldingMargin = event.IsChecked()
def OnAutoSaveSettings(self, event):
self.autoSaveSettings = event.IsChecked()
def OnSaveSettings(self, event):
self.DoSaveSettings()
def OnDelSettingsFile(self, event):
if self.config is not None:
d = wx.MessageDialog(
self.parent, "Do you want to revert to the default settings?\n" +
"A restart is needed for the change to take effect",
"Warning", wx.OK | wx.CANCEL | wx.ICON_QUESTION)
answer = d.ShowModal()
d.Destroy()
if (answer == wx.ID_OK):
self.config.DeleteAll()
self.LoadSettings()
def OnEditStartupScript(self, event):
if hasattr(self, 'EditStartupScript'):
self.EditStartupScript()
def OnExecStartupScript(self, event):
self.execStartupScript = event.IsChecked()
self.SaveSettings(force=True)
def OnShowPySlicesTutorial(self,event):
self.showPySlicesTutorial = event.IsChecked()
self.SaveSettings(force=True)
def OnFindText(self, event):
if self.findDlg is not None:
return
win = wx.Window.FindFocus()
if self.shellName == 'PyCrust':
self.findDlg = wx.FindReplaceDialog(win, self.findData,
"Find",wx.FR_NOWHOLEWORD)
else:
self.findDlg = wx.FindReplaceDialog(win, self.findData,
"Find & Replace", wx.FR_NOWHOLEWORD|wx.FR_REPLACEDIALOG)
self.findDlg.Show()
def OnFindNext(self, event,backward=False):
if backward and (self.findData.GetFlags() & wx.FR_DOWN):
self.findData.SetFlags( self.findData.GetFlags() ^ wx.FR_DOWN )
elif not backward and not (self.findData.GetFlags() & wx.FR_DOWN):
self.findData.SetFlags( self.findData.GetFlags() ^ wx.FR_DOWN )
if not self.findData.GetFindString():
self.OnFindText(event)
return
if isinstance(event, wx.FindDialogEvent):
win = self.findDlg.GetParent()
else:
win = wx.Window.FindFocus()
win.DoFindNext(self.findData, self.findDlg)
if self.findDlg is not None:
self.OnFindClose(None)
def OnFindPrevious(self, event):
self.OnFindNext(event,backward=True)
def OnFindClose(self, event):
self.findDlg.Destroy()
self.findDlg = None
def OnToggleTools(self, event):
self.ToggleTools()
def OnUpdateMenu(self, event):
"""Update menu items based on current status and context."""
win = wx.Window.FindFocus()
id = event.GetId()
event.Enable(True)
try:
if id == ID_NEW:
event.Enable(hasattr(self, 'bufferNew'))
elif id == ID_OPEN:
event.Enable(hasattr(self, 'bufferOpen'))
elif id == ID_REVERT:
event.Enable(hasattr(self, 'bufferRevert')
and self.hasBuffer())
elif id == ID_CLOSE:
event.Enable(hasattr(self, 'bufferClose')
and self.hasBuffer())
elif id == ID_SAVE:
event.Enable(hasattr(self, 'bufferSave')
and self.bufferHasChanged())
elif id == ID_SAVEAS:
event.Enable(hasattr(self, 'bufferSaveAs')
and self.hasBuffer())
elif id == ID_SAVEACOPY:
event.Enable(hasattr(self, 'bufferSaveACopy')
and self.hasBuffer())
elif id == ID_NAMESPACE:
event.Enable(hasattr(self, 'updateNamespace')
and self.hasBuffer())
elif id == ID_PRINT:
event.Enable(hasattr(self, 'bufferPrint')
and self.hasBuffer())
elif id == ID_UNDO:
event.Enable(win.CanUndo())
elif id == ID_REDO:
event.Enable(win.CanRedo())
elif id == ID_CUT:
event.Enable(win.CanCut())
elif id == ID_COPY:
event.Enable(win.CanCopy())
elif id == ID_COPY_PLUS:
event.Enable(win.CanCopy() and hasattr(win, 'CopyWithPrompts'))
elif id == ID_PASTE:
event.Enable(win.CanPaste())
elif id == ID_PASTE_PLUS:
event.Enable(win.CanPaste() and hasattr(win, 'PasteAndRun'))
elif id == ID_CLEAR:
event.Enable(win.CanCut())
elif id == ID_SELECTALL:
event.Enable(hasattr(win, 'SelectAll'))
elif id == ID_EMPTYBUFFER:
event.Enable(hasattr(win, 'ClearAll') and not win.GetReadOnly())
elif id == ID_AUTOCOMP_SHOW:
event.Check(win.autoComplete)
elif id == ID_AUTOCOMP_MAGIC:
event.Check(win.autoCompleteIncludeMagic)
elif id == ID_AUTOCOMP_SINGLE:
event.Check(win.autoCompleteIncludeSingle)
elif id == ID_AUTOCOMP_DOUBLE:
event.Check(win.autoCompleteIncludeDouble)
elif id == ID_CALLTIPS_SHOW:
event.Check(win.autoCallTip)
elif id == ID_CALLTIPS_INSERT:
event.Check(win.callTipInsert)
elif id == ID_WRAP:
event.Check(win.GetWrapMode())
elif id == ID_SHOW_LINENUMBERS:
event.Check(win.lineNumbers)
elif id == ID_ENABLESHELLMODE:
event.Check(self.enableShellMode)
event.Enable(self.config is not None)
elif id == ID_ENABLEAUTOSYMPY:
event.Check(self.enableAutoSympy)
event.Enable(self.config is not None)
elif id == ID_AUTO_SAVESETTINGS:
event.Check(self.autoSaveSettings)
event.Enable(self.config is not None)
elif id == ID_SAVESETTINGS:
event.Enable(self.config is not None and
hasattr(self, 'DoSaveSettings'))
elif id == ID_DELSETTINGSFILE:
event.Enable(self.config is not None)
elif id == ID_EXECSTARTUPSCRIPT:
event.Check(self.execStartupScript)
event.Enable(self.config is not None)
elif id == ID_SAVEHISTORY:
event.Check(self.autoSaveHistory)
event.Enable(self.dataDir is not None)
elif id == ID_SAVEHISTORYNOW:
event.Enable(self.dataDir is not None and
hasattr(self, 'SaveHistory'))
elif id == ID_CLEARHISTORY:
event.Enable(self.dataDir is not None)
elif id == ID_EDITSTARTUPSCRIPT:
event.Enable(hasattr(self, 'EditStartupScript'))
event.Enable(self.dataDir is not None)
elif id == ID_FIND:
event.Enable(hasattr(win, 'DoFindNext'))
elif id == ID_FINDNEXT:
event.Enable(hasattr(win, 'DoFindNext'))
elif id == ID_FINDPREVIOUS:
event.Enable(hasattr(win, 'DoFindNext'))
elif id == ID_SHOWTOOLS:
event.Check(self.ToolsShown())
elif id == ID_HIDEFOLDINGMARGIN:
event.Check(self.hideFoldingMargin)
event.Enable(self.config is not None)
else:
event.Enable(False)
except AttributeError:
# This menu option is not supported in the current context.
event.Enable(False)
def OnActivate(self, event):
"""
Event Handler for losing the focus of the Frame. Should close
Autocomplete listbox, if shown.
"""
if not event.GetActive():
# If autocomplete active, cancel it. Otherwise, the
# autocomplete list will stay visible on top of the
# z-order after switching to another application
win = wx.Window.FindFocus()
if hasattr(win, 'AutoCompActive') and win.AutoCompActive():
win.AutoCompCancel()
event.Skip()
def LoadSettings(self, config):
"""Called by derived classes to load settings specific to the Frame"""
pos = wx.Point(config.ReadInt('Window/PosX', -1),
config.ReadInt('Window/PosY', -1))
size = wx.Size(config.ReadInt('Window/Width', -1),
config.ReadInt('Window/Height', -1))
self.SetSize(size)
self.Move(pos)
def SaveSettings(self, config):
"""Called by derived classes to save Frame settings to a wx.Config object"""
# TODO: track position/size so we can save it even if the
# frame is maximized or iconized.
if not self.iconized and not self.parent.IsMaximized():
w, h = self.GetSize()
config.WriteInt('Window/Width', w)
config.WriteInt('Window/Height', h)
px, py = self.GetPosition()
config.WriteInt('Window/PosX', px)
config.WriteInt('Window/PosY', py)
class KiCadEditorFrame(KiCadPyFrame):
def __init__(self, parent=None, id=-1, title='KiCad Python'):
"""Create EditorFrame instance."""
KiCadPyFrame.__init__(self, parent)
self.buffers = {}
self.buffer = None # Current buffer.
self.editor = None
self._defaultText = title
self._statusText = self._defaultText
self.parent.SetStatusText(self._statusText)
self.parent.Bind(wx.EVT_IDLE, self.OnIdle)
self._setup()
def _setup(self):
"""Setup prior to first buffer creation.
Useful for subclasses."""
pass
def setEditor(self, editor):
self.editor = editor
self.buffer = self.editor.buffer
self.buffers[self.buffer.id] = self.buffer
def OnClose(self, event):
"""Event handler for closing."""
for buffer in self.buffers.values():
self.buffer = buffer
if buffer.hasChanged():
cancel = self.bufferSuggestSave()
if cancel and event.CanVeto():
event.Veto()
return
self.parent.Destroy()
def OnIdle(self, event):
"""Event handler for idle time."""
self._updateStatus()
if hasattr(self, 'notebook'):
self._updateTabText()
self._updateTitle()
event.Skip()
def _updateStatus(self):
"""Show current status information."""
if self.editor and hasattr(self.editor, 'getStatus'):
status = self.editor.getStatus()
text = 'File: %s | Line: %d | Column: %d' % status
else:
text = self._defaultText
if text != self._statusText:
self.parent.SetStatusText(text)
self._statusText = text
def _updateTabText(self):
"""Show current buffer information on notebook tab."""
## suffix = ' **'
## notebook = self.notebook
## selection = notebook.GetSelection()
## if selection == -1:
## return
## text = notebook.GetPageText(selection)
## window = notebook.GetPage(selection)
## if window.editor and window.editor.buffer.hasChanged():
## if text.endswith(suffix):
## pass
## else:
## notebook.SetPageText(selection, text + suffix)
## else:
## if text.endswith(suffix):
## notebook.SetPageText(selection, text[:len(suffix)])
def _updateTitle(self):
"""Show current title information."""
title = self.GetTitle()
if self.bufferHasChanged():
if title.startswith('* '):
pass
else:
self.SetTitle('* ' + title)
else:
if title.startswith('* '):
self.SetTitle(title[2:])
def hasBuffer(self):
"""Return True if there is a current buffer."""
if self.buffer:
return True
else:
return False
def bufferClose(self):
"""Close buffer."""
if self.bufferHasChanged():
cancel = self.bufferSuggestSave()
if cancel:
return cancel
self.bufferDestroy()
cancel = False
return cancel
def bufferCreate(self, filename=None):
"""Create new buffer."""
self.bufferDestroy()
buffer = Buffer()
self.panel = panel = wx.Panel(parent=self, id=-1)
panel.Bind (wx.EVT_ERASE_BACKGROUND, lambda x: x)
editor = Editor(parent=panel)
panel.editor = editor
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(editor.window, 1, wx.EXPAND)
panel.SetSizer(sizer)
panel.SetAutoLayout(True)
sizer.Layout()
buffer.addEditor(editor)
buffer.open(filename)
self.setEditor(editor)
self.editor.setFocus()
self.SendSizeEvent()
def bufferDestroy(self):
"""Destroy the current buffer."""
if self.buffer:
for editor in self.buffer.editors.values():
editor.destroy()
self.editor = None
del self.buffers[self.buffer.id]
self.buffer = None
self.panel.Destroy()
def bufferHasChanged(self):
"""Return True if buffer has changed since last save."""
if self.buffer:
return self.buffer.hasChanged()
else:
return False
def bufferNew(self):
"""Create new buffer."""
if self.bufferHasChanged():
cancel = self.bufferSuggestSave()
if cancel:
return cancel
self.bufferCreate()
cancel = False
return cancel
def bufferOpen(self):
"""Open file in buffer."""
if self.bufferHasChanged():
cancel = self.bufferSuggestSave()
if cancel:
return cancel
filedir = ''
if self.buffer and self.buffer.doc.filedir:
filedir = self.buffer.doc.filedir
result = openSingle(directory=filedir)
if result.path:
self.bufferCreate(result.path)
cancel = False
return cancel
def bufferSave(self):
"""Save buffer to its file."""
if self.buffer.doc.filepath:
self.buffer.save()
cancel = False
else:
cancel = self.bufferSaveAs()
return cancel
def bufferSaveAs(self):
"""Save buffer to a new filename."""
if self.bufferHasChanged() and self.buffer.doc.filepath:
cancel = self.bufferSuggestSave()
if cancel:
return cancel
filedir = ''
if self.buffer and self.buffer.doc.filedir:
filedir = self.buffer.doc.filedir
result = saveSingle(directory=filedir)
if result.path:
self.buffer.saveAs(result.path)
cancel = False
else:
cancel = True
return cancel
def bufferSuggestSave(self):
"""Suggest saving changes. Return True if user selected Cancel."""
result = messageDialog(parent=None,
message='%s has changed.\n'
'Would you like to save it first'
'?' % self.buffer.name,
title='Save current file?')
if result.positive:
cancel = self.bufferSave()
else:
cancel = result.text == 'Cancel'
return cancel
def updateNamespace(self):
"""Update the buffer namespace for autocompletion and calltips."""
if self.buffer.updateNamespace():
self.SetStatusText('Namespace updated')
else:
self.SetStatusText('Error executing, unable to update namespace')
class KiCadEditorNotebookFrame(KiCadEditorFrame):
def __init__(self, parent):
"""Create EditorNotebookFrame instance."""
self.notebook = None
KiCadEditorFrame.__init__(self, parent)
if self.notebook:
"""Keep pydoc output on stdout instead of pager and
place the stdout into the editor window """
import pydoc, sys
self._keep_stdin = sys.stdin
pydoc.pager = pydoc.plainpager
dispatcher.connect(receiver=self._editorChange,
signal='EditorChange', sender=self.notebook)
def _setup(self):
"""Setup prior to first buffer creation.
Called automatically by base class during init."""
self.notebook = EditorNotebook(parent=self)
intro = 'Py %s' % version.VERSION
import imp
module = imp.new_module('__main__')
module.__dict__['__builtins__'] = __builtins__
namespace = module.__dict__.copy()
self.crust = crust.Crust(parent=self.notebook, intro=intro, locals=namespace)
self.shell = self.crust.shell
# Override the filling so that status messages go to the status bar.
self.crust.filling.tree.setStatusText = self.SetStatusText
# Override the shell so that status messages go to the status bar.
self.shell.setStatusText = self.SetStatusText
# Fix a problem with the sash shrinking to nothing.
self.crust.filling.SetSashPosition(200)
self.notebook.AddPage(page=self.crust, text='*Shell*', select=True)
self.setEditor(self.crust.editor)
self.crust.editor.SetFocus()
def _editorChange(self, editor):
"""Editor change signal receiver."""
if not self:
dispatcher.disconnect(receiver=self._editorChange,
signal='EditorChange', sender=self.notebook)
return
self.setEditor(editor)
def _updateTitle(self):
"""Show current title information."""
pass
def bufferCreate(self, filename=None):
"""Create new buffer."""
buffer = Buffer()
panel = wx.Panel(parent=self.notebook, id=-1)
panel.Bind(wx.EVT_ERASE_BACKGROUND, lambda x: x)
editor = Editor(parent=panel)
panel.editor = editor
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(editor.window, 1, wx.EXPAND)
panel.SetSizer(sizer)
panel.SetAutoLayout(True)
sizer.Layout()
buffer.addEditor(editor)
buffer.open(filename)
self.setEditor(editor)
self.notebook.AddPage(page=panel, text=self.buffer.name, select=True)
self.editor.setFocus()
def bufferDestroy(self):
"""Destroy the current buffer."""
selection = self.notebook.GetSelection()
## print("Destroy Selection:", selection)
if selection > 0: # Don't destroy the PyCrust tab.
if self.buffer:
del self.buffers[self.buffer.id]
self.buffer = None # Do this before DeletePage().
self.notebook.DeletePage(selection)
def bufferNew(self):
"""Create new buffer."""
self.bufferCreate()
cancel = False
return cancel
def bufferOpen(self):
"""Open file in buffer."""
filedir = ''
if self.buffer and self.buffer.doc.filedir:
filedir = self.buffer.doc.filedir
result = openMultiple(directory=filedir)
for path in result.paths:
self.bufferCreate(path)
cancel = False
return cancel
class KiCadEditorNotebook(wx.Notebook):
"""A notebook containing a page for each editor."""
def __init__(self, parent):
"""Create EditorNotebook instance."""
wx.Notebook.__init__(self, parent, id=-1, style=wx.CLIP_CHILDREN)
self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGING, self.OnPageChanging, id=self.GetId())
self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.OnPageChanged, id=self.GetId())
self.Bind(wx.EVT_IDLE, self.OnIdle)
def OnIdle(self, event):
"""Event handler for idle time."""
self._updateTabText()
event.Skip()
def _updateTabText(self):
"""Show current buffer display name on all but first tab."""
size = 3
changed = ' **'
unchanged = ' --'
selection = self.GetSelection()
if selection < 1:
return
text = self.GetPageText(selection)
window = self.GetPage(selection)
if not window.editor:
return
if text.endswith(changed) or text.endswith(unchanged):
name = text[:-size]
else:
name = text
if name != window.editor.buffer.name:
text = window.editor.buffer.name
if window.editor.buffer.hasChanged():
if text.endswith(changed):
text = None
elif text.endswith(unchanged):
text = text[:-size] + changed
else:
text += changed
else:
if text.endswith(changed):
text = text[:-size] + unchanged
elif text.endswith(unchanged):
text = None
else:
text += unchanged
if text is not None:
self.SetPageText(selection, text)
self.Refresh() # Needed on Win98.
def OnPageChanging(self, event):
"""Page changing event handler."""
event.Skip()
def OnPageChanged(self, event):
"""Page changed event handler."""
new = event.GetSelection()
window = self.GetPage(new)
dispatcher.send(signal='EditorChange', sender=self,
editor=window.editor)
window.SetFocus()
event.Skip()