AppTextEditor.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  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, FCButton
  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 AppTextEditor(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.callback = lambda x: None
  25. self.setSizePolicy(
  26. QtWidgets.QSizePolicy.MinimumExpanding,
  27. QtWidgets.QSizePolicy.MinimumExpanding
  28. )
  29. # UI Layout
  30. self.main_editor_layout = QtWidgets.QVBoxLayout(self)
  31. self.main_editor_layout.setContentsMargins(0, 0, 0, 0)
  32. self.t_frame = QtWidgets.QFrame()
  33. self.t_frame.setContentsMargins(0, 0, 0, 0)
  34. self.main_editor_layout.addWidget(self.t_frame)
  35. self.work_editor_layout = QtWidgets.QGridLayout(self.t_frame)
  36. self.work_editor_layout.setContentsMargins(2, 2, 2, 2)
  37. self.t_frame.setLayout(self.work_editor_layout)
  38. # CODE Editor
  39. if self.plain_text:
  40. self.editor_class = FCTextAreaLineNumber()
  41. self.code_editor = self.editor_class.edit
  42. stylesheet = """
  43. QPlainTextEdit { selection-background-color:yellow;
  44. selection-color:black;
  45. }
  46. """
  47. self.work_editor_layout.addWidget(self.editor_class, 0, 0, 1, 5)
  48. else:
  49. self.code_editor = FCTextAreaExtended()
  50. stylesheet = """
  51. QTextEdit { selection-background-color:yellow;
  52. selection-color:black;
  53. }
  54. """
  55. self.work_editor_layout.addWidget(self.code_editor, 0, 0, 1, 5)
  56. self.code_editor.setStyleSheet(stylesheet)
  57. if text:
  58. self.code_editor.setPlainText(text)
  59. # #############################################################################################################
  60. # UI SETUP
  61. # #############################################################################################################
  62. control_lay = QtWidgets.QHBoxLayout()
  63. self.work_editor_layout.addLayout(control_lay, 1, 0, 1, 5)
  64. # FIND
  65. self.buttonFind = FCButton(_('Find'))
  66. self.buttonFind.setIcon(QtGui.QIcon(self.app.resource_location + '/find32.png'))
  67. self.buttonFind.setToolTip(_("Will search and highlight in yellow the string in the Find box."))
  68. control_lay.addWidget(self.buttonFind)
  69. # Entry FIND
  70. self.entryFind = FCEntry()
  71. self.entryFind.setToolTip(_("Find box. Enter here the strings to be searched in the text."))
  72. control_lay.addWidget(self.entryFind)
  73. # REPLACE
  74. self.buttonReplace = FCButton(_('Replace With'))
  75. self.buttonReplace.setIcon(QtGui.QIcon(self.app.resource_location + '/replace32.png'))
  76. self.buttonReplace.setToolTip(_("Will replace the string from the Find box with the one in the Replace box."))
  77. control_lay.addWidget(self.buttonReplace)
  78. # Entry REPLACE
  79. self.entryReplace = FCEntry()
  80. self.entryReplace.setToolTip(_("String to replace the one in the Find box throughout the text."))
  81. control_lay.addWidget(self.entryReplace)
  82. # Select All
  83. self.sel_all_cb = QtWidgets.QCheckBox(_('All'))
  84. self.sel_all_cb.setToolTip(_("When checked it will replace all instances in the 'Find' box\n"
  85. "with the text in the 'Replace' box.."))
  86. control_lay.addWidget(self.sel_all_cb)
  87. # COPY All
  88. # self.button_copy_all = FCButton(_('Copy All'))
  89. # self.button_copy_all.setIcon(QtGui.QIcon(self.app.resource_location + '/copy_file32.png'))
  90. # self.button_copy_all.setToolTip(_("Will copy all the text in the Code Editor to the clipboard."))
  91. # control_lay.addWidget(self.button_copy_all)
  92. # Update
  93. self.button_update_code = QtWidgets.QToolButton()
  94. self.button_update_code.setIcon(QtGui.QIcon(self.app.resource_location + '/save_as.png'))
  95. self.button_update_code.setToolTip(_("Save changes internally."))
  96. self.button_update_code.hide()
  97. control_lay.addWidget(self.button_update_code)
  98. # Print PREVIEW
  99. self.buttonPreview = QtWidgets.QToolButton()
  100. self.buttonPreview.setIcon(QtGui.QIcon(self.app.resource_location + '/preview32.png'))
  101. self.buttonPreview.setToolTip(_("Open a OS standard Preview Print window."))
  102. control_lay.addWidget(self.buttonPreview)
  103. # PRINT
  104. self.buttonPrint = QtWidgets.QToolButton()
  105. self.buttonPrint.setIcon(QtGui.QIcon(self.app.resource_location + '/printer32.png'))
  106. self.buttonPrint.setToolTip(_("Open a OS standard Print window."))
  107. control_lay.addWidget(self.buttonPrint)
  108. # OPEN
  109. self.buttonOpen = QtWidgets.QToolButton()
  110. self.buttonOpen.setIcon(QtGui.QIcon(self.app.resource_location + '/folder32_bis.png'))
  111. self.buttonOpen.setToolTip(_("Will open a text file in the editor."))
  112. control_lay.addWidget(self.buttonOpen)
  113. # SAVE
  114. self.buttonSave = QtWidgets.QToolButton()
  115. self.buttonSave.setIcon(QtGui.QIcon(self.app.resource_location + '/save_as.png'))
  116. self.buttonSave.setToolTip(_("Will save the text in the editor into a file."))
  117. control_lay.addWidget(self.buttonSave)
  118. # RUN
  119. self.buttonRun = FCButton(_('Run'))
  120. self.buttonRun.setToolTip(_("Will run the TCL commands found in the text file, one by one."))
  121. self.buttonRun.hide()
  122. control_lay.addWidget(self.buttonRun)
  123. # #############################################################################################################
  124. # ################### SIGNALS #################################################################################
  125. # #############################################################################################################
  126. self.code_editor.textChanged.connect(self.handleTextChanged)
  127. self.buttonOpen.clicked.connect(self.handleOpen)
  128. self.buttonSave.clicked.connect(self.handleSaveGCode)
  129. self.buttonPrint.clicked.connect(self.handlePrint)
  130. self.buttonPreview.clicked.connect(self.handlePreview)
  131. self.buttonFind.clicked.connect(self.handleFindGCode)
  132. self.buttonReplace.clicked.connect(self.handleReplaceGCode)
  133. # self.button_copy_all.clicked.connect(self.handleCopyAll)
  134. self.code_editor.set_model_data(self.app.myKeywords)
  135. self.code_edited = ''
  136. def set_callback(self, callback):
  137. self.callback = callback
  138. def handlePrint(self):
  139. dialog = QtPrintSupport.QPrintDialog()
  140. if dialog.exec() == QtWidgets.QDialog.Accepted:
  141. self.code_editor.document().print_(dialog.printer())
  142. def handlePreview(self):
  143. dialog = QtPrintSupport.QPrintPreviewDialog()
  144. dialog.paintRequested.connect(self.code_editor.print)
  145. dialog.exec()
  146. def handleTextChanged(self):
  147. # enable = not self.ui.code_editor.document().isEmpty()
  148. # self.ui.buttonPrint.setEnabled(enable)
  149. # self.ui.buttonPreview.setEnabled(enable)
  150. self.buttonSave.setStyleSheet("QPushButton {color: red;}")
  151. self.buttonSave.setIcon(QtGui.QIcon(self.app.resource_location + '/save_as_red.png'))
  152. def load_text(self, text, move_to_start=False, move_to_end=False, clear_text=True, as_html=False):
  153. self.code_editor.textChanged.disconnect()
  154. if clear_text:
  155. # first clear previous text in text editor (if any)
  156. self.code_editor.clear()
  157. self.code_editor.setReadOnly(False)
  158. if as_html is False:
  159. self.code_editor.setPlainText(text)
  160. else:
  161. self.code_editor.setHtml(text)
  162. if move_to_start:
  163. self.code_editor.moveCursor(QtGui.QTextCursor.Start)
  164. elif move_to_end:
  165. self.code_editor.moveCursor(QtGui.QTextCursor.End)
  166. self.code_editor.textChanged.connect(self.handleTextChanged)
  167. def handleOpen(self, filt=None):
  168. self.app.defaults.report_usage("handleOpen()")
  169. if filt:
  170. _filter_ = filt
  171. else:
  172. _filter_ = "G-Code Files (*.nc);; G-Code Files (*.txt);; G-Code Files (*.tap);; G-Code Files (*.cnc);; " \
  173. "All Files (*.*)"
  174. path, _f = QtWidgets.QFileDialog.getOpenFileName(
  175. caption=_('Open file'), directory=self.app.get_last_folder(), filter=_filter_)
  176. if path:
  177. file = QtCore.QFile(path)
  178. if file.open(QtCore.QIODevice.ReadOnly):
  179. stream = QtCore.QTextStream(file)
  180. self.code_edited = stream.readAll()
  181. self.code_editor.setPlainText(self.code_edited)
  182. file.close()
  183. def handleSaveGCode(self, name=None, filt=None, callback=None):
  184. self.app.defaults.report_usage("handleSaveGCode()")
  185. if filt:
  186. _filter_ = filt
  187. else:
  188. _filter_ = "G-Code Files (*.nc);; G-Code Files (*.txt);; G-Code Files (*.tap);; G-Code Files (*.cnc);; " \
  189. "PDF Files (*.pdf);;All Files (*.*)"
  190. if name:
  191. obj_name = name
  192. else:
  193. try:
  194. obj_name = self.app.collection.get_active().options['name']
  195. except AttributeError:
  196. obj_name = 'file'
  197. if filt is None:
  198. _filter_ = "FlatConfig Files (*.FlatConfig);;PDF Files (*.pdf);;All Files (*.*)"
  199. try:
  200. filename = str(FCFileSaveDialog.get_saved_filename(
  201. caption=_("Export Code ..."),
  202. directory=self.app.defaults["global_last_folder"] + '/' + str(obj_name),
  203. ext_filter=_filter_
  204. )[0])
  205. except TypeError:
  206. filename = str(FCFileSaveDialog.get_saved_filename(caption=_("Export Code ..."), ext_filter=_filter_)[0])
  207. if filename == "":
  208. self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
  209. return
  210. else:
  211. try:
  212. my_gcode = self.code_editor.toPlainText()
  213. if filename.rpartition('.')[2].lower() == 'pdf':
  214. page_size = (
  215. self.app.plotcanvas.pagesize_dict[self.app.defaults['global_workspaceT']][0] * mm,
  216. self.app.plotcanvas.pagesize_dict[self.app.defaults['global_workspaceT']][1] * mm
  217. )
  218. # add new line after each line
  219. lined_gcode = my_gcode.replace("\n", "<br />")
  220. styles = getSampleStyleSheet()
  221. styleN = styles['Normal']
  222. # styleH = styles['Heading1']
  223. story = []
  224. if self.app.defaults['units'].lower() == 'mm':
  225. bmargin = self.app.defaults['global_tpdf_bmargin'] * mm
  226. tmargin = self.app.defaults['global_tpdf_tmargin'] * mm
  227. rmargin = self.app.defaults['global_tpdf_rmargin'] * mm
  228. lmargin = self.app.defaults['global_tpdf_lmargin'] * mm
  229. else:
  230. bmargin = self.app.defaults['global_tpdf_bmargin'] * inch
  231. tmargin = self.app.defaults['global_tpdf_tmargin'] * inch
  232. rmargin = self.app.defaults['global_tpdf_rmargin'] * inch
  233. lmargin = self.app.defaults['global_tpdf_lmargin'] * inch
  234. doc = SimpleDocTemplate(
  235. filename,
  236. pagesize=page_size,
  237. bottomMargin=bmargin,
  238. topMargin=tmargin,
  239. rightMargin=rmargin,
  240. leftMargin=lmargin)
  241. P = Paragraph(lined_gcode, styleN)
  242. story.append(P)
  243. doc.build(
  244. story,
  245. )
  246. else:
  247. with open(filename, 'w') as f:
  248. for line in my_gcode:
  249. f.write(line)
  250. self.buttonSave.setStyleSheet("")
  251. self.buttonSave.setIcon(QtGui.QIcon(self.app.resource_location + '/save_as.png'))
  252. except FileNotFoundError:
  253. self.app.inform.emit('[WARNING] %s' % _("No such file or directory"))
  254. return
  255. except PermissionError:
  256. self.app.inform.emit('[WARNING] %s' %
  257. _("Permission denied, saving not possible.\n"
  258. "Most likely another app is holding the file open and not accessible."))
  259. return
  260. # Just for adding it to the recent files list.
  261. if self.app.defaults["global_open_style"] is False:
  262. self.app.file_opened.emit("cncjob", filename)
  263. self.app.file_saved.emit("cncjob", filename)
  264. self.app.inform.emit('%s: %s' % (_("Saved to"), str(filename)))
  265. if callback is not None:
  266. callback()
  267. def handleFindGCode(self):
  268. flags = QtGui.QTextDocument.FindCaseSensitively
  269. text_to_be_found = self.entryFind.get_value()
  270. r = self.code_editor.find(str(text_to_be_found), flags)
  271. if r is False:
  272. self.code_editor.moveCursor(QtGui.QTextCursor.Start)
  273. r = self.code_editor.find(str(text_to_be_found), flags)
  274. def handleReplaceGCode(self):
  275. old = self.entryFind.get_value()
  276. new = self.entryReplace.get_value()
  277. if self.sel_all_cb.isChecked():
  278. while True:
  279. cursor = self.code_editor.textCursor()
  280. cursor.beginEditBlock()
  281. flags = QtGui.QTextDocument.FindCaseSensitively
  282. # self.ui.editor is the QPlainTextEdit
  283. r = self.code_editor.find(str(old), flags)
  284. if r:
  285. qc = self.code_editor.textCursor()
  286. if qc.hasSelection():
  287. qc.insertText(new)
  288. else:
  289. self.code_editor.moveCursor(QtGui.QTextCursor.Start)
  290. break
  291. # Mark end of undo block
  292. cursor.endEditBlock()
  293. else:
  294. cursor = self.code_editor.textCursor()
  295. cursor.beginEditBlock()
  296. qc = self.code_editor.textCursor()
  297. if qc.hasSelection():
  298. qc.insertText(new)
  299. # Mark end of undo block
  300. cursor.endEditBlock()
  301. # def handleCopyAll(self):
  302. # text = self.code_editor.toPlainText()
  303. # self.app.clipboard.setText(text)
  304. # self.app.inform.emit(_("Content copied to clipboard ..."))
  305. # def closeEvent(self, QCloseEvent):
  306. # super().closeEvent(QCloseEvent)