FlatCAMTextEditor.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. # ##########################################################
  2. # FlatCAM: 2D Post-processing for Manufacturing #
  3. # File Author: Marius Adrian Stanciu (c) #
  4. # Date: 10/10/2019 #
  5. # MIT Licence #
  6. # ##########################################################
  7. from AppGUI.GUIElements import FCFileSaveDialog, FCEntry, FCTextAreaExtended, FCTextAreaLineNumber
  8. from PyQt5 import QtPrintSupport, QtWidgets, QtCore, QtGui
  9. from reportlab.platypus import SimpleDocTemplate, Paragraph
  10. from reportlab.lib.styles import getSampleStyleSheet
  11. from reportlab.lib.units import inch, mm
  12. # from io import StringIO
  13. import gettext
  14. import AppTranslation as fcTranslate
  15. import builtins
  16. fcTranslate.apply_language('strings')
  17. if '_' not in builtins.__dict__:
  18. _ = gettext.gettext
  19. class TextEditor(QtWidgets.QWidget):
  20. def __init__(self, app, text=None, plain_text=None, parent=None):
  21. super().__init__(parent=parent)
  22. self.app = app
  23. self.plain_text = plain_text
  24. self.setSizePolicy(
  25. QtWidgets.QSizePolicy.MinimumExpanding,
  26. QtWidgets.QSizePolicy.MinimumExpanding
  27. )
  28. self.main_editor_layout = QtWidgets.QVBoxLayout(self)
  29. self.main_editor_layout.setContentsMargins(0, 0, 0, 0)
  30. self.t_frame = QtWidgets.QFrame()
  31. self.t_frame.setContentsMargins(0, 0, 0, 0)
  32. self.main_editor_layout.addWidget(self.t_frame)
  33. self.work_editor_layout = QtWidgets.QGridLayout(self.t_frame)
  34. self.work_editor_layout.setContentsMargins(2, 2, 2, 2)
  35. self.t_frame.setLayout(self.work_editor_layout)
  36. if self.plain_text:
  37. self.editor_class = FCTextAreaLineNumber()
  38. self.code_editor = self.editor_class.edit
  39. stylesheet = """
  40. QPlainTextEdit { selection-background-color:yellow;
  41. selection-color:black;
  42. }
  43. """
  44. self.work_editor_layout.addWidget(self.editor_class, 0, 0, 1, 5)
  45. else:
  46. self.code_editor = FCTextAreaExtended()
  47. stylesheet = """
  48. QTextEdit { selection-background-color:yellow;
  49. selection-color:black;
  50. }
  51. """
  52. self.work_editor_layout.addWidget(self.code_editor, 0, 0, 1, 5)
  53. self.code_editor.setStyleSheet(stylesheet)
  54. if text:
  55. self.code_editor.setPlainText(text)
  56. self.buttonPreview = QtWidgets.QPushButton(_('Print Preview'))
  57. self.buttonPreview.setToolTip(_("Open a OS standard Preview Print window."))
  58. self.buttonPreview.setMinimumWidth(100)
  59. self.buttonPrint = QtWidgets.QPushButton(_('Print Code'))
  60. self.buttonPrint.setToolTip(_("Open a OS standard Print window."))
  61. self.buttonFind = QtWidgets.QPushButton(_('Find in Code'))
  62. self.buttonFind.setToolTip(_("Will search and highlight in yellow the string in the Find box."))
  63. self.buttonFind.setMinimumWidth(100)
  64. self.entryFind = FCEntry()
  65. self.entryFind.setToolTip(_("Find box. Enter here the strings to be searched in the text."))
  66. self.buttonReplace = QtWidgets.QPushButton(_('Replace With'))
  67. self.buttonReplace.setToolTip(_("Will replace the string from the Find box with the one in the Replace box."))
  68. self.buttonReplace.setMinimumWidth(100)
  69. self.entryReplace = FCEntry()
  70. self.entryReplace.setToolTip(_("String to replace the one in the Find box throughout the text."))
  71. self.sel_all_cb = QtWidgets.QCheckBox(_('All'))
  72. self.sel_all_cb.setToolTip(_("When checked it will replace all instances in the 'Find' box\n"
  73. "with the text in the 'Replace' box.."))
  74. self.button_copy_all = QtWidgets.QPushButton(_('Copy All'))
  75. self.button_copy_all.setToolTip(_("Will copy all the text in the Code Editor to the clipboard."))
  76. self.button_copy_all.setMinimumWidth(100)
  77. self.buttonOpen = QtWidgets.QPushButton(_('Open Code'))
  78. self.buttonOpen.setToolTip(_("Will open a text file in the editor."))
  79. self.buttonSave = QtWidgets.QPushButton(_('Save Code'))
  80. self.buttonSave.setToolTip(_("Will save the text in the editor into a file."))
  81. self.buttonRun = QtWidgets.QPushButton(_('Run Code'))
  82. self.buttonRun.setToolTip(_("Will run the TCL commands found in the text file, one by one."))
  83. self.buttonRun.hide()
  84. editor_hlay_1 = QtWidgets.QHBoxLayout()
  85. # cnc_tab_lay_1.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
  86. editor_hlay_1.addWidget(self.buttonFind)
  87. editor_hlay_1.addWidget(self.entryFind)
  88. editor_hlay_1.addWidget(self.buttonReplace)
  89. editor_hlay_1.addWidget(self.entryReplace)
  90. editor_hlay_1.addWidget(self.sel_all_cb)
  91. editor_hlay_1.addWidget(self.button_copy_all)
  92. self.work_editor_layout.addLayout(editor_hlay_1, 1, 0, 1, 5)
  93. editor_hlay_2 = QtWidgets.QHBoxLayout()
  94. editor_hlay_2.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
  95. editor_hlay_2.addWidget(self.buttonPreview)
  96. editor_hlay_2.addWidget(self.buttonPrint)
  97. self.work_editor_layout.addLayout(editor_hlay_2, 2, 0, 1, 1, QtCore.Qt.AlignLeft)
  98. cnc_tab_lay_4 = QtWidgets.QHBoxLayout()
  99. cnc_tab_lay_4.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
  100. cnc_tab_lay_4.addWidget(self.buttonOpen)
  101. cnc_tab_lay_4.addWidget(self.buttonSave)
  102. cnc_tab_lay_4.addWidget(self.buttonRun)
  103. self.work_editor_layout.addLayout(cnc_tab_lay_4, 2, 4, 1, 1)
  104. # #################################################################################
  105. # ################### SIGNALS #####################################################
  106. # #################################################################################
  107. self.code_editor.textChanged.connect(self.handleTextChanged)
  108. self.buttonOpen.clicked.connect(self.handleOpen)
  109. self.buttonSave.clicked.connect(self.handleSaveGCode)
  110. self.buttonPrint.clicked.connect(self.handlePrint)
  111. self.buttonPreview.clicked.connect(self.handlePreview)
  112. self.buttonFind.clicked.connect(self.handleFindGCode)
  113. self.buttonReplace.clicked.connect(self.handleReplaceGCode)
  114. self.button_copy_all.clicked.connect(self.handleCopyAll)
  115. self.code_editor.set_model_data(self.app.myKeywords)
  116. self.code_edited = ''
  117. def handlePrint(self):
  118. self.app.defaults.report_usage("handlePrint()")
  119. dialog = QtPrintSupport.QPrintDialog()
  120. if dialog.exec_() == QtWidgets.QDialog.Accepted:
  121. self.code_editor.document().print_(dialog.printer())
  122. def handlePreview(self):
  123. self.app.defaults.report_usage("handlePreview()")
  124. dialog = QtPrintSupport.QPrintPreviewDialog()
  125. dialog.paintRequested.connect(self.code_editor.print_)
  126. dialog.exec_()
  127. def handleTextChanged(self):
  128. # enable = not self.ui.code_editor.document().isEmpty()
  129. # self.ui.buttonPrint.setEnabled(enable)
  130. # self.ui.buttonPreview.setEnabled(enable)
  131. pass
  132. def handleOpen(self, filt=None):
  133. self.app.defaults.report_usage("handleOpen()")
  134. if filt:
  135. _filter_ = filt
  136. else:
  137. _filter_ = "G-Code Files (*.nc);; G-Code Files (*.txt);; G-Code Files (*.tap);; G-Code Files (*.cnc);; " \
  138. "All Files (*.*)"
  139. path, _f = QtWidgets.QFileDialog.getOpenFileName(
  140. caption=_('Open file'), directory=self.app.get_last_folder(), filter=_filter_)
  141. if path:
  142. file = QtCore.QFile(path)
  143. if file.open(QtCore.QIODevice.ReadOnly):
  144. stream = QtCore.QTextStream(file)
  145. self.code_edited = stream.readAll()
  146. self.code_editor.setPlainText(self.code_edited)
  147. file.close()
  148. def handleSaveGCode(self, name=None, filt=None, callback=None):
  149. self.app.defaults.report_usage("handleSaveGCode()")
  150. if filt:
  151. _filter_ = filt
  152. else:
  153. _filter_ = "G-Code Files (*.nc);; G-Code Files (*.txt);; G-Code Files (*.tap);; G-Code Files (*.cnc);; " \
  154. "PDF Files (*.pdf);;All Files (*.*)"
  155. if name:
  156. obj_name = name
  157. else:
  158. try:
  159. obj_name = self.app.collection.get_active().options['name']
  160. except AttributeError:
  161. obj_name = 'file'
  162. if filt is None:
  163. _filter_ = "FlatConfig Files (*.FlatConfig);;PDF Files (*.pdf);;All Files (*.*)"
  164. try:
  165. filename = str(FCFileSaveDialog.get_saved_filename(
  166. caption=_("Export Code ..."),
  167. directory=self.app.defaults["global_last_folder"] + '/' + str(obj_name),
  168. ext_filter=_filter_
  169. )[0])
  170. except TypeError:
  171. filename = str(FCFileSaveDialog.get_saved_filename(caption=_("Export Code ..."), ext_filter=_filter_)[0])
  172. if filename == "":
  173. self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
  174. return
  175. else:
  176. try:
  177. my_gcode = self.code_editor.toPlainText()
  178. if filename.rpartition('.')[2].lower() == 'pdf':
  179. page_size = (
  180. self.app.plotcanvas.pagesize_dict[self.app.defaults['global_workspaceT']][0] * mm,
  181. self.app.plotcanvas.pagesize_dict[self.app.defaults['global_workspaceT']][1] * mm
  182. )
  183. # add new line after each line
  184. lined_gcode = my_gcode.replace("\n", "<br />")
  185. styles = getSampleStyleSheet()
  186. styleN = styles['Normal']
  187. # styleH = styles['Heading1']
  188. story = []
  189. if self.app.defaults['units'].lower() == 'mm':
  190. bmargin = self.app.defaults['global_tpdf_bmargin'] * mm
  191. tmargin = self.app.defaults['global_tpdf_tmargin'] * mm
  192. rmargin = self.app.defaults['global_tpdf_rmargin'] * mm
  193. lmargin = self.app.defaults['global_tpdf_lmargin'] * mm
  194. else:
  195. bmargin = self.app.defaults['global_tpdf_bmargin'] * inch
  196. tmargin = self.app.defaults['global_tpdf_tmargin'] * inch
  197. rmargin = self.app.defaults['global_tpdf_rmargin'] * inch
  198. lmargin = self.app.defaults['global_tpdf_lmargin'] * inch
  199. doc = SimpleDocTemplate(
  200. filename,
  201. pagesize=page_size,
  202. bottomMargin=bmargin,
  203. topMargin=tmargin,
  204. rightMargin=rmargin,
  205. leftMargin=lmargin)
  206. P = Paragraph(lined_gcode, styleN)
  207. story.append(P)
  208. doc.build(
  209. story,
  210. )
  211. else:
  212. with open(filename, 'w') as f:
  213. for line in my_gcode:
  214. f.write(line)
  215. except FileNotFoundError:
  216. self.app.inform.emit('[WARNING] %s' % _("No such file or directory"))
  217. return
  218. except PermissionError:
  219. self.app.inform.emit('[WARNING] %s' %
  220. _("Permission denied, saving not possible.\n"
  221. "Most likely another app is holding the file open and not accessible."))
  222. return
  223. # Just for adding it to the recent files list.
  224. if self.app.defaults["global_open_style"] is False:
  225. self.app.file_opened.emit("cncjob", filename)
  226. self.app.file_saved.emit("cncjob", filename)
  227. self.app.inform.emit('%s: %s' % (_("Saved to"), str(filename)))
  228. if callback is not None:
  229. callback()
  230. def handleFindGCode(self):
  231. self.app.defaults.report_usage("handleFindGCode()")
  232. flags = QtGui.QTextDocument.FindCaseSensitively
  233. text_to_be_found = self.entryFind.get_value()
  234. r = self.code_editor.find(str(text_to_be_found), flags)
  235. if r is False:
  236. self.code_editor.moveCursor(QtGui.QTextCursor.Start)
  237. r = self.code_editor.find(str(text_to_be_found), flags)
  238. def handleReplaceGCode(self):
  239. self.app.defaults.report_usage("handleReplaceGCode()")
  240. old = self.entryFind.get_value()
  241. new = self.entryReplace.get_value()
  242. if self.sel_all_cb.isChecked():
  243. while True:
  244. cursor = self.code_editor.textCursor()
  245. cursor.beginEditBlock()
  246. flags = QtGui.QTextDocument.FindCaseSensitively
  247. # self.ui.editor is the QPlainTextEdit
  248. r = self.code_editor.find(str(old), flags)
  249. if r:
  250. qc = self.code_editor.textCursor()
  251. if qc.hasSelection():
  252. qc.insertText(new)
  253. else:
  254. self.code_editor.moveCursor(QtGui.QTextCursor.Start)
  255. break
  256. # Mark end of undo block
  257. cursor.endEditBlock()
  258. else:
  259. cursor = self.code_editor.textCursor()
  260. cursor.beginEditBlock()
  261. qc = self.code_editor.textCursor()
  262. if qc.hasSelection():
  263. qc.insertText(new)
  264. # Mark end of undo block
  265. cursor.endEditBlock()
  266. def handleCopyAll(self):
  267. text = self.code_editor.toPlainText()
  268. self.app.clipboard.setText(text)
  269. self.app.inform.emit(_("Code Editor content copied to clipboard ..."))
  270. # def closeEvent(self, QCloseEvent):
  271. # super().closeEvent(QCloseEvent)