bec-widgets 0.76.1__py3-none-any.whl → 0.77.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.
Files changed (38) hide show
  1. CHANGELOG.md +36 -38
  2. PKG-INFO +2 -1
  3. bec_widgets/cli/client.py +73 -196
  4. bec_widgets/examples/jupyter_console/jupyter_console_window.py +25 -4
  5. bec_widgets/utils/bec_connector.py +66 -8
  6. bec_widgets/utils/colors.py +38 -0
  7. bec_widgets/utils/yaml_dialog.py +27 -3
  8. bec_widgets/widgets/console/console.py +496 -0
  9. bec_widgets/widgets/dock/dock.py +2 -2
  10. bec_widgets/widgets/dock/dock_area.py +2 -2
  11. bec_widgets/widgets/figure/figure.py +149 -195
  12. bec_widgets/widgets/figure/plots/image/image.py +62 -49
  13. bec_widgets/widgets/figure/plots/image/image_item.py +4 -3
  14. bec_widgets/widgets/figure/plots/motor_map/motor_map.py +98 -29
  15. bec_widgets/widgets/figure/plots/plot_base.py +1 -1
  16. bec_widgets/widgets/figure/plots/waveform/waveform.py +7 -8
  17. bec_widgets/widgets/figure/plots/waveform/waveform_curve.py +2 -2
  18. bec_widgets/widgets/ring_progress_bar/ring.py +3 -3
  19. bec_widgets/widgets/ring_progress_bar/ring_progress_bar.py +3 -3
  20. {bec_widgets-0.76.1.dist-info → bec_widgets-0.77.0.dist-info}/METADATA +2 -1
  21. {bec_widgets-0.76.1.dist-info → bec_widgets-0.77.0.dist-info}/RECORD +38 -37
  22. pyproject.toml +2 -1
  23. tests/end-2-end/test_bec_dock_rpc_e2e.py +16 -16
  24. tests/end-2-end/test_bec_figure_rpc_e2e.py +7 -7
  25. tests/end-2-end/test_rpc_register_e2e.py +8 -8
  26. tests/unit_tests/client_mocks.py +1 -0
  27. tests/unit_tests/test_bec_figure.py +49 -26
  28. tests/unit_tests/test_bec_motor_map.py +179 -41
  29. tests/unit_tests/test_color_validation.py +15 -0
  30. tests/unit_tests/test_device_input_base.py +1 -1
  31. tests/unit_tests/test_device_input_widgets.py +2 -0
  32. tests/unit_tests/test_motor_control.py +5 -4
  33. tests/unit_tests/test_plot_base.py +3 -3
  34. tests/unit_tests/test_waveform1d.py +18 -17
  35. tests/unit_tests/test_yaml_dialog.py +7 -7
  36. {bec_widgets-0.76.1.dist-info → bec_widgets-0.77.0.dist-info}/WHEEL +0 -0
  37. {bec_widgets-0.76.1.dist-info → bec_widgets-0.77.0.dist-info}/entry_points.txt +0 -0
  38. {bec_widgets-0.76.1.dist-info → bec_widgets-0.77.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,496 @@
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.
4
+
5
+ BECConsole is powered by Pyte, a Python based terminal emulator
6
+ (https://github.com/selectel/pyte).
7
+ """
8
+
9
+ import fcntl
10
+ import html
11
+ import os
12
+ import pty
13
+ import subprocess
14
+ import sys
15
+ import threading
16
+
17
+ import pyte
18
+ from qtpy import QtCore, QtGui, QtWidgets
19
+ from qtpy.QtCore import QSize, QSocketNotifier, Qt
20
+ from qtpy.QtCore import Signal as pyqtSignal
21
+ from qtpy.QtCore import Slot as pyqtSlot
22
+ from qtpy.QtGui import QClipboard, QTextCursor
23
+ from qtpy.QtWidgets import QApplication, QHBoxLayout, QScrollBar, QSizePolicy
24
+
25
+ ansi_colors = {
26
+ "black": "#000000",
27
+ "red": "#CD0000",
28
+ "green": "#00CD00",
29
+ "brown": "#996633", # Brown, replacing the yellow
30
+ "blue": "#0000EE",
31
+ "magenta": "#CD00CD",
32
+ "cyan": "#00CDCD",
33
+ "white": "#E5E5E5",
34
+ "brightblack": "#7F7F7F",
35
+ "brightred": "#FF0000",
36
+ "brightgreen": "#00FF00",
37
+ "brightyellow": "#FFFF00",
38
+ "brightblue": "#5C5CFF",
39
+ "brightmagenta": "#FF00FF",
40
+ "brightcyan": "#00FFFF",
41
+ "brightwhite": "#FFFFFF",
42
+ }
43
+
44
+ control_keys_mapping = {
45
+ QtCore.Qt.Key_A: b"\x01", # Ctrl-A
46
+ QtCore.Qt.Key_B: b"\x02", # Ctrl-B
47
+ QtCore.Qt.Key_C: b"\x03", # Ctrl-C
48
+ QtCore.Qt.Key_D: b"\x04", # Ctrl-D
49
+ QtCore.Qt.Key_E: b"\x05", # Ctrl-E
50
+ QtCore.Qt.Key_F: b"\x06", # Ctrl-F
51
+ QtCore.Qt.Key_G: b"\x07", # Ctrl-G (Bell)
52
+ QtCore.Qt.Key_H: b"\x08", # Ctrl-H (Backspace)
53
+ QtCore.Qt.Key_I: b"\x09", # Ctrl-I (Tab)
54
+ QtCore.Qt.Key_J: b"\x0A", # Ctrl-J (Line Feed)
55
+ QtCore.Qt.Key_K: b"\x0B", # Ctrl-K (Vertical Tab)
56
+ QtCore.Qt.Key_L: b"\x0C", # Ctrl-L (Form Feed)
57
+ QtCore.Qt.Key_M: b"\x0D", # Ctrl-M (Carriage Return)
58
+ QtCore.Qt.Key_N: b"\x0E", # Ctrl-N
59
+ QtCore.Qt.Key_O: b"\x0F", # Ctrl-O
60
+ QtCore.Qt.Key_P: b"\x10", # Ctrl-P
61
+ QtCore.Qt.Key_Q: b"\x11", # Ctrl-Q
62
+ QtCore.Qt.Key_R: b"\x12", # Ctrl-R
63
+ QtCore.Qt.Key_S: b"\x13", # Ctrl-S
64
+ QtCore.Qt.Key_T: b"\x14", # Ctrl-T
65
+ QtCore.Qt.Key_U: b"\x15", # Ctrl-U
66
+ QtCore.Qt.Key_V: b"\x16", # Ctrl-V
67
+ QtCore.Qt.Key_W: b"\x17", # Ctrl-W
68
+ QtCore.Qt.Key_X: b"\x18", # Ctrl-X
69
+ QtCore.Qt.Key_Y: b"\x19", # Ctrl-Y
70
+ QtCore.Qt.Key_Z: b"\x1A", # Ctrl-Z
71
+ QtCore.Qt.Key_Escape: b"\x1B", # Ctrl-Escape
72
+ QtCore.Qt.Key_Backslash: b"\x1C", # Ctrl-\
73
+ QtCore.Qt.Key_Underscore: b"\x1F", # Ctrl-_
74
+ }
75
+
76
+ normal_keys_mapping = {
77
+ QtCore.Qt.Key_Return: b"\n",
78
+ QtCore.Qt.Key_Space: b" ",
79
+ QtCore.Qt.Key_Enter: b"\n",
80
+ QtCore.Qt.Key_Tab: b"\t",
81
+ QtCore.Qt.Key_Backspace: b"\x08",
82
+ QtCore.Qt.Key_Home: b"\x47",
83
+ QtCore.Qt.Key_End: b"\x4f",
84
+ QtCore.Qt.Key_Left: b"\x02",
85
+ QtCore.Qt.Key_Up: b"\x10",
86
+ QtCore.Qt.Key_Right: b"\x06",
87
+ QtCore.Qt.Key_Down: b"\x0E",
88
+ QtCore.Qt.Key_PageUp: b"\x49",
89
+ QtCore.Qt.Key_PageDown: b"\x51",
90
+ QtCore.Qt.Key_F1: b"\x1b\x31",
91
+ QtCore.Qt.Key_F2: b"\x1b\x32",
92
+ QtCore.Qt.Key_F3: b"\x1b\x33",
93
+ QtCore.Qt.Key_F4: b"\x1b\x34",
94
+ QtCore.Qt.Key_F5: b"\x1b\x35",
95
+ QtCore.Qt.Key_F6: b"\x1b\x36",
96
+ QtCore.Qt.Key_F7: b"\x1b\x37",
97
+ QtCore.Qt.Key_F8: b"\x1b\x38",
98
+ QtCore.Qt.Key_F9: b"\x1b\x39",
99
+ QtCore.Qt.Key_F10: b"\x1b\x30",
100
+ QtCore.Qt.Key_F11: b"\x45",
101
+ QtCore.Qt.Key_F12: b"\x46",
102
+ }
103
+
104
+
105
+ def QtKeyToAscii(event):
106
+ """
107
+ Convert the Qt key event to the corresponding ASCII sequence for
108
+ the terminal. This works fine for standard alphanumerical characters, but
109
+ most other characters require terminal specific control sequences.
110
+
111
+ The conversion below works for TERM="linux" terminals.
112
+ """
113
+ if sys.platform == "darwin":
114
+ # special case for MacOS
115
+ # /!\ Qt maps ControlModifier to CMD
116
+ # CMD-C, CMD-V for copy/paste
117
+ # CTRL-C and other modifiers -> key mapping
118
+ if event.modifiers() == QtCore.Qt.MetaModifier:
119
+ if event.key() == Qt.Key_Backspace:
120
+ return control_keys_mapping.get(Qt.Key_W)
121
+ return control_keys_mapping.get(event.key())
122
+ elif event.modifiers() == QtCore.Qt.ControlModifier:
123
+ if event.key() == Qt.Key_C:
124
+ # copy
125
+ return "copy"
126
+ elif event.key() == Qt.Key_V:
127
+ # paste
128
+ return "paste"
129
+ return None
130
+ else:
131
+ return normal_keys_mapping.get(event.key(), event.text().encode("utf8"))
132
+ if event.modifiers() == QtCore.Qt.ControlModifier:
133
+ return control_keys_mapping.get(event.key())
134
+ else:
135
+ return normal_keys_mapping.get(event.key(), event.text().encode("utf8"))
136
+
137
+
138
+ class Screen(pyte.HistoryScreen):
139
+ def __init__(self, stdin_fd, numColumns, numLines, historyLength):
140
+ super().__init__(numColumns, numLines, historyLength, ratio=1 / numLines)
141
+ self._fd = stdin_fd
142
+
143
+ def write_process_input(self, data):
144
+ """Response to CPR request for example"""
145
+ os.write(self._fd, data.encode("utf-8"))
146
+
147
+
148
+ class Backend(QtCore.QObject):
149
+ """
150
+ Poll Bash.
151
+
152
+ This class will run as a qsocketnotifier (started in ``_TerminalWidget``) and poll the
153
+ file descriptor of the Bash terminal.
154
+ """
155
+
156
+ # Signals to communicate with ``_TerminalWidget``.
157
+ startWork = pyqtSignal()
158
+ dataReady = pyqtSignal(object)
159
+
160
+ def __init__(self, fd, numColumns, numLines):
161
+ super().__init__()
162
+
163
+ # File descriptor that connects to Bash process.
164
+ self.fd = fd
165
+
166
+ # Setup Pyte (hard coded display size for now).
167
+ self.screen = Screen(self.fd, numColumns, numLines, 10000)
168
+ self.stream = pyte.ByteStream()
169
+ self.stream.attach(self.screen)
170
+
171
+ self.notifier = QSocketNotifier(fd, QSocketNotifier.Read)
172
+ self.notifier.activated.connect(self._fd_readable)
173
+
174
+ def _fd_readable(self):
175
+ """
176
+ Poll the Bash output, run it through Pyte, and notify the main applet.
177
+ """
178
+ # Read the shell output until the file descriptor is closed.
179
+ try:
180
+ out = os.read(self.fd, 2**16)
181
+ except OSError:
182
+ return
183
+
184
+ # Feed output into Pyte's state machine and send the new screen
185
+ # output to the GUI
186
+ self.stream.feed(out)
187
+ self.dataReady.emit(self.screen)
188
+
189
+
190
+ class BECConsole(QtWidgets.QScrollArea):
191
+ """Container widget for the terminal text area"""
192
+
193
+ def __init__(self, parent=None, numLines=50, numColumns=125):
194
+ super().__init__(parent)
195
+
196
+ self.innerWidget = QtWidgets.QWidget(self)
197
+ QHBoxLayout(self.innerWidget)
198
+ self.innerWidget.layout().setContentsMargins(0, 0, 0, 0)
199
+
200
+ self.term = _TerminalWidget(self.innerWidget, numLines, numColumns)
201
+ self.term.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
202
+ self.innerWidget.layout().addWidget(self.term)
203
+
204
+ self.scroll_bar = QScrollBar(Qt.Vertical, self.term)
205
+ self.innerWidget.layout().addWidget(self.scroll_bar)
206
+
207
+ self.term.set_scroll(self.scroll_bar)
208
+
209
+ self.setWidget(self.innerWidget)
210
+
211
+ def start(self, cmd=["bec", "--nogui"], deactivate_ctrl_d=True):
212
+ self.term._cmd = cmd
213
+ self.term.start(deactivate_ctrl_d=deactivate_ctrl_d)
214
+
215
+ def push(self, text):
216
+ """Push some text to the terminal"""
217
+ return self.term.push(text)
218
+
219
+
220
+ class _TerminalWidget(QtWidgets.QPlainTextEdit):
221
+ """
222
+ Start ``Backend`` process and render Pyte output as text.
223
+ """
224
+
225
+ def __init__(self, parent, numColumns, numLines, **kwargs):
226
+ super().__init__(parent)
227
+
228
+ # file descriptor to communicate with the subprocess
229
+ self.fd = None
230
+ self.backend = None
231
+ self.lock = threading.Lock()
232
+ # command to execute
233
+ self._cmd = None
234
+ # should ctrl-d be deactivated ? (prevent Python exit)
235
+ self._deactivate_ctrl_d = False
236
+
237
+ # Specify the terminal size in terms of lines and columns.
238
+ self.numLines = numLines
239
+ self.numColumns = numColumns
240
+ self.output = [""] * numLines
241
+
242
+ # Use Monospace fonts and disable line wrapping.
243
+ self.setFont(QtGui.QFont("Courier", 9))
244
+ self.setFont(QtGui.QFont("Monospace"))
245
+ self.setLineWrapMode(QtWidgets.QPlainTextEdit.NoWrap)
246
+
247
+ # Disable vertical scrollbar (we use our own, to be set via .set_scroll())
248
+ self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
249
+
250
+ fmt = QtGui.QFontMetrics(self.font())
251
+ self._char_width = fmt.width("w")
252
+ self._char_height = fmt.height()
253
+ self.setCursorWidth(self._char_width)
254
+ # self.setStyleSheet("QPlainTextEdit { color: #ffff00; background-color: #303030; } ");
255
+
256
+ def start(self, deactivate_ctrl_d=False):
257
+ self._deactivate_ctrl_d = deactivate_ctrl_d
258
+
259
+ # Start the Bash process
260
+ self.fd = self.forkShell()
261
+
262
+ # Create the ``Backend`` object
263
+ self.backend = Backend(self.fd, self.numColumns, self.numLines)
264
+ self.backend.dataReady.connect(self.dataReady)
265
+
266
+ def minimumSizeHint(self):
267
+ width = self._char_width * self.numColumns
268
+ height = self._char_height * self.numLines
269
+ return QSize(width, height + 20)
270
+
271
+ def set_scroll(self, scroll):
272
+ self.scroll = scroll
273
+ self.scroll.setMinimum(0)
274
+ self.scroll.valueChanged.connect(self.scroll_value_change)
275
+
276
+ def scroll_value_change(self, value, old={"value": 0}):
277
+ if value <= old["value"]:
278
+ # scroll up
279
+ # value is number of lines from the start
280
+ nlines = old["value"] - value
281
+ # history ratio gives prev_page == 1 line
282
+ for i in range(nlines):
283
+ self.backend.screen.prev_page()
284
+ else:
285
+ # scroll down
286
+ nlines = value - old["value"]
287
+ for i in range(nlines):
288
+ self.backend.screen.next_page()
289
+ old["value"] = value
290
+ self.dataReady(self.backend.screen, reset_scroll=False)
291
+
292
+ @pyqtSlot(object)
293
+ def keyPressEvent(self, event):
294
+ """
295
+ Redirect all keystrokes to the terminal process.
296
+ """
297
+ # Convert the Qt key to the correct ASCII code.
298
+ if (
299
+ self._deactivate_ctrl_d
300
+ and event.modifiers() == QtCore.Qt.ControlModifier
301
+ and event.key() == QtCore.Qt.Key_D
302
+ ):
303
+ return None
304
+
305
+ code = QtKeyToAscii(event)
306
+ if code == "copy":
307
+ # MacOS only: CMD-C handling
308
+ self.copy()
309
+ elif code == "paste":
310
+ # MacOS only: CMD-V handling
311
+ self._push_clipboard()
312
+ elif code is not None:
313
+ os.write(self.fd, code)
314
+
315
+ def push(self, text):
316
+ """
317
+ Write 'text' to terminal
318
+ """
319
+ os.write(self.fd, text.encode("utf-8"))
320
+
321
+ def contextMenuEvent(self, event):
322
+ menu = self.createStandardContextMenu()
323
+ for action in menu.actions():
324
+ # remove all actions except copy and paste
325
+ if "opy" in action.text():
326
+ # redefine text without shortcut
327
+ # since it probably clashes with control codes (like CTRL-C etc)
328
+ action.setText("Copy")
329
+ continue
330
+ if "aste" in action.text():
331
+ # redefine text without shortcut
332
+ action.setText("Paste")
333
+ # paste -> have to insert with self.push
334
+ action.triggered.connect(self._push_clipboard)
335
+ continue
336
+ menu.removeAction(action)
337
+ menu.exec_(event.globalPos())
338
+
339
+ def _push_clipboard(self):
340
+ clipboard = QApplication.instance().clipboard()
341
+ self.push(clipboard.text())
342
+
343
+ def mouseReleaseEvent(self, event):
344
+ if event.button() == Qt.MiddleButton:
345
+ # push primary selection buffer ("mouse clipboard") to terminal
346
+ clipboard = QApplication.instance().clipboard()
347
+ if clipboard.supportsSelection():
348
+ self.push(clipboard.text(QClipboard.Selection))
349
+ return None
350
+ elif event.button() == Qt.LeftButton:
351
+ # left button click
352
+ textCursor = self.textCursor()
353
+ if textCursor.selectedText():
354
+ # mouse was used to select text -> nothing to do
355
+ pass
356
+ else:
357
+ # a simple 'click', make cursor going to end
358
+ textCursor.setPosition(0)
359
+ textCursor.movePosition(
360
+ QTextCursor.Down, QTextCursor.MoveAnchor, self.backend.screen.cursor.y
361
+ )
362
+ textCursor.movePosition(
363
+ QTextCursor.Right, QTextCursor.MoveAnchor, self.backend.screen.cursor.x
364
+ )
365
+ self.setTextCursor(textCursor)
366
+ self.ensureCursorVisible()
367
+ return None
368
+ return super().mouseReleaseEvent(event)
369
+
370
+ def dataReady(self, screenData, reset_scroll=True):
371
+ """
372
+ Render the new screen as text into the widget.
373
+
374
+ This method is triggered via a signal from ``Backend``.
375
+ """
376
+ with self.lock:
377
+ # Clear the widget
378
+ self.clear()
379
+
380
+ # Prepare the HTML output
381
+ for line_no in screenData.dirty:
382
+ line = text = ""
383
+ style = old_style = ""
384
+ for ch in screenData.buffer[line_no].values():
385
+ 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 ''}"
386
+ if style != old_style:
387
+ if old_style:
388
+ line += f"<span style={repr(old_style)}>{html.escape(text, quote=True)}</span>"
389
+ else:
390
+ line += html.escape(text, quote=True)
391
+ text = ""
392
+ old_style = style
393
+ text += ch.data
394
+ if style:
395
+ line += f"<span style={repr(style)}>{html.escape(text, quote=True)}</span>"
396
+ else:
397
+ line += html.escape(text, quote=True)
398
+ self.output[line_no] = line
399
+ # fill the text area with HTML contents in one go
400
+ self.appendHtml(f"<pre>{chr(10).join(self.output)}</pre>")
401
+ # done updates, all clean
402
+ screenData.dirty.clear()
403
+
404
+ # Activate cursor
405
+ textCursor = self.textCursor()
406
+ textCursor.setPosition(0)
407
+ textCursor.movePosition(QTextCursor.Down, QTextCursor.MoveAnchor, screenData.cursor.y)
408
+ textCursor.movePosition(QTextCursor.Right, QTextCursor.MoveAnchor, screenData.cursor.x)
409
+ self.setTextCursor(textCursor)
410
+ self.ensureCursorVisible()
411
+
412
+ # manage scroll
413
+ if reset_scroll:
414
+ self.scroll.valueChanged.disconnect(self.scroll_value_change)
415
+ tmp = len(self.backend.screen.history.top) + len(self.backend.screen.history.bottom)
416
+ self.scroll.setMaximum(tmp if tmp > 0 else 0)
417
+ self.scroll.setSliderPosition(len(self.backend.screen.history.top))
418
+ self.scroll.valueChanged.connect(self.scroll_value_change)
419
+
420
+ # def resizeEvent(self, event):
421
+ # with self.lock:
422
+ # self.numColumns = int(self.width() / self._char_width)
423
+ # self.numLines = int(self.height() / self._char_height)
424
+ # self.output = [""] * self.numLines
425
+ # print("RESIZING TO", self.numColumns, "x", self.numLines)
426
+ # self.backend.screen.resize(self.numLines, self.numColumns)
427
+
428
+ def wheelEvent(self, event):
429
+ y = event.angleDelta().y()
430
+ if y > 0:
431
+ self.backend.screen.prev_page()
432
+ else:
433
+ self.backend.screen.next_page()
434
+ self.dataReady(self.backend.screen, reset_scroll=False)
435
+
436
+ def forkShell(self):
437
+ """
438
+ Fork the current process and execute bec in shell.
439
+ """
440
+ try:
441
+ pid, fd = pty.fork()
442
+ except (IOError, OSError):
443
+ return False
444
+ if pid == 0:
445
+ # Safe way to make it work under BSD and Linux
446
+ try:
447
+ ls = os.environ["LANG"].split(".")
448
+ except KeyError:
449
+ ls = []
450
+ if len(ls) < 2:
451
+ ls = ["en_US", "UTF-8"]
452
+ try:
453
+ os.putenv("COLUMNS", str(self.numColumns))
454
+ os.putenv("LINES", str(self.numLines))
455
+ os.putenv("TERM", "linux")
456
+ os.putenv("LANG", ls[0] + ".UTF-8")
457
+ if isinstance(self._cmd, str):
458
+ os.execvp(self._cmd, self._cmd)
459
+ else:
460
+ os.execvp(self._cmd[0], self._cmd)
461
+ # print "child_pid", child_pid, sts
462
+ except (IOError, OSError):
463
+ pass
464
+ # self.proc_finish(sid)
465
+ os._exit(0)
466
+ else:
467
+ # We are in the parent process.
468
+ # Set file control
469
+ fcntl.fcntl(fd, fcntl.F_SETFL, os.O_NONBLOCK)
470
+ print("Spawned Bash shell (PID {})".format(pid))
471
+ return fd
472
+
473
+
474
+ if __name__ == "__main__":
475
+ import os
476
+ import sys
477
+
478
+ from qtpy import QtGui, QtWidgets
479
+
480
+ # Terminal size in characters.
481
+ numLines = 25
482
+ numColumns = 100
483
+
484
+ # Create the Qt application and QBash instance.
485
+ app = QtWidgets.QApplication([])
486
+ mainwin = QtWidgets.QMainWindow()
487
+ title = "BECConsole ({}x{})".format(numColumns, numLines)
488
+ mainwin.setWindowTitle(title)
489
+
490
+ console = BECConsole(mainwin, numColumns, numLines)
491
+ mainwin.setCentralWidget(console)
492
+ console.start()
493
+
494
+ # Show widget and launch Qt's event loop.
495
+ mainwin.show()
496
+ sys.exit(app.exec_())
@@ -26,8 +26,8 @@ class DockConfig(ConnectionConfig):
26
26
 
27
27
  class BECDock(BECConnector, Dock):
28
28
  USER_ACCESS = [
29
- "config_dict",
30
- "rpc_id",
29
+ "_config_dict",
30
+ "_rpc_id",
31
31
  "widget_list",
32
32
  "show_title_bar",
33
33
  "hide_title_bar",
@@ -23,7 +23,7 @@ class DockAreaConfig(ConnectionConfig):
23
23
 
24
24
  class BECDockArea(BECConnector, DockArea):
25
25
  USER_ACCESS = [
26
- "config_dict",
26
+ "_config_dict",
27
27
  "panels",
28
28
  "save_state",
29
29
  "remove_dock",
@@ -32,7 +32,7 @@ class BECDockArea(BECConnector, DockArea):
32
32
  "clear_all",
33
33
  "detach_dock",
34
34
  "attach_all",
35
- "get_all_rpc",
35
+ "_get_all_rpc",
36
36
  "temp_areas",
37
37
  ]
38
38