You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1059 lines
40 KiB

  1. '''
  2. Provides the backend for a basic python editor in KiCad.
  3. This takes most code from PyShell/PyCrust but adapts it to the KiCad
  4. environment where the Python doesn't create a frame but instead hooks
  5. into the existing KIWAY_PLAYER
  6. Original PyCrust code used from
  7. https://github.com/wxWidgets/Phoenix/tree/master/wx/py
  8. '''
  9. import wx
  10. from wx.py import crust, version, dispatcher
  11. from wx.py.editor import Editor
  12. from wx.py.buffer import Buffer
  13. ID_NEW = wx.ID_NEW
  14. ID_OPEN = wx.ID_OPEN
  15. ID_REVERT = wx.ID_REVERT
  16. ID_CLOSE = wx.ID_CLOSE
  17. ID_SAVE = wx.ID_SAVE
  18. ID_SAVEAS = wx.ID_SAVEAS
  19. ID_PRINT = wx.ID_PRINT
  20. ID_EXIT = wx.ID_EXIT
  21. ID_UNDO = wx.ID_UNDO
  22. ID_REDO = wx.ID_REDO
  23. ID_CUT = wx.ID_CUT
  24. ID_COPY = wx.ID_COPY
  25. ID_PASTE = wx.ID_PASTE
  26. ID_CLEAR = wx.ID_CLEAR
  27. ID_SELECTALL = wx.ID_SELECTALL
  28. ID_EMPTYBUFFER = wx.NewIdRef()
  29. ID_ABOUT = wx.ID_ABOUT
  30. ID_HELP = wx.NewIdRef()
  31. ID_AUTOCOMP_SHOW = wx.NewIdRef()
  32. ID_AUTOCOMP_MAGIC = wx.NewIdRef()
  33. ID_AUTOCOMP_SINGLE = wx.NewIdRef()
  34. ID_AUTOCOMP_DOUBLE = wx.NewIdRef()
  35. ID_CALLTIPS_SHOW = wx.NewIdRef()
  36. ID_CALLTIPS_INSERT = wx.NewIdRef()
  37. ID_COPY_PLUS = wx.NewIdRef()
  38. ID_NAMESPACE = wx.NewIdRef()
  39. ID_PASTE_PLUS = wx.NewIdRef()
  40. ID_WRAP = wx.NewIdRef()
  41. ID_TOGGLE_MAXIMIZE = wx.NewIdRef()
  42. ID_SHOW_LINENUMBERS = wx.NewIdRef()
  43. ID_ENABLESHELLMODE = wx.NewIdRef()
  44. ID_ENABLEAUTOSYMPY = wx.NewIdRef()
  45. ID_AUTO_SAVESETTINGS = wx.NewIdRef()
  46. ID_SAVEACOPY = wx.NewIdRef()
  47. ID_SAVEHISTORY = wx.NewIdRef()
  48. ID_SAVEHISTORYNOW = wx.NewIdRef()
  49. ID_CLEARHISTORY = wx.NewIdRef()
  50. ID_SAVESETTINGS = wx.NewIdRef()
  51. ID_DELSETTINGSFILE = wx.NewIdRef()
  52. ID_EDITSTARTUPSCRIPT = wx.NewIdRef()
  53. ID_EXECSTARTUPSCRIPT = wx.NewIdRef()
  54. ID_SHOWPYSLICESTUTORIAL = wx.NewIdRef()
  55. ID_FIND = wx.ID_FIND
  56. ID_FINDNEXT = wx.NewIdRef()
  57. ID_FINDPREVIOUS = wx.NewIdRef()
  58. ID_SHOWTOOLS = wx.NewIdRef()
  59. ID_HIDEFOLDINGMARGIN = wx.NewIdRef()
  60. import pcbnew
  61. INTRO = "KiCad - Python Shell"
  62. class KiCadPyFrame():
  63. def __init__(self, parent):
  64. """Create a Frame instance."""
  65. self.parent = parent
  66. self.parent.CreateStatusBar()
  67. self.parent.SetStatusText('Frame')
  68. self.shellName='KiCad Python Editor'
  69. self.__createMenus()
  70. self.iconized = False
  71. self.findDlg = None
  72. self.findData = wx.FindReplaceData()
  73. self.findData.SetFlags(wx.FR_DOWN)
  74. self.parent.Bind(wx.EVT_CLOSE, self.OnClose)
  75. self.parent.Bind(wx.EVT_ICONIZE, self.OnIconize)
  76. def OnIconize(self, event):
  77. """Event handler for Iconize."""
  78. self.iconized = event.Iconized()
  79. def OnClose(self, event):
  80. """Event handler for closing."""
  81. self.parent.Destroy()
  82. def __createMenus(self):
  83. # File Menu
  84. m = self.fileMenu = wx.Menu()
  85. m.Append(ID_NEW, '&New \tCtrl+N',
  86. 'New file')
  87. m.Append(ID_OPEN, '&Open... \tCtrl+O',
  88. 'Open file')
  89. m.AppendSeparator()
  90. m.Append(ID_REVERT, '&Revert \tCtrl+R',
  91. 'Revert to last saved version')
  92. m.Append(ID_CLOSE, '&Close \tCtrl+W',
  93. 'Close file')
  94. m.AppendSeparator()
  95. m.Append(ID_SAVE, '&Save... \tCtrl+S',
  96. 'Save file')
  97. m.Append(ID_SAVEAS, 'Save &As \tCtrl+Shift+S',
  98. 'Save file with new name')
  99. m.AppendSeparator()
  100. m.Append(ID_PRINT, '&Print... \tCtrl+P',
  101. 'Print file')
  102. m.AppendSeparator()
  103. m.Append(ID_NAMESPACE, '&Update Namespace \tCtrl+Shift+N',
  104. 'Update namespace for autocompletion and calltips')
  105. m.AppendSeparator()
  106. m.Append(ID_EXIT, 'E&xit\tCtrl+Q', 'Exit Program')
  107. # Edit
  108. m = self.editMenu = wx.Menu()
  109. m.Append(ID_UNDO, '&Undo \tCtrl+Z',
  110. 'Undo the last action')
  111. m.Append(ID_REDO, '&Redo \tCtrl+Y',
  112. 'Redo the last undone action')
  113. m.AppendSeparator()
  114. m.Append(ID_CUT, 'Cu&t \tCtrl+X',
  115. 'Cut the selection')
  116. m.Append(ID_COPY, '&Copy \tCtrl+C',
  117. 'Copy the selection')
  118. m.Append(ID_COPY_PLUS, 'Cop&y Plus \tCtrl+Shift+C',
  119. 'Copy the selection - retaining prompts')
  120. m.Append(ID_PASTE, '&Paste \tCtrl+V', 'Paste from clipboard')
  121. m.Append(ID_PASTE_PLUS, 'Past&e Plus \tCtrl+Shift+V',
  122. 'Paste and run commands')
  123. m.AppendSeparator()
  124. m.Append(ID_CLEAR, 'Cle&ar',
  125. 'Delete the selection')
  126. m.Append(ID_SELECTALL, 'Select A&ll \tCtrl+A',
  127. 'Select all text')
  128. m.AppendSeparator()
  129. m.Append(ID_EMPTYBUFFER, 'E&mpty Buffer...',
  130. 'Delete all the contents of the edit buffer')
  131. m.Append(ID_FIND, '&Find Text... \tCtrl+F',
  132. 'Search for text in the edit buffer')
  133. m.Append(ID_FINDNEXT, 'Find &Next \tCtrl+G',
  134. 'Find next instance of the search text')
  135. m.Append(ID_FINDPREVIOUS, 'Find Pre&vious \tCtrl+Shift+G',
  136. 'Find previous instance of the search text')
  137. # View
  138. m = self.viewMenu = wx.Menu()
  139. m.Append(ID_WRAP, '&Wrap Lines\tCtrl+Shift+W',
  140. 'Wrap lines at right edge', wx.ITEM_CHECK)
  141. m.Append(ID_SHOW_LINENUMBERS, '&Show Line Numbers\tCtrl+Shift+L',
  142. 'Show Line Numbers', wx.ITEM_CHECK)
  143. m.Append(ID_TOGGLE_MAXIMIZE, '&Toggle Maximize\tF11',
  144. 'Maximize/Restore Application')
  145. # Options
  146. m = self.optionsMenu = wx.Menu()
  147. self.historyMenu = wx.Menu()
  148. self.historyMenu.Append(ID_SAVEHISTORY, '&Autosave History',
  149. 'Automatically save history on close', wx.ITEM_CHECK)
  150. self.historyMenu.Append(ID_SAVEHISTORYNOW, '&Save History Now',
  151. 'Save history')
  152. self.historyMenu.Append(ID_CLEARHISTORY, '&Clear History ',
  153. 'Clear history')
  154. m.AppendSubMenu(self.historyMenu, "&History", "History Options")
  155. self.startupMenu = wx.Menu()
  156. self.startupMenu.Append(ID_EXECSTARTUPSCRIPT,
  157. 'E&xecute Startup Script',
  158. 'Execute Startup Script', wx.ITEM_CHECK)
  159. self.startupMenu.Append(ID_EDITSTARTUPSCRIPT,
  160. '&Edit Startup Script...',
  161. 'Edit Startup Script')
  162. m.AppendSubMenu(self.startupMenu, '&Startup', 'Startup Options')
  163. self.settingsMenu = wx.Menu()
  164. self.settingsMenu.Append(ID_AUTO_SAVESETTINGS,
  165. '&Auto Save Settings',
  166. 'Automatically save settings on close', wx.ITEM_CHECK)
  167. self.settingsMenu.Append(ID_SAVESETTINGS,
  168. '&Save Settings',
  169. 'Save settings now')
  170. self.settingsMenu.Append(ID_DELSETTINGSFILE,
  171. '&Revert to default',
  172. 'Revert to the default settings')
  173. m.AppendSubMenu(self.settingsMenu, '&Settings', 'Settings Options')
  174. m = self.helpMenu = wx.Menu()
  175. m.Append(ID_HELP, '&Help\tF1', 'Help!')
  176. m.AppendSeparator()
  177. m.Append(ID_ABOUT, '&About...', 'About this program')
  178. b = self.menuBar = wx.MenuBar()
  179. b.Append(self.fileMenu, '&File')
  180. b.Append(self.editMenu, '&Edit')
  181. b.Append(self.viewMenu, '&View')
  182. b.Append(self.optionsMenu, '&Options')
  183. b.Append(self.helpMenu, '&Help')
  184. self.parent.SetMenuBar(b)
  185. self.parent.Bind(wx.EVT_MENU, self.OnFileNew, id=ID_NEW)
  186. self.parent.Bind(wx.EVT_MENU, self.OnFileOpen, id=ID_OPEN)
  187. self.parent.Bind(wx.EVT_MENU, self.OnFileRevert, id=ID_REVERT)
  188. self.parent.Bind(wx.EVT_MENU, self.OnFileClose, id=ID_CLOSE)
  189. self.parent.Bind(wx.EVT_MENU, self.OnFileSave, id=ID_SAVE)
  190. self.parent.Bind(wx.EVT_MENU, self.OnFileSaveAs, id=ID_SAVEAS)
  191. self.parent.Bind(wx.EVT_MENU, self.OnFileSaveACopy, id=ID_SAVEACOPY)
  192. self.parent.Bind(wx.EVT_MENU, self.OnFileUpdateNamespace, id=ID_NAMESPACE)
  193. self.parent.Bind(wx.EVT_MENU, self.OnFilePrint, id=ID_PRINT)
  194. self.parent.Bind(wx.EVT_MENU, self.OnExit, id=ID_EXIT)
  195. self.parent.Bind(wx.EVT_MENU, self.OnUndo, id=ID_UNDO)
  196. self.parent.Bind(wx.EVT_MENU, self.OnRedo, id=ID_REDO)
  197. self.parent.Bind(wx.EVT_MENU, self.OnCut, id=ID_CUT)
  198. self.parent.Bind(wx.EVT_MENU, self.OnCopy, id=ID_COPY)
  199. self.parent.Bind(wx.EVT_MENU, self.OnCopyPlus, id=ID_COPY_PLUS)
  200. self.parent.Bind(wx.EVT_MENU, self.OnPaste, id=ID_PASTE)
  201. self.parent.Bind(wx.EVT_MENU, self.OnPastePlus, id=ID_PASTE_PLUS)
  202. self.parent.Bind(wx.EVT_MENU, self.OnClear, id=ID_CLEAR)
  203. self.parent.Bind(wx.EVT_MENU, self.OnSelectAll, id=ID_SELECTALL)
  204. self.parent.Bind(wx.EVT_MENU, self.OnEmptyBuffer, id=ID_EMPTYBUFFER)
  205. self.parent.Bind(wx.EVT_MENU, self.OnAbout, id=ID_ABOUT)
  206. self.parent.Bind(wx.EVT_MENU, self.OnHelp, id=ID_HELP)
  207. self.parent.Bind(wx.EVT_MENU, self.OnAutoCompleteShow, id=ID_AUTOCOMP_SHOW)
  208. self.parent.Bind(wx.EVT_MENU, self.OnAutoCompleteMagic, id=ID_AUTOCOMP_MAGIC)
  209. self.parent.Bind(wx.EVT_MENU, self.OnAutoCompleteSingle, id=ID_AUTOCOMP_SINGLE)
  210. self.parent.Bind(wx.EVT_MENU, self.OnAutoCompleteDouble, id=ID_AUTOCOMP_DOUBLE)
  211. self.parent.Bind(wx.EVT_MENU, self.OnCallTipsShow, id=ID_CALLTIPS_SHOW)
  212. self.parent.Bind(wx.EVT_MENU, self.OnCallTipsInsert, id=ID_CALLTIPS_INSERT)
  213. self.parent.Bind(wx.EVT_MENU, self.OnWrap, id=ID_WRAP)
  214. self.parent.Bind(wx.EVT_MENU, self.OnToggleMaximize, id=ID_TOGGLE_MAXIMIZE)
  215. self.parent.Bind(wx.EVT_MENU, self.OnShowLineNumbers, id=ID_SHOW_LINENUMBERS)
  216. self.parent.Bind(wx.EVT_MENU, self.OnEnableShellMode, id=ID_ENABLESHELLMODE)
  217. self.parent.Bind(wx.EVT_MENU, self.OnEnableAutoSympy, id=ID_ENABLEAUTOSYMPY)
  218. self.parent.Bind(wx.EVT_MENU, self.OnAutoSaveSettings, id=ID_AUTO_SAVESETTINGS)
  219. self.parent.Bind(wx.EVT_MENU, self.OnSaveHistory, id=ID_SAVEHISTORY)
  220. self.parent.Bind(wx.EVT_MENU, self.OnSaveHistoryNow, id=ID_SAVEHISTORYNOW)
  221. self.parent.Bind(wx.EVT_MENU, self.OnClearHistory, id=ID_CLEARHISTORY)
  222. self.parent.Bind(wx.EVT_MENU, self.OnSaveSettings, id=ID_SAVESETTINGS)
  223. self.parent.Bind(wx.EVT_MENU, self.OnDelSettingsFile, id=ID_DELSETTINGSFILE)
  224. self.parent.Bind(wx.EVT_MENU, self.OnEditStartupScript, id=ID_EDITSTARTUPSCRIPT)
  225. self.parent.Bind(wx.EVT_MENU, self.OnExecStartupScript, id=ID_EXECSTARTUPSCRIPT)
  226. self.parent.Bind(wx.EVT_MENU, self.OnShowPySlicesTutorial, id=ID_SHOWPYSLICESTUTORIAL)
  227. self.parent.Bind(wx.EVT_MENU, self.OnFindText, id=ID_FIND)
  228. self.parent.Bind(wx.EVT_MENU, self.OnFindNext, id=ID_FINDNEXT)
  229. self.parent.Bind(wx.EVT_MENU, self.OnFindPrevious, id=ID_FINDPREVIOUS)
  230. self.parent.Bind(wx.EVT_MENU, self.OnToggleTools, id=ID_SHOWTOOLS)
  231. self.parent.Bind(wx.EVT_MENU, self.OnHideFoldingMargin, id=ID_HIDEFOLDINGMARGIN)
  232. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_NEW)
  233. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_OPEN)
  234. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_REVERT)
  235. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_CLOSE)
  236. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_SAVE)
  237. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_SAVEAS)
  238. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_NAMESPACE)
  239. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_PRINT)
  240. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_UNDO)
  241. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_REDO)
  242. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_CUT)
  243. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_COPY)
  244. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_COPY_PLUS)
  245. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_PASTE)
  246. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_PASTE_PLUS)
  247. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_CLEAR)
  248. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_SELECTALL)
  249. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_EMPTYBUFFER)
  250. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_AUTOCOMP_SHOW)
  251. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_AUTOCOMP_MAGIC)
  252. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_AUTOCOMP_SINGLE)
  253. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_AUTOCOMP_DOUBLE)
  254. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_CALLTIPS_SHOW)
  255. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_CALLTIPS_INSERT)
  256. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_WRAP)
  257. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_SHOW_LINENUMBERS)
  258. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_ENABLESHELLMODE)
  259. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_ENABLEAUTOSYMPY)
  260. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_AUTO_SAVESETTINGS)
  261. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_SAVESETTINGS)
  262. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_DELSETTINGSFILE)
  263. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_EXECSTARTUPSCRIPT)
  264. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_SHOWPYSLICESTUTORIAL)
  265. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_SAVEHISTORY)
  266. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_SAVEHISTORYNOW)
  267. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_CLEARHISTORY)
  268. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_EDITSTARTUPSCRIPT)
  269. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_FIND)
  270. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_FINDNEXT)
  271. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_FINDPREVIOUS)
  272. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_SHOWTOOLS)
  273. self.parent.Bind(wx.EVT_UPDATE_UI, self.OnUpdateMenu, id=ID_HIDEFOLDINGMARGIN)
  274. self.parent.Bind(wx.EVT_ACTIVATE, self.OnActivate)
  275. self.parent.Bind(wx.EVT_FIND, self.OnFindNext)
  276. self.parent.Bind(wx.EVT_FIND_NEXT, self.OnFindNext)
  277. self.parent.Bind(wx.EVT_FIND_CLOSE, self.OnFindClose)
  278. def OnShowLineNumbers(self, event):
  279. win = wx.Window.FindFocus()
  280. if hasattr(win, 'lineNumbers'):
  281. win.lineNumbers = event.IsChecked()
  282. win.setDisplayLineNumbers(win.lineNumbers)
  283. def OnFileNew(self, event):
  284. self.bufferNew()
  285. def OnFileOpen(self, event):
  286. self.bufferOpen()
  287. def OnFileRevert(self, event):
  288. self.bufferRevert()
  289. def OnFileClose(self, event):
  290. self.bufferClose()
  291. def OnFileSave(self, event):
  292. self.bufferSave()
  293. def OnFileSaveAs(self, event):
  294. self.bufferSaveAs()
  295. def OnFileSaveACopy(self, event):
  296. self.bufferSaveACopy()
  297. def OnFileUpdateNamespace(self, event):
  298. self.updateNamespace()
  299. def OnFilePrint(self, event):
  300. self.bufferPrint()
  301. def OnExit(self, event):
  302. self.parent.Close(False)
  303. def OnUndo(self, event):
  304. win = wx.Window.FindFocus()
  305. win.Undo()
  306. def OnRedo(self, event):
  307. win = wx.Window.FindFocus()
  308. win.Redo()
  309. def OnCut(self, event):
  310. win = wx.Window.FindFocus()
  311. win.Cut()
  312. def OnCopy(self, event):
  313. win = wx.Window.FindFocus()
  314. win.Copy()
  315. def OnCopyPlus(self, event):
  316. win = wx.Window.FindFocus()
  317. win.CopyWithPrompts()
  318. def OnPaste(self, event):
  319. win = wx.Window.FindFocus()
  320. win.Paste()
  321. def OnPastePlus(self, event):
  322. win = wx.Window.FindFocus()
  323. win.PasteAndRun()
  324. def OnClear(self, event):
  325. win = wx.Window.FindFocus()
  326. win.Clear()
  327. def OnEmptyBuffer(self, event):
  328. win = wx.Window.FindFocus()
  329. d = wx.MessageDialog(self,
  330. "Are you sure you want to clear the edit buffer,\n"
  331. "deleting all the text?",
  332. "Empty Buffer", wx.OK | wx.CANCEL | wx.ICON_QUESTION)
  333. answer = d.ShowModal()
  334. d.Destroy()
  335. if (answer == wx.ID_OK):
  336. win.ClearAll()
  337. if hasattr(win,'prompt'):
  338. win.prompt()
  339. def OnSelectAll(self, event):
  340. win = wx.Window.FindFocus()
  341. win.SelectAll()
  342. def OnAbout(self, event):
  343. """Display an About window."""
  344. title = 'About'
  345. text = 'Your message here.'
  346. dialog = wx.MessageDialog(self.parent, text, title,
  347. wx.OK | wx.ICON_INFORMATION)
  348. dialog.ShowModal()
  349. dialog.Destroy()
  350. def OnHelp(self, event):
  351. """Display a Help window."""
  352. title = 'Help'
  353. text = "Type 'shell.help()' in the shell window."
  354. dialog = wx.MessageDialog(self.parent, text, title,
  355. wx.OK | wx.ICON_INFORMATION)
  356. dialog.ShowModal()
  357. dialog.Destroy()
  358. def OnAutoCompleteShow(self, event):
  359. win = wx.Window.FindFocus()
  360. win.autoComplete = event.IsChecked()
  361. def OnAutoCompleteMagic(self, event):
  362. win = wx.Window.FindFocus()
  363. win.autoCompleteIncludeMagic = event.IsChecked()
  364. def OnAutoCompleteSingle(self, event):
  365. win = wx.Window.FindFocus()
  366. win.autoCompleteIncludeSingle = event.IsChecked()
  367. def OnAutoCompleteDouble(self, event):
  368. win = wx.Window.FindFocus()
  369. win.autoCompleteIncludeDouble = event.IsChecked()
  370. def OnCallTipsShow(self, event):
  371. win = wx.Window.FindFocus()
  372. win.autoCallTip = event.IsChecked()
  373. def OnCallTipsInsert(self, event):
  374. win = wx.Window.FindFocus()
  375. win.callTipInsert = event.IsChecked()
  376. def OnWrap(self, event):
  377. win = wx.Window.FindFocus()
  378. win.SetWrapMode(event.IsChecked())
  379. wx.CallLater(1, self.shell.EnsureCaretVisible)
  380. def OnToggleMaximize(self, event):
  381. self.parent.Maximize(not self.parent.IsMaximized())
  382. def OnSaveHistory(self, event):
  383. self.autoSaveHistory = event.IsChecked()
  384. def OnSaveHistoryNow(self, event):
  385. self.SaveHistory()
  386. def OnClearHistory(self, event):
  387. self.shell.clearHistory()
  388. def OnEnableShellMode(self, event):
  389. self.enableShellMode = event.IsChecked()
  390. def OnEnableAutoSympy(self, event):
  391. self.enableAutoSympy = event.IsChecked()
  392. def OnHideFoldingMargin(self, event):
  393. self.hideFoldingMargin = event.IsChecked()
  394. def OnAutoSaveSettings(self, event):
  395. self.autoSaveSettings = event.IsChecked()
  396. def OnSaveSettings(self, event):
  397. self.DoSaveSettings()
  398. def OnDelSettingsFile(self, event):
  399. if self.config is not None:
  400. d = wx.MessageDialog(
  401. self.parent, "Do you want to revert to the default settings?\n" +
  402. "A restart is needed for the change to take effect",
  403. "Warning", wx.OK | wx.CANCEL | wx.ICON_QUESTION)
  404. answer = d.ShowModal()
  405. d.Destroy()
  406. if (answer == wx.ID_OK):
  407. self.config.DeleteAll()
  408. self.LoadSettings()
  409. def OnEditStartupScript(self, event):
  410. if hasattr(self, 'EditStartupScript'):
  411. self.EditStartupScript()
  412. def OnExecStartupScript(self, event):
  413. self.execStartupScript = event.IsChecked()
  414. self.SaveSettings(force=True)
  415. def OnShowPySlicesTutorial(self,event):
  416. self.showPySlicesTutorial = event.IsChecked()
  417. self.SaveSettings(force=True)
  418. def OnFindText(self, event):
  419. if self.findDlg is not None:
  420. return
  421. win = wx.Window.FindFocus()
  422. if self.shellName == 'PyCrust':
  423. self.findDlg = wx.FindReplaceDialog(win, self.findData,
  424. "Find",wx.FR_NOWHOLEWORD)
  425. else:
  426. self.findDlg = wx.FindReplaceDialog(win, self.findData,
  427. "Find & Replace", wx.FR_NOWHOLEWORD|wx.FR_REPLACEDIALOG)
  428. self.findDlg.Show()
  429. def OnFindNext(self, event,backward=False):
  430. if backward and (self.findData.GetFlags() & wx.FR_DOWN):
  431. self.findData.SetFlags( self.findData.GetFlags() ^ wx.FR_DOWN )
  432. elif not backward and not (self.findData.GetFlags() & wx.FR_DOWN):
  433. self.findData.SetFlags( self.findData.GetFlags() ^ wx.FR_DOWN )
  434. if not self.findData.GetFindString():
  435. self.OnFindText(event)
  436. return
  437. if isinstance(event, wx.FindDialogEvent):
  438. win = self.findDlg.GetParent()
  439. else:
  440. win = wx.Window.FindFocus()
  441. win.DoFindNext(self.findData, self.findDlg)
  442. if self.findDlg is not None:
  443. self.OnFindClose(None)
  444. def OnFindPrevious(self, event):
  445. self.OnFindNext(event,backward=True)
  446. def OnFindClose(self, event):
  447. self.findDlg.Destroy()
  448. self.findDlg = None
  449. def OnToggleTools(self, event):
  450. self.ToggleTools()
  451. def OnUpdateMenu(self, event):
  452. """Update menu items based on current status and context."""
  453. win = wx.Window.FindFocus()
  454. id = event.GetId()
  455. event.Enable(True)
  456. try:
  457. if id == ID_NEW:
  458. event.Enable(hasattr(self, 'bufferNew'))
  459. elif id == ID_OPEN:
  460. event.Enable(hasattr(self, 'bufferOpen'))
  461. elif id == ID_REVERT:
  462. event.Enable(hasattr(self, 'bufferRevert')
  463. and self.hasBuffer())
  464. elif id == ID_CLOSE:
  465. event.Enable(hasattr(self, 'bufferClose')
  466. and self.hasBuffer())
  467. elif id == ID_SAVE:
  468. event.Enable(hasattr(self, 'bufferSave')
  469. and self.bufferHasChanged())
  470. elif id == ID_SAVEAS:
  471. event.Enable(hasattr(self, 'bufferSaveAs')
  472. and self.hasBuffer())
  473. elif id == ID_SAVEACOPY:
  474. event.Enable(hasattr(self, 'bufferSaveACopy')
  475. and self.hasBuffer())
  476. elif id == ID_NAMESPACE:
  477. event.Enable(hasattr(self, 'updateNamespace')
  478. and self.hasBuffer())
  479. elif id == ID_PRINT:
  480. event.Enable(hasattr(self, 'bufferPrint')
  481. and self.hasBuffer())
  482. elif id == ID_UNDO:
  483. event.Enable(win.CanUndo())
  484. elif id == ID_REDO:
  485. event.Enable(win.CanRedo())
  486. elif id == ID_CUT:
  487. event.Enable(win.CanCut())
  488. elif id == ID_COPY:
  489. event.Enable(win.CanCopy())
  490. elif id == ID_COPY_PLUS:
  491. event.Enable(win.CanCopy() and hasattr(win, 'CopyWithPrompts'))
  492. elif id == ID_PASTE:
  493. event.Enable(win.CanPaste())
  494. elif id == ID_PASTE_PLUS:
  495. event.Enable(win.CanPaste() and hasattr(win, 'PasteAndRun'))
  496. elif id == ID_CLEAR:
  497. event.Enable(win.CanCut())
  498. elif id == ID_SELECTALL:
  499. event.Enable(hasattr(win, 'SelectAll'))
  500. elif id == ID_EMPTYBUFFER:
  501. event.Enable(hasattr(win, 'ClearAll') and not win.GetReadOnly())
  502. elif id == ID_AUTOCOMP_SHOW:
  503. event.Check(win.autoComplete)
  504. elif id == ID_AUTOCOMP_MAGIC:
  505. event.Check(win.autoCompleteIncludeMagic)
  506. elif id == ID_AUTOCOMP_SINGLE:
  507. event.Check(win.autoCompleteIncludeSingle)
  508. elif id == ID_AUTOCOMP_DOUBLE:
  509. event.Check(win.autoCompleteIncludeDouble)
  510. elif id == ID_CALLTIPS_SHOW:
  511. event.Check(win.autoCallTip)
  512. elif id == ID_CALLTIPS_INSERT:
  513. event.Check(win.callTipInsert)
  514. elif id == ID_WRAP:
  515. event.Check(win.GetWrapMode())
  516. elif id == ID_SHOW_LINENUMBERS:
  517. event.Check(win.lineNumbers)
  518. elif id == ID_ENABLESHELLMODE:
  519. event.Check(self.enableShellMode)
  520. event.Enable(self.config is not None)
  521. elif id == ID_ENABLEAUTOSYMPY:
  522. event.Check(self.enableAutoSympy)
  523. event.Enable(self.config is not None)
  524. elif id == ID_AUTO_SAVESETTINGS:
  525. event.Check(self.autoSaveSettings)
  526. event.Enable(self.config is not None)
  527. elif id == ID_SAVESETTINGS:
  528. event.Enable(self.config is not None and
  529. hasattr(self, 'DoSaveSettings'))
  530. elif id == ID_DELSETTINGSFILE:
  531. event.Enable(self.config is not None)
  532. elif id == ID_EXECSTARTUPSCRIPT:
  533. event.Check(self.execStartupScript)
  534. event.Enable(self.config is not None)
  535. elif id == ID_SAVEHISTORY:
  536. event.Check(self.autoSaveHistory)
  537. event.Enable(self.dataDir is not None)
  538. elif id == ID_SAVEHISTORYNOW:
  539. event.Enable(self.dataDir is not None and
  540. hasattr(self, 'SaveHistory'))
  541. elif id == ID_CLEARHISTORY:
  542. event.Enable(self.dataDir is not None)
  543. elif id == ID_EDITSTARTUPSCRIPT:
  544. event.Enable(hasattr(self, 'EditStartupScript'))
  545. event.Enable(self.dataDir is not None)
  546. elif id == ID_FIND:
  547. event.Enable(hasattr(win, 'DoFindNext'))
  548. elif id == ID_FINDNEXT:
  549. event.Enable(hasattr(win, 'DoFindNext'))
  550. elif id == ID_FINDPREVIOUS:
  551. event.Enable(hasattr(win, 'DoFindNext'))
  552. elif id == ID_SHOWTOOLS:
  553. event.Check(self.ToolsShown())
  554. elif id == ID_HIDEFOLDINGMARGIN:
  555. event.Check(self.hideFoldingMargin)
  556. event.Enable(self.config is not None)
  557. else:
  558. event.Enable(False)
  559. except AttributeError:
  560. # This menu option is not supported in the current context.
  561. event.Enable(False)
  562. def OnActivate(self, event):
  563. """
  564. Event Handler for losing the focus of the Frame. Should close
  565. Autocomplete listbox, if shown.
  566. """
  567. if not event.GetActive():
  568. # If autocomplete active, cancel it. Otherwise, the
  569. # autocomplete list will stay visible on top of the
  570. # z-order after switching to another application
  571. win = wx.Window.FindFocus()
  572. if hasattr(win, 'AutoCompActive') and win.AutoCompActive():
  573. win.AutoCompCancel()
  574. event.Skip()
  575. def LoadSettings(self, config):
  576. """Called by derived classes to load settings specific to the Frame"""
  577. pos = wx.Point(config.ReadInt('Window/PosX', -1),
  578. config.ReadInt('Window/PosY', -1))
  579. size = wx.Size(config.ReadInt('Window/Width', -1),
  580. config.ReadInt('Window/Height', -1))
  581. self.SetSize(size)
  582. self.Move(pos)
  583. def SaveSettings(self, config):
  584. """Called by derived classes to save Frame settings to a wx.Config object"""
  585. # TODO: track position/size so we can save it even if the
  586. # frame is maximized or iconized.
  587. if not self.iconized and not self.parent.IsMaximized():
  588. w, h = self.GetSize()
  589. config.WriteInt('Window/Width', w)
  590. config.WriteInt('Window/Height', h)
  591. px, py = self.GetPosition()
  592. config.WriteInt('Window/PosX', px)
  593. config.WriteInt('Window/PosY', py)
  594. class KiCadEditorFrame(KiCadPyFrame):
  595. def __init__(self, parent=None, id=-1, title='KiCad Python'):
  596. """Create EditorFrame instance."""
  597. KiCadPyFrame.__init__(self, parent)
  598. self.buffers = {}
  599. self.buffer = None # Current buffer.
  600. self.editor = None
  601. self._defaultText = title
  602. self._statusText = self._defaultText
  603. self.parent.SetStatusText(self._statusText)
  604. self.parent.Bind(wx.EVT_IDLE, self.OnIdle)
  605. self._setup()
  606. def _setup(self):
  607. """Setup prior to first buffer creation.
  608. Useful for subclasses."""
  609. pass
  610. def setEditor(self, editor):
  611. self.editor = editor
  612. self.buffer = self.editor.buffer
  613. self.buffers[self.buffer.id] = self.buffer
  614. def OnClose(self, event):
  615. """Event handler for closing."""
  616. for buffer in self.buffers.values():
  617. self.buffer = buffer
  618. if buffer.hasChanged():
  619. cancel = self.bufferSuggestSave()
  620. if cancel and event.CanVeto():
  621. event.Veto()
  622. return
  623. self.parent.Destroy()
  624. def OnIdle(self, event):
  625. """Event handler for idle time."""
  626. self._updateStatus()
  627. if hasattr(self, 'notebook'):
  628. self._updateTabText()
  629. self._updateTitle()
  630. event.Skip()
  631. def _updateStatus(self):
  632. """Show current status information."""
  633. if self.editor and hasattr(self.editor, 'getStatus'):
  634. status = self.editor.getStatus()
  635. text = 'File: %s | Line: %d | Column: %d' % status
  636. else:
  637. text = self._defaultText
  638. if text != self._statusText:
  639. self.parent.SetStatusText(text)
  640. self._statusText = text
  641. def _updateTabText(self):
  642. """Show current buffer information on notebook tab."""
  643. ## suffix = ' **'
  644. ## notebook = self.notebook
  645. ## selection = notebook.GetSelection()
  646. ## if selection == -1:
  647. ## return
  648. ## text = notebook.GetPageText(selection)
  649. ## window = notebook.GetPage(selection)
  650. ## if window.editor and window.editor.buffer.hasChanged():
  651. ## if text.endswith(suffix):
  652. ## pass
  653. ## else:
  654. ## notebook.SetPageText(selection, text + suffix)
  655. ## else:
  656. ## if text.endswith(suffix):
  657. ## notebook.SetPageText(selection, text[:len(suffix)])
  658. def _updateTitle(self):
  659. """Show current title information."""
  660. title = self.GetTitle()
  661. if self.bufferHasChanged():
  662. if title.startswith('* '):
  663. pass
  664. else:
  665. self.SetTitle('* ' + title)
  666. else:
  667. if title.startswith('* '):
  668. self.SetTitle(title[2:])
  669. def hasBuffer(self):
  670. """Return True if there is a current buffer."""
  671. if self.buffer:
  672. return True
  673. else:
  674. return False
  675. def bufferClose(self):
  676. """Close buffer."""
  677. if self.bufferHasChanged():
  678. cancel = self.bufferSuggestSave()
  679. if cancel:
  680. return cancel
  681. self.bufferDestroy()
  682. cancel = False
  683. return cancel
  684. def bufferCreate(self, filename=None):
  685. """Create new buffer."""
  686. self.bufferDestroy()
  687. buffer = Buffer()
  688. self.panel = panel = wx.Panel(parent=self, id=-1)
  689. panel.Bind (wx.EVT_ERASE_BACKGROUND, lambda x: x)
  690. editor = Editor(parent=panel)
  691. panel.editor = editor
  692. sizer = wx.BoxSizer(wx.VERTICAL)
  693. sizer.Add(editor.window, 1, wx.EXPAND)
  694. panel.SetSizer(sizer)
  695. panel.SetAutoLayout(True)
  696. sizer.Layout()
  697. buffer.addEditor(editor)
  698. buffer.open(filename)
  699. self.setEditor(editor)
  700. self.editor.setFocus()
  701. self.SendSizeEvent()
  702. def bufferDestroy(self):
  703. """Destroy the current buffer."""
  704. if self.buffer:
  705. for editor in self.buffer.editors.values():
  706. editor.destroy()
  707. self.editor = None
  708. del self.buffers[self.buffer.id]
  709. self.buffer = None
  710. self.panel.Destroy()
  711. def bufferHasChanged(self):
  712. """Return True if buffer has changed since last save."""
  713. if self.buffer:
  714. return self.buffer.hasChanged()
  715. else:
  716. return False
  717. def bufferNew(self):
  718. """Create new buffer."""
  719. if self.bufferHasChanged():
  720. cancel = self.bufferSuggestSave()
  721. if cancel:
  722. return cancel
  723. self.bufferCreate()
  724. cancel = False
  725. return cancel
  726. def bufferOpen(self):
  727. """Open file in buffer."""
  728. if self.bufferHasChanged():
  729. cancel = self.bufferSuggestSave()
  730. if cancel:
  731. return cancel
  732. filedir = ''
  733. if self.buffer and self.buffer.doc.filedir:
  734. filedir = self.buffer.doc.filedir
  735. result = openSingle(directory=filedir)
  736. if result.path:
  737. self.bufferCreate(result.path)
  738. cancel = False
  739. return cancel
  740. def bufferSave(self):
  741. """Save buffer to its file."""
  742. if self.buffer.doc.filepath:
  743. self.buffer.save()
  744. cancel = False
  745. else:
  746. cancel = self.bufferSaveAs()
  747. return cancel
  748. def bufferSaveAs(self):
  749. """Save buffer to a new filename."""
  750. if self.bufferHasChanged() and self.buffer.doc.filepath:
  751. cancel = self.bufferSuggestSave()
  752. if cancel:
  753. return cancel
  754. filedir = ''
  755. if self.buffer and self.buffer.doc.filedir:
  756. filedir = self.buffer.doc.filedir
  757. result = saveSingle(directory=filedir)
  758. if result.path:
  759. self.buffer.saveAs(result.path)
  760. cancel = False
  761. else:
  762. cancel = True
  763. return cancel
  764. def bufferSuggestSave(self):
  765. """Suggest saving changes. Return True if user selected Cancel."""
  766. result = messageDialog(parent=None,
  767. message='%s has changed.\n'
  768. 'Would you like to save it first'
  769. '?' % self.buffer.name,
  770. title='Save current file?')
  771. if result.positive:
  772. cancel = self.bufferSave()
  773. else:
  774. cancel = result.text == 'Cancel'
  775. return cancel
  776. def updateNamespace(self):
  777. """Update the buffer namespace for autocompletion and calltips."""
  778. if self.buffer.updateNamespace():
  779. self.SetStatusText('Namespace updated')
  780. else:
  781. self.SetStatusText('Error executing, unable to update namespace')
  782. class KiCadEditorNotebookFrame(KiCadEditorFrame):
  783. def __init__(self, parent):
  784. """Create EditorNotebookFrame instance."""
  785. self.notebook = None
  786. KiCadEditorFrame.__init__(self, parent)
  787. if self.notebook:
  788. dispatcher.connect(receiver=self._editorChange,
  789. signal='EditorChange', sender=self.notebook)
  790. def _setup(self):
  791. """Setup prior to first buffer creation.
  792. Called automatically by base class during init."""
  793. self.notebook = EditorNotebook(parent=self)
  794. intro = 'Py %s' % version.VERSION
  795. import imp
  796. module = imp.new_module('__main__')
  797. module.__dict__['__builtins__'] = __builtins__
  798. namespace = module.__dict__.copy()
  799. self.crust = crust.Crust(parent=self.notebook, intro=intro, locals=namespace)
  800. self.shell = self.crust.shell
  801. # Override the filling so that status messages go to the status bar.
  802. self.crust.filling.tree.setStatusText = self.SetStatusText
  803. # Override the shell so that status messages go to the status bar.
  804. self.shell.setStatusText = self.SetStatusText
  805. # Fix a problem with the sash shrinking to nothing.
  806. self.crust.filling.SetSashPosition(200)
  807. self.notebook.AddPage(page=self.crust, text='*Shell*', select=True)
  808. self.setEditor(self.crust.editor)
  809. self.crust.editor.SetFocus()
  810. def _editorChange(self, editor):
  811. """Editor change signal receiver."""
  812. if not self:
  813. dispatcher.disconnect(receiver=self._editorChange,
  814. signal='EditorChange', sender=self.notebook)
  815. return
  816. self.setEditor(editor)
  817. def _updateTitle(self):
  818. """Show current title information."""
  819. pass
  820. def bufferCreate(self, filename=None):
  821. """Create new buffer."""
  822. buffer = Buffer()
  823. panel = wx.Panel(parent=self.notebook, id=-1)
  824. panel.Bind(wx.EVT_ERASE_BACKGROUND, lambda x: x)
  825. editor = Editor(parent=panel)
  826. panel.editor = editor
  827. sizer = wx.BoxSizer(wx.VERTICAL)
  828. sizer.Add(editor.window, 1, wx.EXPAND)
  829. panel.SetSizer(sizer)
  830. panel.SetAutoLayout(True)
  831. sizer.Layout()
  832. buffer.addEditor(editor)
  833. buffer.open(filename)
  834. self.setEditor(editor)
  835. self.notebook.AddPage(page=panel, text=self.buffer.name, select=True)
  836. self.editor.setFocus()
  837. def bufferDestroy(self):
  838. """Destroy the current buffer."""
  839. selection = self.notebook.GetSelection()
  840. ## print("Destroy Selection:", selection)
  841. if selection > 0: # Don't destroy the PyCrust tab.
  842. if self.buffer:
  843. del self.buffers[self.buffer.id]
  844. self.buffer = None # Do this before DeletePage().
  845. self.notebook.DeletePage(selection)
  846. def bufferNew(self):
  847. """Create new buffer."""
  848. self.bufferCreate()
  849. cancel = False
  850. return cancel
  851. def bufferOpen(self):
  852. """Open file in buffer."""
  853. filedir = ''
  854. if self.buffer and self.buffer.doc.filedir:
  855. filedir = self.buffer.doc.filedir
  856. result = openMultiple(directory=filedir)
  857. for path in result.paths:
  858. self.bufferCreate(path)
  859. cancel = False
  860. return cancel
  861. class KiCadEditorNotebook(wx.Notebook):
  862. """A notebook containing a page for each editor."""
  863. def __init__(self, parent):
  864. """Create EditorNotebook instance."""
  865. wx.Notebook.__init__(self, parent, id=-1, style=wx.CLIP_CHILDREN)
  866. self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGING, self.OnPageChanging, id=self.GetId())
  867. self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.OnPageChanged, id=self.GetId())
  868. self.Bind(wx.EVT_IDLE, self.OnIdle)
  869. def OnIdle(self, event):
  870. """Event handler for idle time."""
  871. self._updateTabText()
  872. event.Skip()
  873. def _updateTabText(self):
  874. """Show current buffer display name on all but first tab."""
  875. size = 3
  876. changed = ' **'
  877. unchanged = ' --'
  878. selection = self.GetSelection()
  879. if selection < 1:
  880. return
  881. text = self.GetPageText(selection)
  882. window = self.GetPage(selection)
  883. if not window.editor:
  884. return
  885. if text.endswith(changed) or text.endswith(unchanged):
  886. name = text[:-size]
  887. else:
  888. name = text
  889. if name != window.editor.buffer.name:
  890. text = window.editor.buffer.name
  891. if window.editor.buffer.hasChanged():
  892. if text.endswith(changed):
  893. text = None
  894. elif text.endswith(unchanged):
  895. text = text[:-size] + changed
  896. else:
  897. text += changed
  898. else:
  899. if text.endswith(changed):
  900. text = text[:-size] + unchanged
  901. elif text.endswith(unchanged):
  902. text = None
  903. else:
  904. text += unchanged
  905. if text is not None:
  906. self.SetPageText(selection, text)
  907. self.Refresh() # Needed on Win98.
  908. def OnPageChanging(self, event):
  909. """Page changing event handler."""
  910. event.Skip()
  911. def OnPageChanged(self, event):
  912. """Page changed event handler."""
  913. new = event.GetSelection()
  914. window = self.GetPage(new)
  915. dispatcher.send(signal='EditorChange', sender=self,
  916. editor=window.editor)
  917. window.SetFocus()
  918. event.Skip()