- fixed bug in Geometry Editor that did not allow the copy of geometric elements
- created a new class that holds all the Code Editor functionality and integrated as a Editor in FlatCAM, the location is in flatcamEditors folder - remade all the functions for view_source, scripts and view_code to use the new TextEditor class; now all the Code Editor tabs are being kept alive, before only one could be in an open state
This commit is contained in:
347
flatcamEditors/FlatCAMTextEditor.py
Normal file
347
flatcamEditors/FlatCAMTextEditor.py
Normal file
@@ -0,0 +1,347 @@
|
||||
from flatcamGUI.GUIElements import *
|
||||
from PyQt5 import QtPrintSupport
|
||||
|
||||
import tkinter as tk
|
||||
from copy import deepcopy
|
||||
|
||||
import sys
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
if '_' not in builtins.__dict__:
|
||||
_ = gettext.gettext
|
||||
|
||||
|
||||
class TextEditor(QtWidgets.QWidget):
|
||||
|
||||
def __init__(self, app, text=None):
|
||||
super().__init__()
|
||||
|
||||
self.app = app
|
||||
self.setSizePolicy(
|
||||
QtWidgets.QSizePolicy.MinimumExpanding,
|
||||
QtWidgets.QSizePolicy.MinimumExpanding
|
||||
)
|
||||
|
||||
self.main_editor_layout = QtWidgets.QVBoxLayout(self)
|
||||
self.main_editor_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
self.t_frame = QtWidgets.QFrame()
|
||||
self.t_frame.setContentsMargins(0, 0, 0, 0)
|
||||
self.main_editor_layout.addWidget(self.t_frame)
|
||||
|
||||
self.work_editor_layout = QtWidgets.QGridLayout(self.t_frame)
|
||||
self.work_editor_layout.setContentsMargins(2, 2, 2, 2)
|
||||
self.t_frame.setLayout(self.work_editor_layout)
|
||||
|
||||
self.code_editor = FCTextAreaExtended()
|
||||
stylesheet = """
|
||||
QTextEdit { selection-background-color:yellow;
|
||||
selection-color:black;
|
||||
}
|
||||
"""
|
||||
|
||||
self.code_editor.setStyleSheet(stylesheet)
|
||||
|
||||
if text:
|
||||
self.code_editor.setPlainText(text)
|
||||
|
||||
self.buttonPreview = QtWidgets.QPushButton(_('Print Preview'))
|
||||
self.buttonPreview.setToolTip(_("Open a OS standard Preview Print window."))
|
||||
self.buttonPreview.setMinimumWidth(100)
|
||||
|
||||
self.buttonPrint = QtWidgets.QPushButton(_('Print Code'))
|
||||
self.buttonPrint.setToolTip(_("Open a OS standard Print window."))
|
||||
|
||||
self.buttonFind = QtWidgets.QPushButton(_('Find in Code'))
|
||||
self.buttonFind.setToolTip(_("Will search and highlight in yellow the string in the Find box."))
|
||||
self.buttonFind.setMinimumWidth(100)
|
||||
|
||||
self.entryFind = FCEntry()
|
||||
self.entryFind.setToolTip(_("Find box. Enter here the strings to be searched in the text."))
|
||||
|
||||
self.buttonReplace = QtWidgets.QPushButton(_('Replace With'))
|
||||
self.buttonReplace.setToolTip(_("Will replace the string from the Find box with the one in the Replace box."))
|
||||
self.buttonReplace.setMinimumWidth(100)
|
||||
|
||||
self.entryReplace = FCEntry()
|
||||
self.entryReplace.setToolTip(_("String to replace the one in the Find box throughout the text."))
|
||||
|
||||
self.sel_all_cb = QtWidgets.QCheckBox(_('All'))
|
||||
self.sel_all_cb.setToolTip(_("When checked it will replace all instances in the 'Find' box\n"
|
||||
"with the text in the 'Replace' box.."))
|
||||
|
||||
self.button_copy_all = QtWidgets.QPushButton(_('Copy All'))
|
||||
self.button_copy_all.setToolTip(_("Will copy all the text in the Code Editor to the clipboard."))
|
||||
self.button_copy_all.setMinimumWidth(100)
|
||||
|
||||
self.buttonOpen = QtWidgets.QPushButton(_('Open Code'))
|
||||
self.buttonOpen.setToolTip(_("Will open a text file in the editor."))
|
||||
|
||||
self.buttonSave = QtWidgets.QPushButton(_('Save Code'))
|
||||
self.buttonSave.setToolTip(_("Will save the text in the editor into a file."))
|
||||
|
||||
self.buttonRun = QtWidgets.QPushButton(_('Run Code'))
|
||||
self.buttonRun.setToolTip(_("Will run the TCL commands found in the text file, one by one."))
|
||||
|
||||
self.buttonRun.hide()
|
||||
self.work_editor_layout.addWidget(self.code_editor, 0, 0, 1, 5)
|
||||
|
||||
editor_hlay_1 = QtWidgets.QHBoxLayout()
|
||||
# cnc_tab_lay_1.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
|
||||
editor_hlay_1.addWidget(self.buttonFind)
|
||||
editor_hlay_1.addWidget(self.entryFind)
|
||||
editor_hlay_1.addWidget(self.buttonReplace)
|
||||
editor_hlay_1.addWidget(self.entryReplace)
|
||||
editor_hlay_1.addWidget(self.sel_all_cb)
|
||||
editor_hlay_1.addWidget(self.button_copy_all)
|
||||
self.work_editor_layout.addLayout(editor_hlay_1, 1, 0, 1, 5)
|
||||
|
||||
editor_hlay_2 = QtWidgets.QHBoxLayout()
|
||||
editor_hlay_2.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
|
||||
editor_hlay_2.addWidget(self.buttonPreview)
|
||||
editor_hlay_2.addWidget(self.buttonPrint)
|
||||
self.work_editor_layout.addLayout(editor_hlay_2, 2, 0, 1, 1, QtCore.Qt.AlignLeft)
|
||||
|
||||
cnc_tab_lay_4 = QtWidgets.QHBoxLayout()
|
||||
cnc_tab_lay_4.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
cnc_tab_lay_4.addWidget(self.buttonOpen)
|
||||
cnc_tab_lay_4.addWidget(self.buttonSave)
|
||||
cnc_tab_lay_4.addWidget(self.buttonRun)
|
||||
self.work_editor_layout.addLayout(cnc_tab_lay_4, 2, 4, 1, 1)
|
||||
|
||||
# #################################################################################
|
||||
# ################### SIGNALS #####################################################
|
||||
# #################################################################################
|
||||
|
||||
self.code_editor.textChanged.connect(self.handleTextChanged)
|
||||
self.buttonOpen.clicked.connect(self.handleOpen)
|
||||
self.buttonSave.clicked.connect(self.handleSaveGCode)
|
||||
self.buttonPrint.clicked.connect(self.handlePrint)
|
||||
self.buttonPreview.clicked.connect(self.handlePreview)
|
||||
self.buttonFind.clicked.connect(self.handleFindGCode)
|
||||
self.buttonReplace.clicked.connect(self.handleReplaceGCode)
|
||||
self.button_copy_all.clicked.connect(self.handleCopyAll)
|
||||
|
||||
self.code_editor.set_model_data(self.app.myKeywords)
|
||||
|
||||
self.gcode_edited = ''
|
||||
self.script_code = ''
|
||||
|
||||
def handlePrint(self):
|
||||
self.app.report_usage("handlePrint()")
|
||||
|
||||
dialog = QtPrintSupport.QPrintDialog()
|
||||
if dialog.exec_() == QtWidgets.QDialog.Accepted:
|
||||
self.code_editor.document().print_(dialog.printer())
|
||||
|
||||
def handlePreview(self):
|
||||
self.app.report_usage("handlePreview()")
|
||||
|
||||
dialog = QtPrintSupport.QPrintPreviewDialog()
|
||||
dialog.paintRequested.connect(self.code_editor.print_)
|
||||
dialog.exec_()
|
||||
|
||||
def handleTextChanged(self):
|
||||
# enable = not self.ui.code_editor.document().isEmpty()
|
||||
# self.ui.buttonPrint.setEnabled(enable)
|
||||
# self.ui.buttonPreview.setEnabled(enable)
|
||||
pass
|
||||
|
||||
def handleOpen(self, filt=None):
|
||||
self.app.report_usage("handleOpen()")
|
||||
|
||||
if filt:
|
||||
_filter_ = filt
|
||||
else:
|
||||
_filter_ = "G-Code Files (*.nc);; G-Code Files (*.txt);; G-Code Files (*.tap);; G-Code Files (*.cnc);; " \
|
||||
"All Files (*.*)"
|
||||
|
||||
path, _f = QtWidgets.QFileDialog.getOpenFileName(
|
||||
caption=_('Open file'), directory=self.app.get_last_folder(), filter=_filter_)
|
||||
|
||||
if path:
|
||||
file = QtCore.QFile(path)
|
||||
if file.open(QtCore.QIODevice.ReadOnly):
|
||||
stream = QtCore.QTextStream(file)
|
||||
self.gcode_edited = stream.readAll()
|
||||
self.code_editor.setPlainText(self.gcode_edited)
|
||||
file.close()
|
||||
|
||||
def handleSaveGCode(self, name=None, filt=None):
|
||||
self.app.report_usage("handleSaveGCode()")
|
||||
|
||||
if filt:
|
||||
_filter_ = filt
|
||||
else:
|
||||
_filter_ = "G-Code Files (*.nc);; G-Code Files (*.txt);; G-Code Files (*.tap);; G-Code Files (*.cnc);; " \
|
||||
"All Files (*.*)"
|
||||
|
||||
if name:
|
||||
obj_name = name
|
||||
else:
|
||||
try:
|
||||
obj_name = self.app.collection.get_active().options['name']
|
||||
except AttributeError:
|
||||
obj_name = 'file'
|
||||
if filt is None:
|
||||
_filter_ = "FlatConfig Files (*.FlatConfig);;All Files (*.*)"
|
||||
|
||||
try:
|
||||
filename = str(QtWidgets.QFileDialog.getSaveFileName(
|
||||
caption=_("Export G-Code ..."),
|
||||
directory=self.app.defaults["global_last_folder"] + '/' + str(obj_name),
|
||||
filter=_filter_
|
||||
)[0])
|
||||
except TypeError:
|
||||
filename = str(QtWidgets.QFileDialog.getSaveFileName(caption=_("Export G-Code ..."), filter=_filter_)[0])
|
||||
|
||||
if filename == "":
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export Code cancelled."))
|
||||
return
|
||||
else:
|
||||
try:
|
||||
my_gcode = self.code_editor.toPlainText()
|
||||
with open(filename, 'w') as f:
|
||||
for line in my_gcode:
|
||||
f.write(line)
|
||||
except FileNotFoundError:
|
||||
self.app.inform.emit('[WARNING] %s' % _("No such file or directory"))
|
||||
return
|
||||
except PermissionError:
|
||||
self.app.inform.emit('[WARNING] %s' %
|
||||
_("Permission denied, saving not possible.\n"
|
||||
"Most likely another app is holding the file open and not accessible."))
|
||||
return
|
||||
|
||||
# Just for adding it to the recent files list.
|
||||
if self.app.defaults["global_open_style"] is False:
|
||||
self.app.file_opened.emit("cncjob", filename)
|
||||
self.app.file_saved.emit("cncjob", filename)
|
||||
self.app.inform.emit('%s: %s' % (_("Saved to"), str(filename)))
|
||||
|
||||
def handleFindGCode(self):
|
||||
self.app.report_usage("handleFindGCode()")
|
||||
|
||||
flags = QtGui.QTextDocument.FindCaseSensitively
|
||||
text_to_be_found = self.entryFind.get_value()
|
||||
|
||||
r = self.code_editor.find(str(text_to_be_found), flags)
|
||||
if r is False:
|
||||
self.code_editor.moveCursor(QtGui.QTextCursor.Start)
|
||||
|
||||
def handleReplaceGCode(self):
|
||||
self.app.report_usage("handleReplaceGCode()")
|
||||
|
||||
old = self.entryFind.get_value()
|
||||
new = self.entryReplace.get_value()
|
||||
|
||||
if self.sel_all_cb.isChecked():
|
||||
while True:
|
||||
cursor = self.code_editor.textCursor()
|
||||
cursor.beginEditBlock()
|
||||
flags = QtGui.QTextDocument.FindCaseSensitively
|
||||
# self.ui.editor is the QPlainTextEdit
|
||||
r = self.code_editor.find(str(old), flags)
|
||||
if r:
|
||||
qc = self.code_editor.textCursor()
|
||||
if qc.hasSelection():
|
||||
qc.insertText(new)
|
||||
else:
|
||||
self.ui.code_editor.moveCursor(QtGui.QTextCursor.Start)
|
||||
break
|
||||
# Mark end of undo block
|
||||
cursor.endEditBlock()
|
||||
else:
|
||||
cursor = self.code_editor.textCursor()
|
||||
cursor.beginEditBlock()
|
||||
qc = self.code_editor.textCursor()
|
||||
if qc.hasSelection():
|
||||
qc.insertText(new)
|
||||
# Mark end of undo block
|
||||
cursor.endEditBlock()
|
||||
|
||||
def handleCopyAll(self):
|
||||
text = self.code_editor.toPlainText()
|
||||
self.app.clipboard.setText(text)
|
||||
self.app.inform.emit(_("Code Editor content copied to clipboard ..."))
|
||||
|
||||
def handleRunCode(self):
|
||||
# trying to run a Tcl command without having the Shell open will create some warnings because the Tcl Shell
|
||||
# tries to print on a hidden widget, therefore show the dock if hidden
|
||||
if self.app.ui.shell_dock.isHidden():
|
||||
self.app.ui.shell_dock.show()
|
||||
|
||||
self.script_code = deepcopy(self.code_editor.toPlainText())
|
||||
|
||||
old_line = ''
|
||||
for tcl_command_line in self.app.script_code.splitlines():
|
||||
# do not process lines starting with '#' = comment and empty lines
|
||||
if not tcl_command_line.startswith('#') and tcl_command_line != '':
|
||||
# id FlatCAM is run in Windows then replace all the slashes with
|
||||
# the UNIX style slash that TCL understands
|
||||
if sys.platform == 'win32':
|
||||
if "open" in tcl_command_line:
|
||||
tcl_command_line = tcl_command_line.replace('\\', '/')
|
||||
|
||||
if old_line != '':
|
||||
new_command = old_line + tcl_command_line + '\n'
|
||||
else:
|
||||
new_command = tcl_command_line
|
||||
|
||||
# execute the actual Tcl command
|
||||
try:
|
||||
self.app.shell.open_proccessing() # Disables input box.
|
||||
|
||||
result = self.app.tcl.eval(str(new_command))
|
||||
if result != 'None':
|
||||
self.app.shell.append_output(result + '\n')
|
||||
|
||||
old_line = ''
|
||||
except tk.TclError:
|
||||
old_line = old_line + tcl_command_line + '\n'
|
||||
except Exception as e:
|
||||
log.debug("App.handleRunCode() --> %s" % str(e))
|
||||
|
||||
if old_line != '':
|
||||
# it means that the script finished with an error
|
||||
result = self.app.tcl.eval("set errorInfo")
|
||||
log.error("Exec command Exception: %s" % (result + '\n'))
|
||||
self.app.shell.append_error('ERROR: ' + result + '\n')
|
||||
|
||||
self.app.shell.close_proccessing()
|
||||
|
||||
def closeEvent(self, QCloseEvent):
|
||||
try:
|
||||
self.code_editor.textChanged.disconnect()
|
||||
except TypeError:
|
||||
pass
|
||||
try:
|
||||
self.buttonOpen.clicked.disconnect()
|
||||
except TypeError:
|
||||
pass
|
||||
try:
|
||||
self.buttonPrint.clicked.disconnect()
|
||||
except TypeError:
|
||||
pass
|
||||
try:
|
||||
self.buttonPreview.clicked.disconnect()
|
||||
except TypeError:
|
||||
pass
|
||||
try:
|
||||
self.buttonFind.clicked.disconnect()
|
||||
except TypeError:
|
||||
pass
|
||||
try:
|
||||
self.buttonReplace.clicked.disconnect()
|
||||
except TypeError:
|
||||
pass
|
||||
try:
|
||||
self.button_copy_all.clicked.disconnect()
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
super().closeEvent(QCloseEvent)
|
||||
Reference in New Issue
Block a user