ToolShell.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. # ##########################################################
  2. # FlatCAM: 2D Post-processing for Manufacturing #
  3. # http://flatcam.org #
  4. # Author: Juan Pablo Caram (c) #
  5. # Date: 2/5/2014 #
  6. # MIT Licence #
  7. # ##########################################################
  8. from PyQt5.QtCore import Qt
  9. from PyQt5.QtGui import QTextCursor
  10. from PyQt5.QtWidgets import QVBoxLayout, QWidget
  11. from flatcamGUI.GUIElements import _BrowserTextEdit, _ExpandableTextEdit
  12. import html
  13. import sys
  14. import gettext
  15. import FlatCAMTranslation as fcTranslate
  16. import builtins
  17. fcTranslate.apply_language('strings')
  18. if '_' not in builtins.__dict__:
  19. _ = gettext.gettext
  20. class TermWidget(QWidget):
  21. """
  22. Widget which represents terminal. It only displays text and allows to enter text.
  23. All high level logic should be implemented by client classes
  24. User pressed Enter. Client class should decide, if command must be executed or user may continue edit it
  25. """
  26. def __init__(self, version, *args):
  27. QWidget.__init__(self, *args)
  28. self._browser = _BrowserTextEdit(version=version)
  29. self._browser.setStyleSheet("font: 9pt \"Courier\";")
  30. self._browser.setReadOnly(True)
  31. self._browser.document().setDefaultStyleSheet(
  32. self._browser.document().defaultStyleSheet() +
  33. "span {white-space:pre;}")
  34. self._edit = _ExpandableTextEdit(self, self)
  35. self._edit.historyNext.connect(self._on_history_next)
  36. self._edit.historyPrev.connect(self._on_history_prev)
  37. self._edit.setFocus()
  38. self.setFocusProxy(self._edit)
  39. layout = QVBoxLayout(self)
  40. layout.setSpacing(0)
  41. layout.setContentsMargins(0, 0, 0, 0)
  42. layout.addWidget(self._browser)
  43. layout.addWidget(self._edit)
  44. self._history = [''] # current empty line
  45. self._historyIndex = 0
  46. def open_proccessing(self, detail=None):
  47. """
  48. Open processing and disable using shell commands again until all commands are finished
  49. :param detail: text detail about what is currently called from TCL to python
  50. :return: None
  51. """
  52. self._edit.setTextColor(Qt.white)
  53. self._edit.setTextBackgroundColor(Qt.darkGreen)
  54. if detail is None:
  55. self._edit.setPlainText(_("...proccessing..."))
  56. else:
  57. self._edit.setPlainText('%s [%s]' % (_("...proccessing..."), detail))
  58. self._edit.setDisabled(True)
  59. self._edit.setFocus()
  60. def close_proccessing(self):
  61. """
  62. Close processing and enable using shell commands again
  63. :return:
  64. """
  65. self._edit.setTextColor(Qt.black)
  66. self._edit.setTextBackgroundColor(Qt.white)
  67. self._edit.setPlainText('')
  68. self._edit.setDisabled(False)
  69. self._edit.setFocus()
  70. def _append_to_browser(self, style, text):
  71. """
  72. Convert text to HTML for inserting it to browser
  73. """
  74. assert style in ('in', 'out', 'err', 'warning', 'success', 'selected')
  75. text = html.escape(text)
  76. text = text.replace('\n', '<br/>')
  77. if style == 'in':
  78. text = '<span style="font-weight: bold;">%s</span>' % text
  79. elif style == 'err':
  80. text = '<span style="font-weight: bold; color: red;">%s</span>' % text
  81. elif style == 'warning':
  82. text = '<span style="font-weight: bold; color: #f4b642;">%s</span>' % text
  83. elif style == 'success':
  84. text = '<span style="font-weight: bold; color: #084400;">%s</span>' % text
  85. elif style == 'selected':
  86. text = ''
  87. else:
  88. text = '<span>%s</span>' % text # without span <br/> is ignored!!!
  89. scrollbar = self._browser.verticalScrollBar()
  90. old_value = scrollbar.value()
  91. scrollattheend = old_value == scrollbar.maximum()
  92. self._browser.moveCursor(QTextCursor.End)
  93. self._browser.insertHtml(text)
  94. """TODO When user enters second line to the input, and input is resized, scrollbar changes its position
  95. and stops moving. As quick fix of this problem, now we always scroll down when add new text.
  96. To fix it correctly, scroll to the bottom, if before input has been resized,
  97. scrollbar was in the bottom, and remove next line
  98. """
  99. scrollattheend = True
  100. if scrollattheend:
  101. scrollbar.setValue(scrollbar.maximum())
  102. else:
  103. scrollbar.setValue(old_value)
  104. def exec_current_command(self):
  105. """
  106. Save current command in the history. Append it to the log. Clear edit line
  107. Re-implement in the child classes to actually execute command
  108. """
  109. text = str(self._edit.toPlainText())
  110. # in Windows replace all backslash symbols '\' with '\\' slash because Windows paths are made with backslash
  111. # and in Python single slash is the escape symbol
  112. if sys.platform == 'win32':
  113. text = text.replace('\\', '\\\\')
  114. self._append_to_browser('in', '> ' + text + '\n')
  115. if len(self._history) < 2 or self._history[-2] != text: # don't insert duplicating items
  116. try:
  117. if text[-1] == '\n':
  118. self._history.insert(-1, text[:-1])
  119. else:
  120. self._history.insert(-1, text)
  121. except IndexError:
  122. return
  123. self._historyIndex = len(self._history) - 1
  124. self._history[-1] = ''
  125. self._edit.clear()
  126. if not text[-1] == '\n':
  127. text += '\n'
  128. self.child_exec_command(text)
  129. def child_exec_command(self, text):
  130. """
  131. Re-implement in the child classes
  132. """
  133. pass
  134. def add_line_break_to_input(self):
  135. self._edit.textCursor().insertText('\n')
  136. def append_output(self, text):
  137. """
  138. Append text to output widget
  139. """
  140. self._append_to_browser('out', text)
  141. def append_success(self, text):
  142. """Appent text to output widget
  143. """
  144. self._append_to_browser('success', text)
  145. def append_selected(self, text):
  146. """Appent text to output widget
  147. """
  148. self._append_to_browser('selected', text)
  149. def append_warning(self, text):
  150. """Appent text to output widget
  151. """
  152. self._append_to_browser('warning', text)
  153. def append_error(self, text):
  154. """Appent error text to output widget. Text is drawn with red background
  155. """
  156. self._append_to_browser('err', text)
  157. def is_command_complete(self, text):
  158. """
  159. Executed by _ExpandableTextEdit. Reimplement this function in the child classes.
  160. """
  161. return True
  162. def browser(self):
  163. return self._browser
  164. def _on_history_next(self):
  165. """
  166. Down pressed, show next item from the history
  167. """
  168. if (self._historyIndex + 1) < len(self._history):
  169. self._historyIndex += 1
  170. self._edit.setPlainText(self._history[self._historyIndex])
  171. self._edit.moveCursor(QTextCursor.End)
  172. def _on_history_prev(self):
  173. """
  174. Up pressed, show previous item from the history
  175. """
  176. if self._historyIndex > 0:
  177. if self._historyIndex == (len(self._history) - 1):
  178. self._history[-1] = self._edit.toPlainText()
  179. self._historyIndex -= 1
  180. self._edit.setPlainText(self._history[self._historyIndex])
  181. self._edit.moveCursor(QTextCursor.End)
  182. class FCShell(TermWidget):
  183. def __init__(self, sysShell, version, *args):
  184. TermWidget.__init__(self, version, *args)
  185. self._sysShell = sysShell
  186. def is_command_complete(self, text):
  187. def skipQuotes(text):
  188. quote = text[0]
  189. text = text[1:]
  190. endIndex = str(text).index(quote)
  191. return text[endIndex:]
  192. while text:
  193. if text[0] in ('"', "'"):
  194. try:
  195. text = skipQuotes(text)
  196. except ValueError:
  197. return False
  198. text = text[1:]
  199. return True
  200. def child_exec_command(self, text):
  201. self._sysShell.exec_command(text)