| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359 |
- # ##########################################################
- # FlatCAM: 2D Post-processing for Manufacturing #
- # http://flatcam.org #
- # Author: Juan Pablo Caram (c) #
- # Date: 2/5/2014 #
- # MIT Licence #
- # ##########################################################
- from PyQt5.QtCore import Qt
- from PyQt5.QtGui import QTextCursor
- from PyQt5.QtWidgets import QVBoxLayout, QWidget
- from flatcamGUI.GUIElements import _BrowserTextEdit, _ExpandableTextEdit
- import html
- import sys
- import tkinter as tk
- import gettext
- import FlatCAMTranslation as fcTranslate
- import builtins
- fcTranslate.apply_language('strings')
- if '_' not in builtins.__dict__:
- _ = gettext.gettext
- class TermWidget(QWidget):
- """
- Widget which represents terminal. It only displays text and allows to enter text.
- All high level logic should be implemented by client classes
- User pressed Enter. Client class should decide, if command must be executed or user may continue edit it
- """
- def __init__(self, version, app, *args):
- QWidget.__init__(self, *args)
- self._browser = _BrowserTextEdit(version=version, app=app)
- self._browser.setStyleSheet("font: 9pt \"Courier\";")
- self._browser.setReadOnly(True)
- self._browser.document().setDefaultStyleSheet(
- self._browser.document().defaultStyleSheet() +
- "span {white-space:pre;}")
- self._edit = _ExpandableTextEdit(self, self)
- self._edit.historyNext.connect(self._on_history_next)
- self._edit.historyPrev.connect(self._on_history_prev)
- self._edit.setFocus()
- self.setFocusProxy(self._edit)
- layout = QVBoxLayout(self)
- layout.setSpacing(0)
- layout.setContentsMargins(0, 0, 0, 0)
- layout.addWidget(self._browser)
- layout.addWidget(self._edit)
- self._history = [''] # current empty line
- self._historyIndex = 0
- def open_processing(self, detail=None):
- """
- Open processing and disable using shell commands again until all commands are finished
- :param detail: text detail about what is currently called from TCL to python
- :return: None
- """
- self._edit.setTextColor(Qt.white)
- self._edit.setTextBackgroundColor(Qt.darkGreen)
- if detail is None:
- self._edit.setPlainText(_("...processing..."))
- else:
- self._edit.setPlainText('%s [%s]' % (_("...processing..."), detail))
- self._edit.setDisabled(True)
- self._edit.setFocus()
- def close_processing(self):
- """
- Close processing and enable using shell commands again
- :return:
- """
- self._edit.setTextColor(Qt.black)
- self._edit.setTextBackgroundColor(Qt.white)
- self._edit.setPlainText('')
- self._edit.setDisabled(False)
- self._edit.setFocus()
- def _append_to_browser(self, style, text):
- """
- Convert text to HTML for inserting it to browser
- """
- assert style in ('in', 'out', 'err', 'warning', 'success', 'selected', 'raw')
- if style != 'raw':
- text = html.escape(text)
- text = text.replace('\n', '<br/>')
- else:
- text = text.replace('\n', '<br>')
- text = text.replace('\t', ' ')
- if style == 'in':
- text = '<span style="font-weight: bold;">%s</span>' % text
- elif style == 'err':
- text = '<span style="font-weight: bold; color: red;">%s</span>' % text
- elif style == 'warning':
- text = '<span style="font-weight: bold; color: #f4b642;">%s</span>' % text
- elif style == 'success':
- text = '<span style="font-weight: bold; color: #15b300;">%s</span>' % text
- elif style == 'selected':
- text = ''
- elif style == 'raw':
- text = text
- else:
- text = '<span>%s</span>' % text # without span <br/> is ignored!!!
- scrollbar = self._browser.verticalScrollBar()
- old_value = scrollbar.value()
- scrollattheend = old_value == scrollbar.maximum()
- self._browser.moveCursor(QTextCursor.End)
- self._browser.insertHtml(text)
- """TODO When user enters second line to the input, and input is resized, scrollbar changes its position
- and stops moving. As quick fix of this problem, now we always scroll down when add new text.
- To fix it correctly, scroll to the bottom, if before input has been resized,
- scrollbar was in the bottom, and remove next line
- """
- scrollattheend = True
- if scrollattheend:
- scrollbar.setValue(scrollbar.maximum())
- else:
- scrollbar.setValue(old_value)
- def exec_current_command(self):
- """
- Save current command in the history. Append it to the log. Clear edit line
- Re-implement in the child classes to actually execute command
- """
- text = str(self._edit.toPlainText())
- # in Windows replace all backslash symbols '\' with '\\' slash because Windows paths are made with backslash
- # and in Python single slash is the escape symbol
- if sys.platform == 'win32':
- text = text.replace('\\', '\\\\')
- self._append_to_browser('in', '> ' + text + '\n')
- if len(self._history) < 2 or self._history[-2] != text: # don't insert duplicating items
- try:
- if text[-1] == '\n':
- self._history.insert(-1, text[:-1])
- else:
- self._history.insert(-1, text)
- except IndexError:
- return
- self._historyIndex = len(self._history) - 1
- self._history[-1] = ''
- self._edit.clear()
- if not text[-1] == '\n':
- text += '\n'
- self.child_exec_command(text)
- def child_exec_command(self, text):
- """
- Re-implement in the child classes
- """
- pass
- def add_line_break_to_input(self):
- self._edit.textCursor().insertText('\n')
- def append_output(self, text):
- """
- Append text to output widget
- """
- self._append_to_browser('out', text)
- def append_raw(self, text):
- """
- Append text to output widget as it is
- """
- self._append_to_browser('raw', text)
- def append_success(self, text):
- """Append text to output widget
- """
- self._append_to_browser('success', text)
- def append_selected(self, text):
- """Append text to output widget
- """
- self._append_to_browser('selected', text)
- def append_warning(self, text):
- """Append text to output widget
- """
- self._append_to_browser('warning', text)
- def append_error(self, text):
- """Append error text to output widget. Text is drawn with red background
- """
- self._append_to_browser('err', text)
- def is_command_complete(self, text):
- """
- Executed by _ExpandableTextEdit. Reimplement this function in the child classes.
- """
- return True
- def browser(self):
- return self._browser
- def _on_history_next(self):
- """
- Down pressed, show next item from the history
- """
- if (self._historyIndex + 1) < len(self._history):
- self._historyIndex += 1
- self._edit.setPlainText(self._history[self._historyIndex])
- self._edit.moveCursor(QTextCursor.End)
- def _on_history_prev(self):
- """
- Up pressed, show previous item from the history
- """
- if self._historyIndex > 0:
- if self._historyIndex == (len(self._history) - 1):
- self._history[-1] = self._edit.toPlainText()
- self._historyIndex -= 1
- self._edit.setPlainText(self._history[self._historyIndex])
- self._edit.moveCursor(QTextCursor.End)
- class FCShell(TermWidget):
- def __init__(self, sysShell, version, *args):
- """
- :param sysShell: When instantiated the sysShell will be actually the FlatCAMApp.App() class
- :param version: FlatCAM version string
- :param args: Parameters passed to the TermWidget parent class
- """
- TermWidget.__init__(self, version, *args, app=sysShell)
- self._sysShell = sysShell
- def is_command_complete(self, text):
- def skipQuotes(text):
- quote = text[0]
- text = text[1:]
- endIndex = str(text).index(quote)
- return text[endIndex:]
- while text:
- if text[0] in ('"', "'"):
- try:
- text = skipQuotes(text)
- except ValueError:
- return False
- text = text[1:]
- return True
- def child_exec_command(self, text):
- self.exec_command(text)
- def exec_command(self, text, no_echo=False):
- """
- Handles input from the shell. See FlatCAMApp.setup_shell for shell commands.
- Also handles execution in separated threads
- :param text: FlatCAM TclCommand with parameters
- :param no_echo: If True it will not try to print to the Shell because most likely the shell is hidden and it
- will create crashes of the _Expandable_Edit widget
- :return: output if there was any
- """
- self._sysShell.report_usage('exec_command')
- return self.exec_command_test(text, False, no_echo=no_echo)
- def exec_command_test(self, text, reraise=True, no_echo=False):
- """
- Same as exec_command(...) with additional control over exceptions.
- Handles input from the shell. See FlatCAMApp.setup_shell for shell commands.
- :param text: Input command
- :param reraise: Re-raise TclError exceptions in Python (mostly for unittests).
- :param no_echo: If True it will not try to print to the Shell because most likely the shell is hidden and it
- will create crashes of the _Expandable_Edit widget
- :return: Output from the command
- """
- tcl_command_string = str(text)
- try:
- if no_echo is False:
- self.open_processing() # Disables input box.
- result = self._sysShell.tcl.eval(str(tcl_command_string))
- if result != 'None' and no_echo is False:
- self.append_output(result + '\n')
- except tk.TclError as e:
- # This will display more precise answer if something in TCL shell fails
- result = self._sysShell.tcl.eval("set errorInfo")
- self._sysShell.log.error("Exec command Exception: %s" % (result + '\n'))
- if no_echo is False:
- self.append_error('ERROR: ' + result + '\n')
- # Show error in console and just return or in test raise exception
- if reraise:
- raise e
- finally:
- if no_echo is False:
- self.close_processing()
- pass
- return result
- # """
- # Code below is unsused. Saved for later.
- # """
- # parts = re.findall(r'([\w\\:\.]+|".*?")+', text)
- # parts = [p.replace('\n', '').replace('"', '') for p in parts]
- # self.log.debug(parts)
- # try:
- # if parts[0] not in commands:
- # self.shell.append_error("Unknown command\n")
- # return
- #
- # #import inspect
- # #inspect.getargspec(someMethod)
- # if (type(commands[parts[0]]["params"]) is not list and len(parts)-1 != commands[parts[0]]["params"]) or \
- # (type(commands[parts[0]]["params"]) is list and len(parts)-1 not in commands[parts[0]]["params"]):
- # self.shell.append_error(
- # "Command %s takes %d arguments. %d given.\n" %
- # (parts[0], commands[parts[0]]["params"], len(parts)-1)
- # )
- # return
- #
- # cmdfcn = commands[parts[0]]["fcn"]
- # cmdconv = commands[parts[0]]["converters"]
- # if len(parts) - 1 > 0:
- # retval = cmdfcn(*[cmdconv[i](parts[i + 1]) for i in range(len(parts)-1)])
- # else:
- # retval = cmdfcn()
- # retfcn = commands[parts[0]]["retfcn"]
- # if retval and retfcn(retval):
- # self.shell.append_output(retfcn(retval) + "\n")
- #
- # except Exception as e:
- # #self.shell.append_error(''.join(traceback.format_exc()))
- # #self.shell.append_error("?\n")
- # self.shell.append_error(str(e) + "\n")
|