bec-widgets 0.110.0__py3-none-any.whl → 0.112.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,24 +1,24 @@
1
1
  """
2
- BECConsole is a Qt widget that runs a Bash shell. The widget can be used and
3
- embedded like any other Qt widget.
2
+ BECConsole is a Qt widget that runs a Bash shell.
4
3
 
5
- BECConsole is powered by Pyte, a Python based terminal emulator
4
+ BECConsole VT100 emulation is powered by Pyte,
6
5
  (https://github.com/selectel/pyte).
7
6
  """
8
7
 
8
+ import collections
9
9
  import fcntl
10
10
  import html
11
11
  import os
12
12
  import pty
13
- import subprocess
14
13
  import sys
15
- import threading
16
14
 
17
15
  import pyte
16
+ from pyte.screens import History
18
17
  from qtpy import QtCore, QtGui, QtWidgets
19
- from qtpy.QtCore import QSize, QSocketNotifier, Qt
18
+ from qtpy.QtCore import Property as pyqtProperty
19
+ from qtpy.QtCore import QSize, QSocketNotifier, Qt, QTimer
20
20
  from qtpy.QtCore import Signal as pyqtSignal
21
- from qtpy.QtGui import QClipboard, QTextCursor
21
+ from qtpy.QtGui import QClipboard, QColor, QPalette, QTextCursor
22
22
  from qtpy.QtWidgets import QApplication, QHBoxLayout, QScrollBar, QSizePolicy
23
23
 
24
24
  from bec_widgets.qt_utils.error_popups import SafeSlot as Slot
@@ -137,13 +137,52 @@ def QtKeyToAscii(event):
137
137
 
138
138
 
139
139
  class Screen(pyte.HistoryScreen):
140
- def __init__(self, stdin_fd, numColumns, numLines, historyLength):
141
- super().__init__(numColumns, numLines, historyLength, ratio=1 / numLines)
140
+ def __init__(self, stdin_fd, cols, rows, historyLength):
141
+ super().__init__(cols, rows, historyLength, ratio=1 / rows)
142
142
  self._fd = stdin_fd
143
143
 
144
144
  def write_process_input(self, data):
145
- """Response to CPR request for example"""
146
- os.write(self._fd, data.encode("utf-8"))
145
+ """Response to CPR request (for example),
146
+ this can be for other requests
147
+ """
148
+ try:
149
+ os.write(self._fd, data.encode("utf-8"))
150
+ except (IOError, OSError):
151
+ pass
152
+
153
+ def resize(self, lines, columns):
154
+ lines = lines or self.lines
155
+ columns = columns or self.columns
156
+
157
+ if lines == self.lines and columns == self.columns:
158
+ return # No changes.
159
+
160
+ self.dirty.clear()
161
+ self.dirty.update(range(lines))
162
+
163
+ self.save_cursor()
164
+ if lines < self.lines:
165
+ if lines <= self.cursor.y:
166
+ nlines_to_move_up = self.lines - lines
167
+ for i in range(nlines_to_move_up):
168
+ line = self.buffer[i] # .pop(0)
169
+ self.history.top.append(line)
170
+ self.cursor_position(0, 0)
171
+ self.delete_lines(nlines_to_move_up)
172
+ self.restore_cursor()
173
+ self.cursor.y -= nlines_to_move_up
174
+ else:
175
+ self.restore_cursor()
176
+
177
+ self.lines, self.columns = lines, columns
178
+ self.history = History(
179
+ self.history.top,
180
+ self.history.bottom,
181
+ 1 / self.lines,
182
+ self.history.size,
183
+ self.history.position,
184
+ )
185
+ self.set_margins()
147
186
 
148
187
 
149
188
  class Backend(QtCore.QObject):
@@ -155,17 +194,17 @@ class Backend(QtCore.QObject):
155
194
  """
156
195
 
157
196
  # Signals to communicate with ``_TerminalWidget``.
158
- startWork = pyqtSignal()
159
197
  dataReady = pyqtSignal(object)
198
+ processExited = pyqtSignal()
160
199
 
161
- def __init__(self, fd, numColumns, numLines):
200
+ def __init__(self, fd, cols, rows):
162
201
  super().__init__()
163
202
 
164
203
  # File descriptor that connects to Bash process.
165
204
  self.fd = fd
166
205
 
167
206
  # Setup Pyte (hard coded display size for now).
168
- self.screen = Screen(self.fd, numColumns, numLines, 10000)
207
+ self.screen = Screen(self.fd, cols, rows, 10000)
169
208
  self.stream = pyte.ByteStream()
170
209
  self.stream.attach(self.screen)
171
210
 
@@ -174,12 +213,14 @@ class Backend(QtCore.QObject):
174
213
 
175
214
  def _fd_readable(self):
176
215
  """
177
- Poll the Bash output, run it through Pyte, and notify the main applet.
216
+ Poll the Bash output, run it through Pyte, and notify
178
217
  """
179
218
  # Read the shell output until the file descriptor is closed.
180
219
  try:
181
220
  out = os.read(self.fd, 2**16)
182
221
  except OSError:
222
+ self.processExited.emit()
223
+ self.notifier.setEnabled(False)
183
224
  return
184
225
 
185
226
  # Feed output into Pyte's state machine and send the new screen
@@ -188,93 +229,256 @@ class Backend(QtCore.QObject):
188
229
  self.dataReady.emit(self.screen)
189
230
 
190
231
 
191
- class BECConsole(QtWidgets.QScrollArea):
232
+ class BECConsole(QtWidgets.QWidget):
192
233
  """Container widget for the terminal text area"""
193
234
 
194
- def __init__(self, parent=None, numLines=50, numColumns=125):
195
- super().__init__(parent)
196
-
197
- self.innerWidget = QtWidgets.QWidget(self)
198
- QHBoxLayout(self.innerWidget)
199
- self.innerWidget.layout().setContentsMargins(0, 0, 0, 0)
235
+ ICON_NAME = "terminal"
200
236
 
201
- self.term = _TerminalWidget(self.innerWidget, numLines, numColumns)
202
- self.term.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
203
- self.innerWidget.layout().addWidget(self.term)
204
-
205
- self.scroll_bar = QScrollBar(Qt.Vertical, self.term)
206
- self.innerWidget.layout().addWidget(self.scroll_bar)
207
-
208
- self.term.set_scroll(self.scroll_bar)
237
+ def __init__(self, parent=None, cols=132):
238
+ super().__init__(parent)
209
239
 
210
- self.setWidget(self.innerWidget)
240
+ self.term = _TerminalWidget(self, cols, rows=43)
241
+ self.scroll_bar = QScrollBar(Qt.Vertical, self)
242
+ # self.scroll_bar.hide()
243
+ layout = QHBoxLayout(self)
244
+ layout.addWidget(self.term)
245
+ layout.addWidget(self.scroll_bar)
246
+ layout.setAlignment(Qt.AlignLeft | Qt.AlignTop)
247
+ layout.setContentsMargins(0, 0, 0, 0)
248
+ self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding)
249
+
250
+ pal = QPalette()
251
+ self.set_bgcolor(pal.window().color())
252
+ self.set_fgcolor(pal.windowText().color())
253
+ self.term.set_scroll_bar(self.scroll_bar)
254
+ self.set_cmd("bec --nogui")
255
+
256
+ self._check_designer_timer = QTimer()
257
+ self._check_designer_timer.timeout.connect(self.check_designer)
258
+ self._check_designer_timer.start(1000)
211
259
 
212
- def start(self, cmd=["bec", "--nogui"], deactivate_ctrl_d=True):
260
+ def minimumSizeHint(self):
261
+ size = self.term.sizeHint()
262
+ size.setWidth(size.width() + self.scroll_bar.width())
263
+ return size
264
+
265
+ def sizeHint(self):
266
+ return self.minimumSizeHint()
267
+
268
+ def check_designer(self, calls={"n": 0}):
269
+ calls["n"] += 1
270
+ if self.term.fd is not None:
271
+ # already started
272
+ self._check_designer_timer.stop()
273
+ elif self.window().windowTitle().endswith("[Preview]"):
274
+ # assuming Designer preview -> start
275
+ self._check_designer_timer.stop()
276
+ self.term.start()
277
+ elif calls["n"] >= 3:
278
+ # assuming not in Designer -> stop checking
279
+ self._check_designer_timer.stop()
280
+
281
+ def get_rows(self):
282
+ return self.term.rows
283
+
284
+ def set_rows(self, rows):
285
+ self.term.rows = rows
286
+ self.adjustSize()
287
+ self.updateGeometry()
288
+
289
+ def get_cols(self):
290
+ return self.term.cols
291
+
292
+ def set_cols(self, cols):
293
+ self.term.cols = cols
294
+ self.adjustSize()
295
+ self.updateGeometry()
296
+
297
+ def get_bgcolor(self):
298
+ return QColor.fromString(self.term.bg_color)
299
+
300
+ def set_bgcolor(self, color):
301
+ self.term.bg_color = color.name(QColor.HexRgb)
302
+
303
+ def get_fgcolor(self):
304
+ return QColor.fromString(self.term.fg_color)
305
+
306
+ def set_fgcolor(self, color):
307
+ self.term.fg_color = color.name(QColor.HexRgb)
308
+
309
+ def get_cmd(self):
310
+ return self.term._cmd
311
+
312
+ def set_cmd(self, cmd):
213
313
  self.term._cmd = cmd
314
+ if self.term.fd is None:
315
+ # not started yet
316
+ self.term.clear()
317
+ self.term.appendHtml(f"<h2>BEC Console - {repr(cmd)}</h2>")
318
+
319
+ def start(self, deactivate_ctrl_d=True):
214
320
  self.term.start(deactivate_ctrl_d=deactivate_ctrl_d)
215
321
 
216
322
  def push(self, text):
217
323
  """Push some text to the terminal"""
218
324
  return self.term.push(text)
219
325
 
326
+ cols = pyqtProperty(int, get_cols, set_cols)
327
+ rows = pyqtProperty(int, get_rows, set_rows)
328
+ bgcolor = pyqtProperty(QColor, get_bgcolor, set_bgcolor)
329
+ fgcolor = pyqtProperty(QColor, get_fgcolor, set_fgcolor)
330
+ cmd = pyqtProperty(str, get_cmd, set_cmd)
331
+
220
332
 
221
333
  class _TerminalWidget(QtWidgets.QPlainTextEdit):
222
334
  """
223
335
  Start ``Backend`` process and render Pyte output as text.
224
336
  """
225
337
 
226
- def __init__(self, parent, numColumns=125, numLines=50, **kwargs):
227
- super().__init__(parent)
228
-
338
+ def __init__(self, parent, cols=125, rows=50, **kwargs):
229
339
  # file descriptor to communicate with the subprocess
230
340
  self.fd = None
231
341
  self.backend = None
232
- self.lock = threading.Lock()
233
342
  # command to execute
234
- self._cmd = None
343
+ self._cmd = ""
235
344
  # should ctrl-d be deactivated ? (prevent Python exit)
236
345
  self._deactivate_ctrl_d = False
237
346
 
347
+ # Default colors
348
+ pal = QPalette()
349
+ self._fg_color = pal.text().color().name()
350
+ self._bg_color = pal.base().color().name()
351
+
238
352
  # Specify the terminal size in terms of lines and columns.
239
- self.numLines = numLines
240
- self.numColumns = numColumns
241
- self.output = [""] * numLines
353
+ self._rows = rows
354
+ self._cols = cols
355
+ self.output = collections.deque()
356
+
357
+ super().__init__(parent)
358
+
359
+ self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Expanding)
360
+
361
+ # Disable default scrollbars (we use our own, to be set via .set_scroll_bar())
362
+ self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
363
+ self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
364
+ self.scroll_bar = None
242
365
 
243
366
  # Use Monospace fonts and disable line wrapping.
244
367
  self.setFont(QtGui.QFont("Courier", 9))
245
368
  self.setFont(QtGui.QFont("Monospace"))
246
369
  self.setLineWrapMode(QtWidgets.QPlainTextEdit.NoWrap)
247
-
248
- # Disable vertical scrollbar (we use our own, to be set via .set_scroll())
249
- self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
250
-
251
370
  fmt = QtGui.QFontMetrics(self.font())
252
- self._char_width = fmt.width("w")
253
- self._char_height = fmt.height()
254
- self.setCursorWidth(self._char_width)
255
- # self.setStyleSheet("QPlainTextEdit { color: #ffff00; background-color: #303030; } ");
371
+ char_width = fmt.width("w")
372
+ self.setCursorWidth(char_width)
373
+
374
+ self.adjustSize()
375
+ self.updateGeometry()
376
+ self.update_stylesheet()
377
+
378
+ @property
379
+ def bg_color(self):
380
+ return self._bg_color
381
+
382
+ @bg_color.setter
383
+ def bg_color(self, hexcolor):
384
+ self._bg_color = hexcolor
385
+ self.update_stylesheet()
386
+
387
+ @property
388
+ def fg_color(self):
389
+ return self._fg_color
390
+
391
+ @fg_color.setter
392
+ def fg_color(self, hexcolor):
393
+ self._fg_color = hexcolor
394
+ self.update_stylesheet()
395
+
396
+ def update_stylesheet(self):
397
+ self.setStyleSheet(
398
+ f"QPlainTextEdit {{ border: 0; color: {self._fg_color}; background-color: {self._bg_color}; }} "
399
+ )
400
+
401
+ @property
402
+ def rows(self):
403
+ return self._rows
404
+
405
+ @rows.setter
406
+ def rows(self, rows: int):
407
+ if self.backend is None:
408
+ # not initialized yet, ok to change
409
+ self._rows = rows
410
+ self.adjustSize()
411
+ self.updateGeometry()
412
+ else:
413
+ raise RuntimeError("Cannot change rows after console is started.")
414
+
415
+ @property
416
+ def cols(self):
417
+ return self._cols
418
+
419
+ @cols.setter
420
+ def cols(self, cols: int):
421
+ if self.fd is None:
422
+ # not initialized yet, ok to change
423
+ self._cols = cols
424
+ self.adjustSize()
425
+ self.updateGeometry()
426
+ else:
427
+ raise RuntimeError("Cannot change cols after console is started.")
256
428
 
257
- def start(self, deactivate_ctrl_d=False):
429
+ def start(self, deactivate_ctrl_d: bool = False):
258
430
  self._deactivate_ctrl_d = deactivate_ctrl_d
259
431
 
432
+ self.update_term_size()
433
+
260
434
  # Start the Bash process
261
- self.fd = self.forkShell()
435
+ self.fd = self.fork_shell()
262
436
 
263
- # Create the ``Backend`` object
264
- self.backend = Backend(self.fd, self.numColumns, self.numLines)
265
- self.backend.dataReady.connect(self.dataReady)
437
+ if self.fd:
438
+ # Create the ``Backend`` object
439
+ self.backend = Backend(self.fd, self.cols, self.rows)
440
+ self.backend.dataReady.connect(self.data_ready)
441
+ self.backend.processExited.connect(self.process_exited)
442
+ else:
443
+ self.process_exited()
266
444
 
267
- def minimumSizeHint(self):
268
- width = self._char_width * self.numColumns
269
- height = self._char_height * self.numLines
270
- return QSize(width, height + 20)
445
+ def process_exited(self):
446
+ self.fd = None
447
+ self.clear()
448
+ self.appendHtml(f"<br><h2>{repr(self._cmd)} - Process exited.</h2>")
449
+ self.setReadOnly(True)
271
450
 
272
- def set_scroll(self, scroll):
273
- self.scroll = scroll
274
- self.scroll.setMinimum(0)
275
- self.scroll.valueChanged.connect(self.scroll_value_change)
451
+ def data_ready(self, screen):
452
+ """Handle new screen: redraw, set scroll bar max and slider, move cursor to its position
276
453
 
277
- def scroll_value_change(self, value, old={"value": 0}):
454
+ This method is triggered via a signal from ``Backend``.
455
+ """
456
+ self.redraw_screen()
457
+ self.adjust_scroll_bar()
458
+ self.move_cursor()
459
+
460
+ def minimumSizeHint(self):
461
+ """Return minimum size for current cols and rows"""
462
+ fmt = QtGui.QFontMetrics(self.font())
463
+ char_width = fmt.width("w")
464
+ char_height = fmt.height()
465
+ width = char_width * self.cols
466
+ height = char_height * self.rows
467
+ return QSize(width, height)
468
+
469
+ def sizeHint(self):
470
+ return self.minimumSizeHint()
471
+
472
+ def set_scroll_bar(self, scroll_bar):
473
+ self.scroll_bar = scroll_bar
474
+ self.scroll_bar.setMinimum(0)
475
+ self.scroll_bar.valueChanged.connect(self.scroll_value_change)
476
+
477
+ def scroll_value_change(self, value, old={"value": -1}):
478
+ if self.backend is None:
479
+ return
480
+ if old["value"] == -1:
481
+ old["value"] = self.scroll_bar.maximum()
278
482
  if value <= old["value"]:
279
483
  # scroll up
280
484
  # value is number of lines from the start
@@ -288,13 +492,35 @@ class _TerminalWidget(QtWidgets.QPlainTextEdit):
288
492
  for i in range(nlines):
289
493
  self.backend.screen.next_page()
290
494
  old["value"] = value
291
- self.dataReady(self.backend.screen, reset_scroll=False)
495
+ self.redraw_screen()
496
+
497
+ def adjust_scroll_bar(self):
498
+ sb = self.scroll_bar
499
+ sb.valueChanged.disconnect(self.scroll_value_change)
500
+ tmp = len(self.backend.screen.history.top) + len(self.backend.screen.history.bottom)
501
+ sb.setMaximum(tmp if tmp > 0 else 0)
502
+ sb.setSliderPosition(tmp if tmp > 0 else 0)
503
+ # if tmp > 0:
504
+ # # show scrollbar, but delayed - prevent recursion with widget size change
505
+ # QTimer.singleShot(0, scrollbar.show)
506
+ # else:
507
+ # QTimer.singleShot(0, scrollbar.hide)
508
+ sb.valueChanged.connect(self.scroll_value_change)
509
+
510
+ def write(self, data):
511
+ try:
512
+ os.write(self.fd, data)
513
+ except (IOError, OSError):
514
+ self.process_exited()
292
515
 
293
516
  @Slot(object)
294
517
  def keyPressEvent(self, event):
295
518
  """
296
519
  Redirect all keystrokes to the terminal process.
297
520
  """
521
+ if self.fd is None:
522
+ # not started
523
+ return
298
524
  # Convert the Qt key to the correct ASCII code.
299
525
  if (
300
526
  self._deactivate_ctrl_d
@@ -311,15 +537,17 @@ class _TerminalWidget(QtWidgets.QPlainTextEdit):
311
537
  # MacOS only: CMD-V handling
312
538
  self._push_clipboard()
313
539
  elif code is not None:
314
- os.write(self.fd, code)
540
+ self.write(code)
315
541
 
316
542
  def push(self, text):
317
543
  """
318
544
  Write 'text' to terminal
319
545
  """
320
- os.write(self.fd, text.encode("utf-8"))
546
+ self.write(text.encode("utf-8"))
321
547
 
322
548
  def contextMenuEvent(self, event):
549
+ if self.fd is None:
550
+ return
323
551
  menu = self.createStandardContextMenu()
324
552
  for action in menu.actions():
325
553
  # remove all actions except copy and paste
@@ -341,7 +569,20 @@ class _TerminalWidget(QtWidgets.QPlainTextEdit):
341
569
  clipboard = QApplication.instance().clipboard()
342
570
  self.push(clipboard.text())
343
571
 
572
+ def move_cursor(self):
573
+ textCursor = self.textCursor()
574
+ textCursor.setPosition(0)
575
+ textCursor.movePosition(
576
+ QTextCursor.Down, QTextCursor.MoveAnchor, self.backend.screen.cursor.y
577
+ )
578
+ textCursor.movePosition(
579
+ QTextCursor.Right, QTextCursor.MoveAnchor, self.backend.screen.cursor.x
580
+ )
581
+ self.setTextCursor(textCursor)
582
+
344
583
  def mouseReleaseEvent(self, event):
584
+ if self.fd is None:
585
+ return
345
586
  if event.button() == Qt.MiddleButton:
346
587
  # push primary selection buffer ("mouse clipboard") to terminal
347
588
  clipboard = QApplication.instance().clipboard()
@@ -355,34 +596,34 @@ class _TerminalWidget(QtWidgets.QPlainTextEdit):
355
596
  # mouse was used to select text -> nothing to do
356
597
  pass
357
598
  else:
358
- # a simple 'click', make cursor going to end
359
- textCursor.setPosition(0)
360
- textCursor.movePosition(
361
- QTextCursor.Down, QTextCursor.MoveAnchor, self.backend.screen.cursor.y
362
- )
363
- textCursor.movePosition(
364
- QTextCursor.Right, QTextCursor.MoveAnchor, self.backend.screen.cursor.x
365
- )
366
- self.setTextCursor(textCursor)
367
- self.ensureCursorVisible()
599
+ # a simple 'click', move scrollbar to end
600
+ self.scroll_bar.setSliderPosition(self.scroll_bar.maximum())
601
+ self.move_cursor()
368
602
  return None
369
603
  return super().mouseReleaseEvent(event)
370
604
 
371
- def dataReady(self, screenData, reset_scroll=True):
605
+ def redraw_screen(self):
372
606
  """
373
- Render the new screen as text into the widget.
374
-
375
- This method is triggered via a signal from ``Backend``.
607
+ Render the screen as formatted text into the widget.
376
608
  """
377
- with self.lock:
378
- # Clear the widget
609
+ screen = self.backend.screen
610
+
611
+ # Clear the widget
612
+ if screen.dirty:
379
613
  self.clear()
614
+ while len(self.output) < (max(screen.dirty) + 1):
615
+ self.output.append("")
616
+ while len(self.output) > (max(screen.dirty) + 1):
617
+ self.output.pop()
380
618
 
381
619
  # Prepare the HTML output
382
- for line_no in screenData.dirty:
620
+ for line_no in screen.dirty:
383
621
  line = text = ""
384
622
  style = old_style = ""
385
- for ch in screenData.buffer[line_no].values():
623
+ old_idx = 0
624
+ for idx, ch in screen.buffer[line_no].items():
625
+ text += " " * (idx - old_idx - 1)
626
+ old_idx = idx
386
627
  style = f"{'background-color:%s;' % ansi_colors.get(ch.bg, ansi_colors['black']) if ch.bg!='default' else ''}{'color:%s;' % ansi_colors.get(ch.fg, ansi_colors['white']) if ch.fg!='default' else ''}{'font-weight:bold;' if ch.bold else ''}{'font-style:italic;' if ch.italics else ''}"
387
628
  if style != old_style:
388
629
  if old_style:
@@ -396,45 +637,47 @@ class _TerminalWidget(QtWidgets.QPlainTextEdit):
396
637
  line += f"<span style={repr(style)}>{html.escape(text, quote=True)}</span>"
397
638
  else:
398
639
  line += html.escape(text, quote=True)
640
+ # do a check at the cursor position:
641
+ # it is possible x pos > output line length,
642
+ # for example if last escape codes are "cursor forward" past end of text,
643
+ # like IPython does for "..." prompt (in a block, like "for" loop or "while" for example)
644
+ # In this case, cursor is at 12 but last text output is at 8 -> insert spaces
645
+ if line_no == screen.cursor.y:
646
+ llen = len(screen.buffer[line_no])
647
+ if llen < screen.cursor.x:
648
+ line += " " * (screen.cursor.x - llen)
399
649
  self.output[line_no] = line
400
650
  # fill the text area with HTML contents in one go
401
651
  self.appendHtml(f"<pre>{chr(10).join(self.output)}</pre>")
402
- # done updates, all clean
403
- screenData.dirty.clear()
652
+ # did updates, all clean
653
+ screen.dirty.clear()
404
654
 
405
- # Activate cursor
406
- textCursor = self.textCursor()
407
- textCursor.setPosition(0)
408
- textCursor.movePosition(QTextCursor.Down, QTextCursor.MoveAnchor, screenData.cursor.y)
409
- textCursor.movePosition(QTextCursor.Right, QTextCursor.MoveAnchor, screenData.cursor.x)
410
- self.setTextCursor(textCursor)
411
- self.ensureCursorVisible()
412
-
413
- # manage scroll
414
- if reset_scroll:
415
- self.scroll.valueChanged.disconnect(self.scroll_value_change)
416
- tmp = len(self.backend.screen.history.top) + len(self.backend.screen.history.bottom)
417
- self.scroll.setMaximum(tmp if tmp > 0 else 0)
418
- self.scroll.setSliderPosition(len(self.backend.screen.history.top))
419
- self.scroll.valueChanged.connect(self.scroll_value_change)
420
-
421
- # def resizeEvent(self, event):
422
- # with self.lock:
423
- # self.numColumns = int(self.width() / self._char_width)
424
- # self.numLines = int(self.height() / self._char_height)
425
- # self.output = [""] * self.numLines
426
- # print("RESIZING TO", self.numColumns, "x", self.numLines)
427
- # self.backend.screen.resize(self.numLines, self.numColumns)
655
+ def update_term_size(self):
656
+ fmt = QtGui.QFontMetrics(self.font())
657
+ char_width = fmt.width("w")
658
+ char_height = fmt.height()
659
+ self._cols = int(self.width() / char_width)
660
+ self._rows = int(self.height() / char_height)
661
+
662
+ def resizeEvent(self, event):
663
+ self.update_term_size()
664
+ if self.fd:
665
+ self.backend.screen.resize(self._rows, self._cols)
666
+ self.redraw_screen()
667
+ self.adjust_scroll_bar()
668
+ self.move_cursor()
428
669
 
429
670
  def wheelEvent(self, event):
671
+ if not self.fd:
672
+ return
430
673
  y = event.angleDelta().y()
431
674
  if y > 0:
432
675
  self.backend.screen.prev_page()
433
676
  else:
434
677
  self.backend.screen.next_page()
435
- self.dataReady(self.backend.screen, reset_scroll=False)
678
+ self.redraw_screen()
436
679
 
437
- def forkShell(self):
680
+ def fork_shell(self):
438
681
  """
439
682
  Fork the current process and execute bec in shell.
440
683
  """
@@ -443,32 +686,30 @@ class _TerminalWidget(QtWidgets.QPlainTextEdit):
443
686
  except (IOError, OSError):
444
687
  return False
445
688
  if pid == 0:
446
- # Safe way to make it work under BSD and Linux
447
689
  try:
448
690
  ls = os.environ["LANG"].split(".")
449
691
  except KeyError:
450
692
  ls = []
451
693
  if len(ls) < 2:
452
694
  ls = ["en_US", "UTF-8"]
695
+ os.putenv("COLUMNS", str(self.cols))
696
+ os.putenv("LINES", str(self.rows))
697
+ os.putenv("TERM", "linux")
698
+ os.putenv("LANG", ls[0] + ".UTF-8")
699
+ if not self._cmd:
700
+ self._cmd = os.environ["SHELL"]
701
+ cmd = self._cmd
702
+ if isinstance(cmd, str):
703
+ cmd = cmd.split()
453
704
  try:
454
- os.putenv("COLUMNS", str(self.numColumns))
455
- os.putenv("LINES", str(self.numLines))
456
- os.putenv("TERM", "linux")
457
- os.putenv("LANG", ls[0] + ".UTF-8")
458
- if isinstance(self._cmd, str):
459
- os.execvp(self._cmd, self._cmd)
460
- else:
461
- os.execvp(self._cmd[0], self._cmd)
462
- # print "child_pid", child_pid, sts
705
+ os.execvp(cmd[0], cmd)
463
706
  except (IOError, OSError):
464
707
  pass
465
- # self.proc_finish(sid)
466
708
  os._exit(0)
467
709
  else:
468
710
  # We are in the parent process.
469
711
  # Set file control
470
712
  fcntl.fcntl(fd, fcntl.F_SETFL, os.O_NONBLOCK)
471
- print("Spawned Bash shell (PID {})".format(pid))
472
713
  return fd
473
714
 
474
715
 
@@ -478,17 +719,13 @@ if __name__ == "__main__":
478
719
 
479
720
  from qtpy import QtGui, QtWidgets
480
721
 
481
- # Terminal size in characters.
482
- numLines = 25
483
- numColumns = 100
484
-
485
- # Create the Qt application and QBash instance.
722
+ # Create the Qt application and console.
486
723
  app = QtWidgets.QApplication([])
487
724
  mainwin = QtWidgets.QMainWindow()
488
- title = "BECConsole ({}x{})".format(numColumns, numLines)
725
+ title = "BECConsole"
489
726
  mainwin.setWindowTitle(title)
490
727
 
491
- console = BECConsole(mainwin, numColumns, numLines)
728
+ console = BECConsole(mainwin)
492
729
  mainwin.setCentralWidget(console)
493
730
  console.start()
494
731