Skip to content

Commit

Permalink
make endianness selectable in order column (#428)
Browse files Browse the repository at this point in the history
* make endianness selectable in order column

* improve display for BE and LE

* add test for choosing endianness

* update coveragerc
  • Loading branch information
jopohl authored Apr 17, 2018
1 parent ea91bf7 commit 09026b9
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 9 deletions.
9 changes: 5 additions & 4 deletions src/urh/models/LabelValueTableModel.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@


class LabelValueTableModel(QAbstractTableModel):
header_labels = ["Name", 'Display format', 'Bit order', 'Value']
header_labels = ["Name", 'Display format', 'Order [Bit/Byte]', 'Value']

def __init__(self, proto_analyzer: ProtocolAnalyzer, controller, parent=None):
super().__init__(parent)
Expand All @@ -28,7 +28,8 @@ def __display_data(self, lbl: ProtocolLabel, expected_checksum: array = None):
lsb = lbl.display_bit_order_index == 1
lsd = lbl.display_bit_order_index == 2

data = util.convert_bits_to_string(data, lbl.display_format_index, pad_zeros=True, lsb=lsb, lsd=lsd)
data = util.convert_bits_to_string(data, lbl.display_format_index, pad_zeros=True, lsb=lsb, lsd=lsd,
endianness=lbl.display_endianness)
if data is None:
return None

Expand Down Expand Up @@ -98,7 +99,7 @@ def data(self, index: QModelIndex, role=Qt.DisplayRole):
elif j == 1:
return lbl.DISPLAY_FORMATS[lbl.display_format_index]
elif j == 2:
return lbl.DISPLAY_BIT_ORDERS[lbl.display_bit_order_index]
return lbl.display_order_str
elif j == 3:
return self.__display_data(lbl, calculated_crc)

Expand Down Expand Up @@ -139,7 +140,7 @@ def setData(self, index: QModelIndex, value, role=None):
if index.column() == 1:
lbl.display_format_index = value
elif index.column() == 2:
lbl.display_bit_order_index = value
lbl.display_order_str = value
self.dataChanged.emit(self.index(row, 0),
self.index(row, self.columnCount()))

Expand Down
30 changes: 29 additions & 1 deletion src/urh/signalprocessing/ProtocoLabel.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class ProtocolLabel(object):

__slots__ = ("__name", "start", "end", "apply_decoding", "color_index", "show", "__fuzz_me", "fuzz_values",
"fuzz_created", "__field_type", "display_format_index", "display_bit_order_index",
"auto_created", "copied")
"display_endianness", "auto_created", "copied")

def __init__(self, name: str, start: int, end: int, color_index: int, fuzz_created=False,
auto_created=False, field_type: FieldType = None):
Expand All @@ -44,6 +44,7 @@ def __init__(self, name: str, start: int, end: int, color_index: int, fuzz_creat

self.display_format_index = 0 if field_type is None else field_type.display_format_index
self.display_bit_order_index = 0
self.display_endianness = "big"

self.auto_created = auto_created

Expand Down Expand Up @@ -104,6 +105,31 @@ def range_complete_fuzzed(self) -> bool:
upper_limit = 2 ** (self.end - self.start)
return len(self.fuzz_values) == upper_limit

@property
def display_order_str(self) -> str:
try:
bit_order = self.DISPLAY_BIT_ORDERS[self.display_bit_order_index]
return bit_order + "/{}".format("BE" if self.display_endianness == "big" else "LE")
except IndexError:
return ""

@display_order_str.setter
def display_order_str(self, value: str):
prefix = value.strip().split("/")[0]
suffix = value.strip().split("/")[-1]
if suffix == "BE":
endianness = "big"
elif suffix == "LE":
endianness = "little"
else:
return

try:
self.display_bit_order_index = self.DISPLAY_BIT_ORDERS.index(prefix)
self.display_endianness = endianness
except ValueError:
return

def get_copy(self):
if self.copied:
return self
Expand Down Expand Up @@ -153,6 +179,7 @@ def to_xml(self) -> ET.Element:
"apply_decoding": str(self.apply_decoding), "show": str(self.show),
"display_format_index": str(self.display_format_index),
"display_bit_order_index": str(self.display_bit_order_index),
"display_endianness": str(self.display_endianness),
"fuzz_me": str(self.fuzz_me), "fuzz_values": ",".join(self.fuzz_values),
"auto_created": str(self.auto_created)})

Expand Down Expand Up @@ -185,5 +212,6 @@ def from_xml(cls, tag: ET.Element, field_types_by_caption=None):
# set this after result.field_type because this would change display_format_index to field_types default
result.display_format_index = int(tag.get("display_format_index", 0))
result.display_bit_order_index = int(tag.get("display_bit_order_index", 0))
result.display_endianness = tag.get("display_endianness", "big")

return result
3 changes: 2 additions & 1 deletion src/urh/ui/delegates/ComboBoxDelegate.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import sys

from PyQt5.QtCore import QModelIndex, Qt, QAbstractItemModel, pyqtSlot
from PyQt5.QtGui import QImage, QPainter, QColor, QPixmap, QFontMetrics
from PyQt5.QtGui import QImage, QPainter, QColor, QPixmap
from PyQt5.QtWidgets import QStyledItemDelegate, QWidget, QStyleOptionViewItem, QComboBox


Expand Down
88 changes: 88 additions & 0 deletions src/urh/ui/delegates/SectionComboBoxDelegate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import sys
from collections import OrderedDict

from PyQt5.QtCore import QModelIndex, pyqtSlot, QAbstractItemModel, Qt
from PyQt5.QtGui import QPainter, QStandardItem
from PyQt5.QtWidgets import QItemDelegate, QStyleOptionViewItem, QStyle, QComboBox, QStyledItemDelegate, QWidget


class SectionItemDelegate(QItemDelegate):
def __init__(self, parent=None):
super().__init__(parent)

def paint(self, painter: QPainter, option: QStyleOptionViewItem, index: QModelIndex):
item_type = index.data(Qt.AccessibleDescriptionRole)
if item_type == "parent":
parent_option = option
parent_option.state |= QStyle.State_Enabled
super().paint(painter, parent_option, index)
elif item_type == "child":
child_option = option
indent = option.fontMetrics.width(4 * " ")
child_option.rect.adjust(indent, 0, 0, 0)
child_option.textElideMode = Qt.ElideNone
super().paint(painter, child_option, index)
else:
super().paint(painter, option, index)


class SectionComboBox(QComboBox):
def __init__(self, parent=None):
super().__init__(parent)

def add_parent_item(self, text):
item = QStandardItem(text)
item.setFlags(item.flags() & ~(Qt.ItemIsEnabled | Qt.ItemIsSelectable))
item.setData("parent", Qt.AccessibleDescriptionRole)

font = item.font()
font.setBold(True)
item.setFont(font)

self.model().appendRow(item)

def add_child_item(self, text):
item = QStandardItem(text)
item.setData("child", Qt.AccessibleDescriptionRole)
self.model().appendRow(item)


class SectionComboBoxDelegate(QStyledItemDelegate):
def __init__(self, items: OrderedDict, parent=None):
"""
:param items:
:param parent:
"""
super().__init__(parent)
self.items = items

def createEditor(self, parent: QWidget, option: QStyleOptionViewItem, index: QModelIndex):
editor = SectionComboBox(parent)
editor.setItemDelegate(SectionItemDelegate(editor.itemDelegate().parent()))
if sys.platform == "win32":
# Ensure text entries are visible with windows combo boxes
editor.setMinimumHeight(self.sizeHint(option, index).height() + 10)

for title, items in self.items.items():
editor.add_parent_item(title)
for item in items:
editor.add_child_item(item)
editor.currentIndexChanged.connect(self.current_index_changed)
return editor

def setEditorData(self, editor: SectionComboBox, index: QModelIndex):
editor.blockSignals(True)
item = index.model().data(index)
editor.setCurrentText(item)
editor.blockSignals(False)

def setModelData(self, editor: QWidget, model: QAbstractItemModel, index: QModelIndex):
model.setData(index, editor.currentText(), Qt.EditRole)

def updateEditorGeometry(self, editor: QWidget, option: QStyleOptionViewItem, index: QModelIndex):
editor.setGeometry(option.rect)

@pyqtSlot()
def current_index_changed(self):
self.commitData.emit(self.sender())
9 changes: 8 additions & 1 deletion src/urh/ui/views/LabelValueTableView.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
from collections import OrderedDict

from PyQt5.QtWidgets import QTableView

from urh.models.LabelValueTableModel import LabelValueTableModel
from urh.signalprocessing.ProtocoLabel import ProtocolLabel
from urh.ui.delegates.ComboBoxDelegate import ComboBoxDelegate
from urh.ui.delegates.SectionComboBoxDelegate import SectionComboBoxDelegate


class LabelValueTableView(QTableView):
def __init__(self, parent=None):
super().__init__(parent)
self.setItemDelegateForColumn(1, ComboBoxDelegate(ProtocolLabel.DISPLAY_FORMATS, parent=self))
self.setItemDelegateForColumn(2, ComboBoxDelegate(ProtocolLabel.DISPLAY_BIT_ORDERS, parent=self))

orders = OrderedDict([("Big Endian (BE)", [bo + "/BE" for bo in ProtocolLabel.DISPLAY_BIT_ORDERS]),
("Little Endian (LE)", [bo + "/LE" for bo in ProtocolLabel.DISPLAY_BIT_ORDERS])])

self.setItemDelegateForColumn(2, SectionComboBoxDelegate(orders, parent=self))
self.setEditTriggers(QTableView.AllEditTriggers)

def model(self) -> LabelValueTableModel:
Expand Down
9 changes: 7 additions & 2 deletions src/urh/util/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,10 @@ def get_windows_lib_path():
return dll_dir


def convert_bits_to_string(bits, output_view_type: int, pad_zeros=False, lsb=False, lsd=False):
def convert_bits_to_string(bits, output_view_type: int, pad_zeros=False, lsb=False, lsd=False, endianness="big"):
"""
Convert bit array to string
:param endianness: Endianness little or big
:param bits: Bit array
:param output_view_type: Output view type index
0 = bit, 1=hex, 2=ascii, 3=decimal 4=binary coded decimal (bcd)
Expand All @@ -86,7 +87,11 @@ def convert_bits_to_string(bits, output_view_type: int, pad_zeros=False, lsb=Fal
# Reverse bit string
bits_str = bits_str[::-1]

if output_view_type == 0: # bt
if endianness == "little":
# reverse byte wise
bits_str = "".join(bits_str[max(i-8, 0):i] for i in range(len(bits_str), 0, -8))

if output_view_type == 0: # bit
result = bits_str

elif output_view_type == 1: # hex
Expand Down
4 changes: 4 additions & 0 deletions tests/.coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,7 @@ exclude_lines =
def on_new_project_action_triggered
def on_label_non_project_mode_link_activated
raise NotImplementedError
class SectionItemDelegate
class SectionComboBox
def createEditor
def updateEditorGeometry
7 changes: 7 additions & 0 deletions tests/test_analysis_tab_GUI.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,13 @@ def test_label_value_table(self):
self.assertIn("display type", model.data(model.index(0, 1), Qt.ToolTipRole))
self.assertIn("bit order", model.data(model.index(0, 2), Qt.ToolTipRole))

lbl = self.cfc.proto_analyzer.default_message_type[0]
self.assertEqual(lbl.display_endianness, "big")
model.setData(model.index(0, 2), "MSB/LE", role=Qt.EditRole)
self.assertEqual(lbl.display_endianness, "little")
model.setData(model.index(0, 2), "LSB/BE", role=Qt.EditRole)
self.assertEqual(lbl.display_endianness, "big")

def test_label_list_view(self):
menus_before = [w for w in QApplication.topLevelWidgets() if isinstance(w, QMenu)]

Expand Down

0 comments on commit 09026b9

Please sign in to comment.