ToolShell.py 7.8 KB

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