2021-03-15 21:56:13 +00:00
|
|
|
'''
|
|
|
|
Provides the backend for a basic python editor in KiCad.
|
|
|
|
|
|
|
|
This takes most code from PyShell/PyCrust but adapts it to the KiCad
|
2021-08-15 10:03:13 +00:00
|
|
|
environment where the Python doesn't create a frame but instead hooks
|
2021-03-15 21:56:13 +00:00
|
|
|
into the existing KIWAY_PLAYER
|
|
|
|
|
2021-08-15 10:03:13 +00:00
|
|
|
Original PyCrust code used from
|
2021-03-15 21:56:13 +00:00
|
|
|
https://github.com/wxWidgets/Phoenix/tree/master/wx/py
|
|
|
|
'''
|
|
|
|
|
|
|
|
import wx
|
|
|
|
|
2021-09-06 21:15:40 +00:00
|
|
|
from wx.py import crust, version, dispatcher, editwindow
|
2021-09-08 15:31:41 +00:00
|
|
|
from wx.py.editor import Editor, openSingle, openMultiple, saveSingle, messageDialog
|
2021-03-15 21:56:13 +00:00
|
|
|
from wx.py.buffer import Buffer
|
2021-09-06 21:15:40 +00:00
|
|
|
from wx import stc
|
2021-03-15 21:56:13 +00:00
|
|
|
|
2021-07-30 17:35:04 +00:00
|
|
|
def KiNewId():
|
|
|
|
try:
|
|
|
|
wx.NewIdRef
|
|
|
|
except NameError:
|
|
|
|
return wx.NewId()
|
|
|
|
else:
|
|
|
|
return wx.NewIdRef()
|
|
|
|
|
2021-03-15 21:56:13 +00:00
|
|
|
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
|
2021-07-30 17:35:04 +00:00
|
|
|
ID_EMPTYBUFFER = KiNewId()
|
2021-03-15 21:56:13 +00:00
|
|
|
ID_ABOUT = wx.ID_ABOUT
|
2021-07-30 17:35:04 +00:00
|
|
|
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()
|
2021-03-15 21:56:13 +00:00
|
|
|
ID_FIND = wx.ID_FIND
|
2021-07-30 17:35:04 +00:00
|
|
|
ID_FINDNEXT = KiNewId()
|
|
|
|
ID_FINDPREVIOUS = KiNewId()
|
|
|
|
ID_SHOWTOOLS = KiNewId()
|
|
|
|
ID_HIDEFOLDINGMARGIN = KiNewId()
|
2021-03-15 21:56:13 +00:00
|
|
|
|
|
|
|
INTRO = "KiCad - Python Shell"
|
|
|
|
|
|
|
|
class KiCadPyFrame():
|
2021-08-15 10:03:13 +00:00
|
|
|
|
2021-03-15 21:56:13 +00:00
|
|
|
def __init__(self, parent):
|
|
|
|
"""Create a Frame instance."""
|
2021-08-15 10:03:13 +00:00
|
|
|
|
2021-03-15 21:56:13 +00:00
|
|
|
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)
|
2021-08-15 10:03:13 +00:00
|
|
|
|
2021-03-15 21:56:13 +00:00
|
|
|
self.parent.Bind(wx.EVT_ICONIZE, self.OnIconize)
|
|
|
|
|
|
|
|
|
|
|
|
def OnIconize(self, event):
|
|
|
|
"""Event handler for Iconize."""
|
|
|
|
self.iconized = event.Iconized()
|
|
|
|
|
|
|
|
|
|
|
|
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()
|
2021-08-15 10:03:13 +00:00
|
|
|
|
2021-03-15 21:56:13 +00:00
|
|
|
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)
|
2021-08-15 10:03:13 +00:00
|
|
|
|
2021-03-15 21:56:13 +00:00
|
|
|
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)
|
2021-08-15 10:03:13 +00:00
|
|
|
|
2021-03-15 21:56:13 +00:00
|
|
|
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)
|
2021-08-15 10:03:13 +00:00
|
|
|
|
2021-03-15 21:56:13 +00:00
|
|
|
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.
|
2021-03-19 23:55:40 +00:00
|
|
|
if not self.iconized and not self.parent.IsMaximized():
|
2021-03-15 21:56:13 +00:00
|
|
|
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'):
|
2021-08-15 10:03:13 +00:00
|
|
|
|
2021-03-15 21:56:13 +00:00
|
|
|
"""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)
|
2021-09-21 21:43:51 +00:00
|
|
|
self.parent.Bind( wx.EVT_IDLE, self.OnIdle )
|
|
|
|
self.parent.Bind( wx.EVT_CLOSE, self.OnClose )
|
2021-03-15 21:56:13 +00:00
|
|
|
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
|
|
|
|
|
2021-09-21 22:00:08 +00:00
|
|
|
self.parent.Hide()
|
|
|
|
pass
|
|
|
|
|
2021-03-15 21:56:13 +00:00
|
|
|
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():
|
2021-09-08 15:31:41 +00:00
|
|
|
self.parent.SetStatusText('Namespace updated')
|
2021-03-15 21:56:13 +00:00
|
|
|
else:
|
2021-09-08 15:31:41 +00:00
|
|
|
self.parent.SetStatusText('Error executing, unable to update namespace')
|
2021-03-15 21:56:13 +00:00
|
|
|
|
|
|
|
|
|
|
|
class KiCadEditorNotebookFrame(KiCadEditorFrame):
|
|
|
|
def __init__(self, parent):
|
|
|
|
"""Create EditorNotebookFrame instance."""
|
2021-07-12 23:20:45 +00:00
|
|
|
|
2021-03-15 21:56:13 +00:00
|
|
|
self.notebook = None
|
|
|
|
KiCadEditorFrame.__init__(self, parent)
|
2021-07-12 23:20:45 +00:00
|
|
|
|
2021-03-15 21:56:13 +00:00
|
|
|
if self.notebook:
|
2021-07-12 23:20:45 +00:00
|
|
|
"""Keep pydoc output on stdout instead of pager and
|
|
|
|
place the stdout into the editor window """
|
|
|
|
import pydoc, sys
|
|
|
|
self._keep_stdin = sys.stdin
|
2021-09-21 21:14:20 +00:00
|
|
|
|
|
|
|
"""getline will crash unexpectedly, so we don't support it
|
|
|
|
and bold fonts wreak havoc on our output, so strip them as well"""
|
|
|
|
pydoc.getpager = lambda: pydoc.plainpager
|
|
|
|
pydoc.Helper.getline = lambda self, prompt: None
|
|
|
|
pydoc.TextDoc.use_bold = lambda self, text: text
|
2021-07-12 23:20:45 +00:00
|
|
|
|
2021-03-15 21:56:13 +00:00
|
|
|
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()
|
|
|
|
|
2021-07-12 23:20:45 +00:00
|
|
|
|
2021-03-15 21:56:13 +00:00
|
|
|
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
|
2021-08-15 10:03:13 +00:00
|
|
|
|
2021-03-15 21:56:13 +00:00
|
|
|
|
|
|
|
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()
|
2021-09-06 21:15:40 +00:00
|
|
|
|
|
|
|
|
|
|
|
def is_using_dark_background():
|
|
|
|
"""Backport of wxSystemAppearance::IsUsingDarkBackground() from wxWidgets 3.1."""
|
|
|
|
bg = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)
|
|
|
|
fg = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT)
|
|
|
|
|
|
|
|
return get_luminance(fg) - get_luminance(bg) > 0.2
|
|
|
|
|
|
|
|
|
|
|
|
def get_luminance(color):
|
|
|
|
"""Backport of wxColour::GetLuminance() from wxWidgets 3.1."""
|
|
|
|
return (0.299*color.Red() + 0.587*color.Green() + 0.114*color.Blue()) / 255.0
|
|
|
|
|
|
|
|
|
|
|
|
editwindow_old_init = editwindow.EditWindow.__init__
|
|
|
|
|
|
|
|
calltip_dark_bg_color = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND).ChangeLightness(108)
|
|
|
|
calltip_dark_fg_color = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT).ChangeLightness(108)
|
|
|
|
|
|
|
|
def editwindow_new_init(self, *args, **kwargs):
|
|
|
|
editwindow_old_init(self, *args, **kwargs)
|
|
|
|
|
|
|
|
# Cannot be used until we switch to wxWidgets >=3.1.3.
|
|
|
|
#if wx.SystemSettings.GetAppearance().IsUsingDarkBackground():
|
|
|
|
|
|
|
|
# If a dark theme is detected, we set the default system colors and add our own Python
|
|
|
|
# styles (which are not otherwise supplied by the system).
|
|
|
|
if is_using_dark_background():
|
|
|
|
faces = editwindow.FACES
|
|
|
|
|
|
|
|
# Override the background in all styles by setting the default style, then copying this
|
|
|
|
# to all styles with StyleClearAll(). Iterating over all styles would be slower.
|
|
|
|
self.StyleSetForeground(stc.STC_STYLE_DEFAULT,
|
|
|
|
wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT))
|
|
|
|
self.StyleSetBackground(stc.STC_STYLE_DEFAULT,
|
|
|
|
wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND))
|
|
|
|
self.StyleClearAll()
|
|
|
|
|
|
|
|
self.SetSelForeground(True, wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHTTEXT))
|
|
|
|
self.SetSelBackground(True, wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT))
|
|
|
|
|
|
|
|
# Override the colors set in EditWindow.setStyles(). We go in the same order as in that
|
|
|
|
# function. Most of the foreground colors are just inverses of the default. The background
|
|
|
|
# colors default system background color with slightly adjusted brightness.
|
|
|
|
|
|
|
|
self.StyleSetSpec(stc.STC_STYLE_LINENUMBER,
|
|
|
|
"back:#3F3F3F,face:%(mono)s,size:%(lnsize)d".format(faces))
|
|
|
|
self.StyleSetSpec(stc.STC_STYLE_CONTROLCHAR, "face:%(mono)s".format(faces))
|
|
|
|
|
|
|
|
self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, "fore:#FFFF00")
|
|
|
|
self.StyleSetBackground(stc.STC_STYLE_BRACELIGHT,
|
|
|
|
wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND).ChangeLightness(120))
|
|
|
|
|
|
|
|
self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, "fore:#FF0000")
|
|
|
|
self.StyleSetBackground(stc.STC_STYLE_BRACEBAD,
|
|
|
|
wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND).ChangeLightness(120))
|
|
|
|
|
|
|
|
# Python styles
|
|
|
|
self.StyleSetSpec(stc.STC_P_DEFAULT, "face:%(mono)s".format(faces))
|
|
|
|
self.StyleSetSpec(stc.STC_P_COMMENTLINE,
|
|
|
|
"fore:#FF80FF,face:%(mono)s".format(faces))
|
|
|
|
self.StyleSetSpec(stc.STC_P_NUMBER, "")
|
|
|
|
self.StyleSetSpec(stc.STC_P_STRING, "fore:#80FF80,face:%(mono)s".format(faces))
|
|
|
|
self.StyleSetSpec(stc.STC_P_CHARACTER,
|
|
|
|
"fore:#80FF80,face:%(mono)s".format(faces))
|
|
|
|
self.StyleSetSpec(stc.STC_P_WORD, "fore:#FFFF80,bold")
|
|
|
|
self.StyleSetSpec(stc.STC_P_TRIPLE, "fore:#80FFFF")
|
|
|
|
|
|
|
|
self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, "fore:#FFFFCC")
|
|
|
|
self.StyleSetBackground(stc.STC_P_TRIPLEDOUBLE,
|
|
|
|
wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND).ChangeLightness(105))
|
|
|
|
|
|
|
|
self.StyleSetSpec(stc.STC_P_CLASSNAME, "fore:#FFFF00,bold")
|
|
|
|
self.StyleSetSpec(stc.STC_P_DEFNAME, "fore:#FF8080,bold")
|
|
|
|
self.StyleSetSpec(stc.STC_P_OPERATOR, "")
|
|
|
|
self.StyleSetSpec(stc.STC_P_IDENTIFIER, "")
|
|
|
|
self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, "fore:#808080")
|
|
|
|
self.StyleSetSpec(stc.STC_P_STRINGEOL,
|
|
|
|
"fore:#FFFFFF,face:%(mono)s,back:#1F3F1F,eolfilled".format(faces))
|
|
|
|
|
|
|
|
# Yellow calltip background is too contrasting.
|
|
|
|
self.CallTipSetBackground(calltip_dark_bg_color)
|
|
|
|
self.CallTipSetForeground(calltip_dark_fg_color)
|
|
|
|
|
|
|
|
# Caret is black by default, override this.
|
|
|
|
self.SetCaretForeground(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT))
|
|
|
|
|
|
|
|
|
|
|
|
# HACK: Monkey-patch EditWindow to support dark themes.
|
|
|
|
editwindow.EditWindow.__init__ = editwindow_new_init
|
|
|
|
|
|
|
|
|
|
|
|
calltip_old_init = crust.Calltip.__init__
|
|
|
|
|
|
|
|
def calltip_new_init(self, *args, **kwargs):
|
|
|
|
calltip_old_init(self, *args, **kwargs)
|
|
|
|
|
|
|
|
if is_using_dark_background():
|
|
|
|
self.SetBackgroundColour(calltip_dark_bg_color)
|
|
|
|
self.SetForegroundColour(calltip_dark_fg_color)
|
|
|
|
|
|
|
|
# HACK: Monkey-patch Calltip to support dark themes.
|
|
|
|
crust.Calltip.__init__ = calltip_new_init
|