FlatCAMTextEditor.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. from flatcamGUI.GUIElements import *
  2. from PyQt5 import QtPrintSupport
  3. import tkinter as tk
  4. from copy import deepcopy
  5. import sys
  6. import gettext
  7. import FlatCAMTranslation as fcTranslate
  8. import builtins
  9. fcTranslate.apply_language('strings')
  10. if '_' not in builtins.__dict__:
  11. _ = gettext.gettext
  12. class TextEditor(QtWidgets.QWidget):
  13. def __init__(self, app, text=None):
  14. super().__init__()
  15. self.app = app
  16. self.setSizePolicy(
  17. QtWidgets.QSizePolicy.MinimumExpanding,
  18. QtWidgets.QSizePolicy.MinimumExpanding
  19. )
  20. self.main_editor_layout = QtWidgets.QVBoxLayout(self)
  21. self.main_editor_layout.setContentsMargins(0, 0, 0, 0)
  22. self.t_frame = QtWidgets.QFrame()
  23. self.t_frame.setContentsMargins(0, 0, 0, 0)
  24. self.main_editor_layout.addWidget(self.t_frame)
  25. self.work_editor_layout = QtWidgets.QGridLayout(self.t_frame)
  26. self.work_editor_layout.setContentsMargins(2, 2, 2, 2)
  27. self.t_frame.setLayout(self.work_editor_layout)
  28. self.code_editor = FCTextAreaExtended()
  29. stylesheet = """
  30. QTextEdit { selection-background-color:yellow;
  31. selection-color:black;
  32. }
  33. """
  34. self.code_editor.setStyleSheet(stylesheet)
  35. if text:
  36. self.code_editor.setPlainText(text)
  37. self.buttonPreview = QtWidgets.QPushButton(_('Print Preview'))
  38. self.buttonPreview.setToolTip(_("Open a OS standard Preview Print window."))
  39. self.buttonPreview.setMinimumWidth(100)
  40. self.buttonPrint = QtWidgets.QPushButton(_('Print Code'))
  41. self.buttonPrint.setToolTip(_("Open a OS standard Print window."))
  42. self.buttonFind = QtWidgets.QPushButton(_('Find in Code'))
  43. self.buttonFind.setToolTip(_("Will search and highlight in yellow the string in the Find box."))
  44. self.buttonFind.setMinimumWidth(100)
  45. self.entryFind = FCEntry()
  46. self.entryFind.setToolTip(_("Find box. Enter here the strings to be searched in the text."))
  47. self.buttonReplace = QtWidgets.QPushButton(_('Replace With'))
  48. self.buttonReplace.setToolTip(_("Will replace the string from the Find box with the one in the Replace box."))
  49. self.buttonReplace.setMinimumWidth(100)
  50. self.entryReplace = FCEntry()
  51. self.entryReplace.setToolTip(_("String to replace the one in the Find box throughout the text."))
  52. self.sel_all_cb = QtWidgets.QCheckBox(_('All'))
  53. self.sel_all_cb.setToolTip(_("When checked it will replace all instances in the 'Find' box\n"
  54. "with the text in the 'Replace' box.."))
  55. self.button_copy_all = QtWidgets.QPushButton(_('Copy All'))
  56. self.button_copy_all.setToolTip(_("Will copy all the text in the Code Editor to the clipboard."))
  57. self.button_copy_all.setMinimumWidth(100)
  58. self.buttonOpen = QtWidgets.QPushButton(_('Open Code'))
  59. self.buttonOpen.setToolTip(_("Will open a text file in the editor."))
  60. self.buttonSave = QtWidgets.QPushButton(_('Save Code'))
  61. self.buttonSave.setToolTip(_("Will save the text in the editor into a file."))
  62. self.buttonRun = QtWidgets.QPushButton(_('Run Code'))
  63. self.buttonRun.setToolTip(_("Will run the TCL commands found in the text file, one by one."))
  64. self.buttonRun.hide()
  65. self.work_editor_layout.addWidget(self.code_editor, 0, 0, 1, 5)
  66. editor_hlay_1 = QtWidgets.QHBoxLayout()
  67. # cnc_tab_lay_1.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
  68. editor_hlay_1.addWidget(self.buttonFind)
  69. editor_hlay_1.addWidget(self.entryFind)
  70. editor_hlay_1.addWidget(self.buttonReplace)
  71. editor_hlay_1.addWidget(self.entryReplace)
  72. editor_hlay_1.addWidget(self.sel_all_cb)
  73. editor_hlay_1.addWidget(self.button_copy_all)
  74. self.work_editor_layout.addLayout(editor_hlay_1, 1, 0, 1, 5)
  75. editor_hlay_2 = QtWidgets.QHBoxLayout()
  76. editor_hlay_2.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
  77. editor_hlay_2.addWidget(self.buttonPreview)
  78. editor_hlay_2.addWidget(self.buttonPrint)
  79. self.work_editor_layout.addLayout(editor_hlay_2, 2, 0, 1, 1, QtCore.Qt.AlignLeft)
  80. cnc_tab_lay_4 = QtWidgets.QHBoxLayout()
  81. cnc_tab_lay_4.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
  82. cnc_tab_lay_4.addWidget(self.buttonOpen)
  83. cnc_tab_lay_4.addWidget(self.buttonSave)
  84. cnc_tab_lay_4.addWidget(self.buttonRun)
  85. self.work_editor_layout.addLayout(cnc_tab_lay_4, 2, 4, 1, 1)
  86. # #################################################################################
  87. # ################### SIGNALS #####################################################
  88. # #################################################################################
  89. self.code_editor.textChanged.connect(self.handleTextChanged)
  90. self.buttonOpen.clicked.connect(self.handleOpen)
  91. self.buttonSave.clicked.connect(self.handleSaveGCode)
  92. self.buttonPrint.clicked.connect(self.handlePrint)
  93. self.buttonPreview.clicked.connect(self.handlePreview)
  94. self.buttonFind.clicked.connect(self.handleFindGCode)
  95. self.buttonReplace.clicked.connect(self.handleReplaceGCode)
  96. self.button_copy_all.clicked.connect(self.handleCopyAll)
  97. self.code_editor.set_model_data(self.app.myKeywords)
  98. self.gcode_edited = ''
  99. self.script_code = ''
  100. def handlePrint(self):
  101. self.app.report_usage("handlePrint()")
  102. dialog = QtPrintSupport.QPrintDialog()
  103. if dialog.exec_() == QtWidgets.QDialog.Accepted:
  104. self.code_editor.document().print_(dialog.printer())
  105. def handlePreview(self):
  106. self.app.report_usage("handlePreview()")
  107. dialog = QtPrintSupport.QPrintPreviewDialog()
  108. dialog.paintRequested.connect(self.code_editor.print_)
  109. dialog.exec_()
  110. def handleTextChanged(self):
  111. # enable = not self.ui.code_editor.document().isEmpty()
  112. # self.ui.buttonPrint.setEnabled(enable)
  113. # self.ui.buttonPreview.setEnabled(enable)
  114. pass
  115. def handleOpen(self, filt=None):
  116. self.app.report_usage("handleOpen()")
  117. if filt:
  118. _filter_ = filt
  119. else:
  120. _filter_ = "G-Code Files (*.nc);; G-Code Files (*.txt);; G-Code Files (*.tap);; G-Code Files (*.cnc);; " \
  121. "All Files (*.*)"
  122. path, _f = QtWidgets.QFileDialog.getOpenFileName(
  123. caption=_('Open file'), directory=self.app.get_last_folder(), filter=_filter_)
  124. if path:
  125. file = QtCore.QFile(path)
  126. if file.open(QtCore.QIODevice.ReadOnly):
  127. stream = QtCore.QTextStream(file)
  128. self.gcode_edited = stream.readAll()
  129. self.code_editor.setPlainText(self.gcode_edited)
  130. file.close()
  131. def handleSaveGCode(self, name=None, filt=None):
  132. self.app.report_usage("handleSaveGCode()")
  133. if filt:
  134. _filter_ = filt
  135. else:
  136. _filter_ = "G-Code Files (*.nc);; G-Code Files (*.txt);; G-Code Files (*.tap);; G-Code Files (*.cnc);; " \
  137. "All Files (*.*)"
  138. if name:
  139. obj_name = name
  140. else:
  141. try:
  142. obj_name = self.app.collection.get_active().options['name']
  143. except AttributeError:
  144. obj_name = 'file'
  145. if filt is None:
  146. _filter_ = "FlatConfig Files (*.FlatConfig);;All Files (*.*)"
  147. try:
  148. filename = str(QtWidgets.QFileDialog.getSaveFileName(
  149. caption=_("Export G-Code ..."),
  150. directory=self.app.defaults["global_last_folder"] + '/' + str(obj_name),
  151. filter=_filter_
  152. )[0])
  153. except TypeError:
  154. filename = str(QtWidgets.QFileDialog.getSaveFileName(caption=_("Export G-Code ..."), filter=_filter_)[0])
  155. if filename == "":
  156. self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export Code cancelled."))
  157. return
  158. else:
  159. try:
  160. my_gcode = self.code_editor.toPlainText()
  161. with open(filename, 'w') as f:
  162. for line in my_gcode:
  163. f.write(line)
  164. except FileNotFoundError:
  165. self.app.inform.emit('[WARNING] %s' % _("No such file or directory"))
  166. return
  167. except PermissionError:
  168. self.app.inform.emit('[WARNING] %s' %
  169. _("Permission denied, saving not possible.\n"
  170. "Most likely another app is holding the file open and not accessible."))
  171. return
  172. # Just for adding it to the recent files list.
  173. if self.app.defaults["global_open_style"] is False:
  174. self.app.file_opened.emit("cncjob", filename)
  175. self.app.file_saved.emit("cncjob", filename)
  176. self.app.inform.emit('%s: %s' % (_("Saved to"), str(filename)))
  177. def handleFindGCode(self):
  178. self.app.report_usage("handleFindGCode()")
  179. flags = QtGui.QTextDocument.FindCaseSensitively
  180. text_to_be_found = self.entryFind.get_value()
  181. r = self.code_editor.find(str(text_to_be_found), flags)
  182. if r is False:
  183. self.code_editor.moveCursor(QtGui.QTextCursor.Start)
  184. def handleReplaceGCode(self):
  185. self.app.report_usage("handleReplaceGCode()")
  186. old = self.entryFind.get_value()
  187. new = self.entryReplace.get_value()
  188. if self.sel_all_cb.isChecked():
  189. while True:
  190. cursor = self.code_editor.textCursor()
  191. cursor.beginEditBlock()
  192. flags = QtGui.QTextDocument.FindCaseSensitively
  193. # self.ui.editor is the QPlainTextEdit
  194. r = self.code_editor.find(str(old), flags)
  195. if r:
  196. qc = self.code_editor.textCursor()
  197. if qc.hasSelection():
  198. qc.insertText(new)
  199. else:
  200. self.ui.code_editor.moveCursor(QtGui.QTextCursor.Start)
  201. break
  202. # Mark end of undo block
  203. cursor.endEditBlock()
  204. else:
  205. cursor = self.code_editor.textCursor()
  206. cursor.beginEditBlock()
  207. qc = self.code_editor.textCursor()
  208. if qc.hasSelection():
  209. qc.insertText(new)
  210. # Mark end of undo block
  211. cursor.endEditBlock()
  212. def handleCopyAll(self):
  213. text = self.code_editor.toPlainText()
  214. self.app.clipboard.setText(text)
  215. self.app.inform.emit(_("Code Editor content copied to clipboard ..."))
  216. def handleRunCode(self):
  217. # trying to run a Tcl command without having the Shell open will create some warnings because the Tcl Shell
  218. # tries to print on a hidden widget, therefore show the dock if hidden
  219. if self.app.ui.shell_dock.isHidden():
  220. self.app.ui.shell_dock.show()
  221. self.script_code = deepcopy(self.code_editor.toPlainText())
  222. old_line = ''
  223. for tcl_command_line in self.app.script_code.splitlines():
  224. # do not process lines starting with '#' = comment and empty lines
  225. if not tcl_command_line.startswith('#') and tcl_command_line != '':
  226. # id FlatCAM is run in Windows then replace all the slashes with
  227. # the UNIX style slash that TCL understands
  228. if sys.platform == 'win32':
  229. if "open" in tcl_command_line:
  230. tcl_command_line = tcl_command_line.replace('\\', '/')
  231. if old_line != '':
  232. new_command = old_line + tcl_command_line + '\n'
  233. else:
  234. new_command = tcl_command_line
  235. # execute the actual Tcl command
  236. try:
  237. self.app.shell.open_proccessing() # Disables input box.
  238. result = self.app.tcl.eval(str(new_command))
  239. if result != 'None':
  240. self.app.shell.append_output(result + '\n')
  241. old_line = ''
  242. except tk.TclError:
  243. old_line = old_line + tcl_command_line + '\n'
  244. except Exception as e:
  245. log.debug("App.handleRunCode() --> %s" % str(e))
  246. if old_line != '':
  247. # it means that the script finished with an error
  248. result = self.app.tcl.eval("set errorInfo")
  249. log.error("Exec command Exception: %s" % (result + '\n'))
  250. self.app.shell.append_error('ERROR: ' + result + '\n')
  251. self.app.shell.close_proccessing()
  252. def closeEvent(self, QCloseEvent):
  253. try:
  254. self.code_editor.textChanged.disconnect()
  255. except TypeError:
  256. pass
  257. try:
  258. self.buttonOpen.clicked.disconnect()
  259. except TypeError:
  260. pass
  261. try:
  262. self.buttonPrint.clicked.disconnect()
  263. except TypeError:
  264. pass
  265. try:
  266. self.buttonPreview.clicked.disconnect()
  267. except TypeError:
  268. pass
  269. try:
  270. self.buttonFind.clicked.disconnect()
  271. except TypeError:
  272. pass
  273. try:
  274. self.buttonReplace.clicked.disconnect()
  275. except TypeError:
  276. pass
  277. try:
  278. self.button_copy_all.clicked.disconnect()
  279. except TypeError:
  280. pass
  281. super().closeEvent(QCloseEvent)