waveform: changes to mvc

This commit is contained in:
Simon Renblad 2024-01-22 14:13:37 +08:00
parent f60924f077
commit 4d97cb5426

View File

@ -113,8 +113,8 @@ class _AddChannelDialog(QtWidgets.QDialog):
for select in selection: for select in selection:
key = self._channel_model.index_to_key(select) key = self._channel_model.index_to_key(select)
if key is not None: if key is not None:
width = self._channel_model[key].ref width, ty = self._channel_model[key].ref
channels.append((key, width)) channels.append((key, width, ty, []))
self.accepted.emit(channels) self.accepted.emit(channels)
self.close() self.close()
@ -136,7 +136,7 @@ class Waveform(pg.PlotWidget):
cursorMoved = QtCore.pyqtSignal(float) cursorMoved = QtCore.pyqtSignal(float)
def __init__(self, channel, state, parent=None): def __init__(self, name, width, parent=None):
pg.PlotWidget.__init__(self, pg.PlotWidget.__init__(self,
parent=parent, parent=parent,
x=None, x=None,
@ -150,13 +150,12 @@ class Waveform(pg.PlotWidget):
self.setMenuEnabled(False) self.setMenuEnabled(False)
self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
self.channel = channel self.name = name
self.name = channel[0] self.width = width
self.width = channel[1][0]
self.state = state #self.state = state
self.x_data = [] #self.x_data = []
self.y_data = [] #self.y_data = []
self.plot_item = self.getPlotItem() self.plot_item = self.getPlotItem()
self.plot_item.hideButtons() self.plot_item.hideButtons()
@ -193,8 +192,8 @@ class Waveform(pg.PlotWidget):
self.label_bg = BackgroundItem(parent=self.plot_item, rect=rect) self.label_bg = BackgroundItem(parent=self.plot_item, rect=rect)
self.label_bg.anchor(itemPos=(0, 0), parentPos=(0, 0), offset=(0, 0)) self.label_bg.anchor(itemPos=(0, 0), parentPos=(0, 0), offset=(0, 0))
def update_x_max(self): #def update_x_max(self):
self.view_box.setLimits(xMax=self.state["stopped_x"]) # self.view_box.setLimits(xMax=self.state["stopped_x"])
def set_cursor_visible(self, visible): def set_cursor_visible(self, visible):
if visible: if visible:
@ -202,31 +201,28 @@ class Waveform(pg.PlotWidget):
else: else:
self.removeItem(self.cursor) self.removeItem(self.cursor)
def on_cursor_move(self, x): #def on_cursor_move(self, x):
self.cursor.setValue(x) # self.cursor.setValue(x)
if len(self.x_data) < 1: # if len(self.x_data) < 1:
return # return
ind = bisect.bisect_left(self.x_data, x) - 1 # ind = bisect.bisect_left(self.x_data, x) - 1
dr = self.plot_data_item.dataRect() # dr = self.plot_data_item.dataRect()
if dr is None: # if dr is None:
self.cursor_y = None # self.cursor_y = None
elif dr.left() <= x \ # elif dr.left() <= x \
and 0 <= ind < len(self.y_data): # and 0 <= ind < len(self.y_data):
self.cursor_y = self.y_data[ind] # self.cursor_y = self.y_data[ind]
elif x >= dr.right(): # elif x >= dr.right():
self.cursor_y = self.y_data[-1] # self.cursor_y = self.y_data[-1]
else: # else:
self.cursor_y = None # self.cursor_y = None
self.format_cursor_label() # self.format_cursor_label()
def extract_data_from_state(self): def onDataChange(self):
raise NotImplementedError raise NotImplementedError
def display(self): #def format_cursor_label(self):
raise NotImplementedError # raise NotImplementedError
def format_cursor_label(self):
raise NotImplementedError
# override # override
def mouseMoveEvent(self, e): def mouseMoveEvent(self, e):
@ -254,24 +250,19 @@ class Waveform(pg.PlotWidget):
class LogWaveform(Waveform): class LogWaveform(Waveform):
def __init__(self, channel, state, parent=None): def __init__(self, name, width, parent=None):
Waveform.__init__(self, channel, state, parent) Waveform.__init__(self, name, width, parent)
def extract_data_from_state(self): def onDataChange(self, data):
try:
self.x_data, self.y_data = zip(*self.state['logs'][self.name])
except:
logger.debug('Error caught when loading waveform: {}'.format(self.name), exc_info=True)
def display(self):
try: try:
x_data = zip(*data)[0]
self.plot_data_item.setData( self.plot_data_item.setData(
x=self.x_data, y=np.ones(len(self.x_data))) x=x_data, y=np.ones(len(x_data)))
self.plot_data_item.opts.update( self.plot_data_item.opts.update(
{"connect": np.zeros(2), "symbol": "x"}) {"connect": np.zeros(2), "symbol": "x"})
old_msg = "" old_msg = ""
old_x = 0 old_x = 0
for x, msg in zip(self.x_data, self.y_data): for x, msg in data:
if x == old_x: if x == old_x:
old_msg += "\n" + msg old_msg += "\n" + msg
else: else:
@ -292,23 +283,17 @@ class LogWaveform(Waveform):
class BitWaveform(Waveform): class BitWaveform(Waveform):
def __init__(self, channel, state, parent=None): def __init__(self, name, width, parent=None):
Waveform.__init__(self, channel, state, parent) Waveform.__init__(self, name, width, parent)
self._arrows = [] self._arrows = []
#self.plot_data_item.setDownsampling(ds=1000, method="peak", auto=False) #self.plot_data_item.setDownsampling(ds=1000, method="peak", auto=False)
def extract_data_from_state(self): def onDataChange(self, data):
try:
self.x_data, self.y_data = zip(*self.state['data'][self.name])
except:
logger.debug('Error caught when loading waveform data: {}'.format(self.name), exc_info=True)
def display(self):
try: try:
display_y = [] display_y = []
display_x = [] display_x = []
previous_y = None previous_y = None
for x, y in zip(self.x_data, self.y_data): for x, y in data:
if y == "X": # TODO: replace with dictionary mapping if y == "X": # TODO: replace with dictionary mapping
dis_y = DISPLAY_MID dis_y = DISPLAY_MID
elif y == "1": elif y == "1":
@ -335,8 +320,8 @@ class BitWaveform(Waveform):
class BitVectorWaveform(Waveform): class BitVectorWaveform(Waveform):
def __init__(self, channel, state, parent=None): def __init__(self, name, width, parent=None):
Waveform.__init__(self, channel, state, parent) Waveform.__init__(self, name, width, parent)
self._labels = [] self._labels = []
hx = math.ceil(self.width / 4) hx = math.ceil(self.width / 4)
self._format_string = "{:0=" + str(hx) + "X}" # TODO: change method.. self._format_string = "{:0=" + str(hx) + "X}" # TODO: change method..
@ -350,26 +335,21 @@ class BitVectorWaveform(Waveform):
right_label_i = bisect.bisect_right(self.x_data, xmax) + 1 right_label_i = bisect.bisect_right(self.x_data, xmax) + 1
for i, j in itertools.pairwise(range(left_label_i, right_label_i)): for i, j in itertools.pairwise(range(left_label_i, right_label_i)):
x1 = self.x_data[i] x1 = self.x_data[i]
x2 = self.x_data[j] if j < len(self.x_data) else self.state["stopped_x"] x2 = self.x_data[j] if j < len(self.x_data) else self.x_data[-1]*2
lbl = self._labels[i] lbl = self._labels[i]
bounds = lbl.boundingRect() bounds = lbl.boundingRect()
bounds_view = self.view_box.mapSceneToView(bounds) bounds_view = self.view_box.mapSceneToView(bounds)
if bounds_view.boundingRect().width() < x2 - x1: if bounds_view.boundingRect().width() < x2 - x1:
self.addItem(lbl) self.addItem(lbl)
def extract_data_from_state(self): # TODO this may need to block sigTransformChanged while changing the labels
try: def onDataChange(self, data):
self.x_data, self.y_data = zip(*self.state['data'][self.name])
except:
logger.debug('Error caught when loading waveform data: {}'.format(self.name), exc_info=True)
def display(self):
try: try:
display_x, display_y = [], [] display_x, display_y = [], []
for x, y in zip(self.x_data, self.y_data): for x, y in data:
display_x.append(x) display_x.append(x)
display_y.append(DISPLAY_LOW) display_y.append(DISPLAY_LOW)
if "X" in y: # TODO change to using a dictionary if "X" in y: # TODO change to using a dictionary, remove any ref to "X" it does not happen in current impl
display_x.append(x) display_x.append(x)
display_y.append(DISPLAY_MID) display_y.append(DISPLAY_MID)
elif int(y) != 0: elif int(y) != 0:
@ -396,28 +376,23 @@ class BitVectorWaveform(Waveform):
class AnalogWaveform(Waveform): class AnalogWaveform(Waveform):
def __init__(self, channel, state, parent=None): def __init__(self, name, width, parent=None):
Waveform.__init__(self, channel, state, parent) Waveform.__init__(self, name, width, parent)
self.plot_data_item.setDownsampling(ds=10, method="peak", auto=True) self.plot_data_item.setDownsampling(ds=10, method="peak", auto=True)
# TODO: experiment with downsampling values for best performance # TODO: experiment with downsampling values for best performance
# TODO: potentially switch to not using a step connect # TODO: potentially switch to not using a step connect
def extract_data_from_state(self):
try:
self.x_data, self.y_data = zip(*self.state['data'][self.name])
except:
logger.debug('Error caught when loading waveform data: {}'.format(self.name), exc_info=True)
# TODO: change to using max_, min_ # TODO: change to using max_, min_
def display(self): def onDataChange(self, data):
try: try:
self.plot_data_item.setData(x=self.x_data, y=self.y_data) x_data, y_data = zip(*data)
mx = max(self.y_data) self.plot_data_item.setData(x=x_data, y=y_data)
mn = min(self.y_data) max_y = max(y_data)
self.plot_item.setRange(yRange=(mn, mx), padding=0.1) min_y = min(y_data)
self.plot_item.setRange(yRange=(min_y, max_y), padding=0.1)
except: except:
logger.debug('Error caught when displaying waveform: {}'.format(self.name), exc_info=True) logger.debug('Error caught when displaying waveform: {}'.format(self.name), exc_info=True)
self.plot_data_item.setData(x=[0], y=[0]) self.plot_data_item.setData(x=[], y=[])
# TODO: can also just leave it as None -> simpler # TODO: can also just leave it as None -> simpler
def format_cursor_label(self): def format_cursor_label(self):
@ -505,7 +480,7 @@ class _WaveformView(QtWidgets.QWidget):
WaveformType.ANALOG: AnalogWaveform, WaveformType.ANALOG: AnalogWaveform,
WaveformType.LOG: LogWaveform WaveformType.LOG: LogWaveform
}[ty] }[ty]
return waveform_cls(name, width) return waveform_cls(name, width, parent=self._splitter)
def _resize(self): def _resize(self):
self._splitter.setMinimumHeight(Waveform.MIN_HEIGHT * self._model.rowCount()) self._splitter.setMinimumHeight(Waveform.MIN_HEIGHT * self._model.rowCount())
@ -785,11 +760,12 @@ class WaveformDock(QtWidgets.QDockWidget):
QtWidgets.QDockWidget.DockWidgetMovable | QtWidgets.QDockWidget.DockWidgetFloatable) QtWidgets.QDockWidget.DockWidgetMovable | QtWidgets.QDockWidget.DockWidgetFloatable)
self._channel_model = Model({}) self._channel_model = Model({})
self._waveform_model = _WaveformModel()
self._devices = None self._ddb = None
self._dump = None self._dump = None
self._state = { self._waveform_data = {
"timescale": 1, "timescale": 1,
"stopped_x": None, "stopped_x": None,
"logs": dict(), "logs": dict(),
@ -821,12 +797,9 @@ class WaveformDock(QtWidgets.QDockWidget):
grid.addWidget(self._request_dump_btn, 0, 1) grid.addWidget(self._request_dump_btn, 0, 1)
self._request_dump_btn.clicked.connect( self._request_dump_btn.clicked.connect(
lambda: asyncio.ensure_future(self.rpc_client.trigger_proxy_task())) lambda: asyncio.ensure_future(self.rpc_client.trigger_proxy_task()))
self._waveform_area = WaveformArea(self, self._state, self._waveform_view = _WaveformView(self)
self._channel_model) grid.addWidget(self._waveform_view, 2, 0, colspan=12)
self.traceDataChanged.connect(self._waveform_area.on_trace_update)
self.traceDataChanged.connect(self._update_log_channels)
grid.addWidget(self._waveform_area, 2, 0, colspan=12)
self._add_btn = QtWidgets.QToolButton() self._add_btn = QtWidgets.QToolButton()
self._add_btn.setToolTip("Add channels...") self._add_btn.setToolTip("Add channels...")
@ -834,13 +807,15 @@ class WaveformDock(QtWidgets.QDockWidget):
QtWidgets.QApplication.style().standardIcon( QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_FileDialogListView)) QtWidgets.QStyle.SP_FileDialogListView))
grid.addWidget(self._add_btn, 0, 2) grid.addWidget(self._add_btn, 0, 2)
self._add_btn.clicked.connect(self._waveform_area.on_add_channel_click) self._add_btn.clicked.connect(self.on_add_channel_click)
self._cursor_control = _CursorTimeControl(parent=self, state=self._state) #self._cursor_control = _CursorTimeControl(parent=self, state=self._state)
grid.addWidget(self._cursor_control, 0, 3, colspan=3) #grid.addWidget(self._cursor_control, 0, 3, colspan=3)
self._cursor_control.submit.connect( #self._cursor_control.submit.connect(
self._waveform_area.on_cursor_move) # self._waveform_area.on_cursor_move)
self._waveform_area.cursorMoved.connect(self._cursor_control.display_value) #self._waveform_area.cursorMoved.connect(self._cursor_control.display_value)
self.traceDataChanged.connect(self._update_log_channels)
self._file_menu = QtWidgets.QMenu() self._file_menu = QtWidgets.QMenu()
self._add_async_action("Open trace...", self.load_trace) self._add_async_action("Open trace...", self.load_trace)
@ -867,6 +842,21 @@ class WaveformDock(QtWidgets.QDockWidget):
self._dump = data self._dump = data
self.traceDataChanged.emit() self.traceDataChanged.emit()
def on_add_channel_click(self):
asyncio.ensure_future(self._add_waveform_task())
async def _add_waveform_task(self):
dialog = _AddChannelDialog(self, self._channel_model)
fut = asyncio.Future()
def on_accept(s):
fut.set_result(s)
dialog.accepted.connect(on_accept)
dialog.open()
channels = await fut
self._waveform_model.extend(channels)
self._waveform_model.update_data(self._waveform_data) # TODO use a specific function for this
async def load_trace(self): async def load_trace(self):
try: try:
filename = await get_open_file_name( filename = await get_open_file_name(