From f86ce2603d5bf63c79dc1de36e4fc8b8739ae405 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sat, 17 Sep 2016 12:16:53 +0530 Subject: [PATCH 01/49] Bugfix: use default gateway for off subnet destinations in all cases - fixes #196 --- server/device.cpp | 37 ++++++++++--- test/emultest.py | 137 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 156 insertions(+), 18 deletions(-) diff --git a/server/device.cpp b/server/device.cpp index b1469ca..bb3eeb6 100644 --- a/server/device.cpp +++ b/server/device.cpp @@ -684,16 +684,20 @@ void Device::sendIp4Reply(PacketBuffer *pktBuf) uchar *pktData = pktBuf->push(20); uchar origTtl = pktData[8]; uchar ipProto = pktData[9]; - quint32 srcIp, dstIp; + quint32 srcIp, dstIp, tgtIp, mask; quint32 sum; // Swap src/dst IP addresses dstIp = qFromBigEndian(pktData + 12); // srcIp in original pkt srcIp = qFromBigEndian(pktData + 16); // dstIp in original pkt - if (!arpTable_.contains(dstIp)) { + mask = ~0 << (32 - ip4PrefixLength_); + qDebug("dst %x mask %x self %x", dstIp, mask, ip4_); + tgtIp = ((dstIp & mask) == (ip4_ & mask)) ? dstIp : ip4Gateway_; + + if (!arpTable_.contains(tgtIp)) { qWarning("%s: mac not found for %s; unable to send IPv4 packet", - __FUNCTION__, qPrintable(QHostAddress(dstIp).toString())); + __FUNCTION__, qPrintable(QHostAddress(tgtIp).toString())); return; } @@ -712,7 +716,7 @@ void Device::sendIp4Reply(PacketBuffer *pktBuf) sum = (sum & 0xFFFF) + (sum >> 16); *(quint16*)(pktData + 10) = qToBigEndian(quint16(~sum)); - encap(pktBuf, arpTable_.value(dstIp), 0x0800); + encap(pktBuf, arpTable_.value(tgtIp), 0x0800); transmitPacket(pktBuf); } @@ -796,7 +800,7 @@ bool Device::sendIp6(PacketBuffer *pktBuf, UInt128 dstIp, quint8 protocol) { int payloadLen = pktBuf->length(); uchar *p = pktBuf->push(kIp6HdrLen); - quint64 dstMac = ndpTable_.value(dstIp); + quint64 dstMac; if (!p) { qWarning("%s: failed to push %d bytes [0x%p, 0x%p]", __FUNCTION__, @@ -807,6 +811,15 @@ bool Device::sendIp6(PacketBuffer *pktBuf, UInt128 dstIp, quint8 protocol) // In case of mcast, derive dstMac if ((dstIp.hi64() >> 56) == 0xff) dstMac = (quint64(0x3333) << 32) | (dstIp.lo64() & 0xffffffff); + else { + UInt128 mask = ~UInt128(0, 0) << (128 - ip6PrefixLength_); + UInt128 tgtIp = ((dstIp & mask) == (ip6_ & mask)) ? dstIp : ip6Gateway_; + qDebug("dst %s mask %s self %s", + qPrintable(QHostAddress(dstIp.toArray()).toString()), + qPrintable(QHostAddress(mask.toArray()).toString()), + qPrintable(QHostAddress(ip6_.toArray()).toString())); + dstMac = ndpTable_.value(tgtIp); + } if (!dstMac) { qWarning("%s: mac not found for %s; unable to send IPv6 packet", @@ -841,16 +854,22 @@ _error_exit: void Device::sendIp6Reply(PacketBuffer *pktBuf) { uchar *pktData = pktBuf->push(kIp6HdrLen); - UInt128 srcIp, dstIp; + UInt128 srcIp, dstIp, tgtIp, mask; // Swap src/dst IP addresses dstIp = qFromBigEndian(pktData + 8); // srcIp in original pkt srcIp = qFromBigEndian(pktData + 24); // dstIp in original pkt - if (!ndpTable_.contains(dstIp)) { + mask = ~UInt128(0, 0) << (128 - ip6PrefixLength_); + qDebug("dst %s mask %s self %s", + qPrintable(QHostAddress(dstIp.toArray()).toString()), + qPrintable(QHostAddress(mask.toArray()).toString()), + qPrintable(QHostAddress(ip6_.toArray()).toString())); + tgtIp = ((dstIp & mask) == (ip6_ & mask)) ? dstIp : ip6Gateway_; + if (!ndpTable_.contains(tgtIp)) { qWarning("%s: mac not found for %s; unable to send IPv6 packet", __FUNCTION__, - qPrintable(QHostAddress(dstIp.toArray()).toString())); + qPrintable(QHostAddress(tgtIp.toArray()).toString())); return; } @@ -860,7 +879,7 @@ void Device::sendIp6Reply(PacketBuffer *pktBuf) // Reset TTL pktData[7] = 64; - encap(pktBuf, ndpTable_.value(dstIp), 0x86dd); + encap(pktBuf, ndpTable_.value(tgtIp), 0x86dd); transmitPacket(pktBuf); } diff --git a/test/emultest.py b/test/emultest.py index 1124354..4f52993 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -21,6 +21,7 @@ from rpc import RpcError from protocols.mac_pb2 import mac, Mac from protocols.ip4_pb2 import ip4, Ip4 from protocols.ip6_pb2 import ip6, Ip6 +from protocols.icmp_pb2 import icmp, Icmp from protocols.vlan_pb2 import vlan use_defaults = True @@ -348,6 +349,106 @@ def dut_vlans(request, dut_ports): request.addfinalizer(delete_vdev) +@pytest.fixture +def ping(request, drone, ip_ver, port_id, src_ip, dst_ip): + # create ICMP stream + stream_id = ost_pb.StreamIdList() + stream_id.port_id.CopyFrom(port_id) + stream_id.stream_id.add().id = 0 + log.info('adding ping tx_stream %d' % stream_id.stream_id[0].id) + + drone.addStream(stream_id) + + # configure the ICMP echo tx stream(s) + stream_cfg = ost_pb.StreamConfigList() + stream_cfg.port_id.CopyFrom(port_id) + s = stream_cfg.stream.add() + s.stream_id.id = stream_id.stream_id[0].id + s.core.is_enabled = True + s.core.frame_len = 128 + s.control.packets_per_sec = 1 + s.control.num_packets = 3 + + # setup stream protocols as mac:eth2:ip:icmp:payload + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kMacFieldNumber + p.Extensions[mac].dst_mac_mode = Mac.e_mm_resolve + p.Extensions[mac].src_mac_mode = Mac.e_mm_resolve + + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kEth2FieldNumber + + if ip_ver == 4: + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kIp4FieldNumber + ip = None + ip = p.Extensions[ip4] + ip.src_ip = src_ip + ip.dst_ip = dst_ip + elif ip_ver == 6: + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kIp6FieldNumber + ip = p.Extensions[ip6] + ip.src_addr_hi = src_ip.hi + ip.src_addr_lo = src_ip.lo + ip.dst_addr_hi = dst_ip.hi + ip.dst_addr_lo = dst_ip.lo + else: + assert False # unreachable + + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kIcmpFieldNumber + if ip_ver == 6: + p.Extensions[icmp].icmp_version = Icmp.kIcmp6 + p.Extensions[icmp].type = 128 # icmpv6 echo request + + s.protocol.add().protocol_id.id = ost_pb.Protocol.kPayloadFieldNumber + + log.info('configuring ping tx_stream %d' % stream_id.stream_id[0].id) + + drone.modifyStream(stream_cfg) + + # send ping packets + ports = ost_pb.PortIdList() + ports.port_id.add().id = port_id.id + drone.startCapture(ports) + drone.startTransmit(ports) + time.sleep(5) + drone.stopCapture(ports) + + # delete ping stream + drone.deleteStream(stream_id) + + # FIXME: workaround for bug#179 + stream_cfg.ClearField("stream") + drone.modifyStream(stream_cfg) + + # verify ICMP Replies are received + buff = drone.getCaptureBuffer(port_id) + drone.saveCaptureBuffer(buff, 'capture.pcap') + log.info('dumping Rx capture buffer (all)') + cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap']) + print(cap_pkts) + if ip_ver == 4: + filter = '(icmp.type == 0)' \ + ' && (icmp.code == 0)' \ + ' && (ip.src == ' + str(ipaddress.ip_address(dst_ip)) + ')' \ + ' && (ip.dst == ' + str(ipaddress.ip_address(src_ip)) + ')' \ + ' && !expert.severity' + elif ip_ver == 6: + filter = '(icmpv6.type == 129)' \ + ' && (icmpv6.code == 0)' \ + ' && (ipv6.src == ' \ + + str(ip6_address(dst_ip)) + ')' \ + ' && (ipv6.dst == ' \ + + str(ip6_address(src_ip)) + ')' \ + ' && !expert.severity' + log.info('dumping Rx capture buffer (filtered)') + print filter + cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', + '-Y', filter]) + print(cap_pkts) + return cap_pkts.count('\n') > 1 # ================================================================= # # ----------------------------------------------------------------- # @@ -360,7 +461,7 @@ def dut_vlans(request, dut_ports): {'ip_ver': [6], 'mac_step': 1, 'ip_step': 1}, {'ip_ver': [4, 6], 'mac_step': 2, 'ip_step': 5}, ]) -def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, +def test_multiEmulDevNoVlan(request, drone, ports, dut, dut_ports, dut_ip, stream_clear, emul_ports, dgid_list, dev_cfg): # ----------------------------------------------------------------- # # TESTCASE: Emulate multiple IPv4 devices (no vlans) @@ -381,9 +482,9 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, ip_step = dev_cfg['ip_step'] # configure the tx device(s) - devgrp_cfg = ost_pb.DeviceGroupConfigList() - devgrp_cfg.port_id.CopyFrom(ports.tx.port_id[0]) - dg = devgrp_cfg.device_group.add() + tx_devgrp_cfg = ost_pb.DeviceGroupConfigList() + tx_devgrp_cfg.port_id.CopyFrom(ports.tx.port_id[0]) + dg = tx_devgrp_cfg.device_group.add() dg.device_group_id.id = dgid_list.tx.device_group_id[0].id dg.core.name = "Host1" dg.device_count = num_devs @@ -406,12 +507,12 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, ip.step.CopyFrom(ip6_address(ip_step).ip6) ip.default_gateway.CopyFrom(ip6addr.gateway) - drone.modifyDeviceGroup(devgrp_cfg) + drone.modifyDeviceGroup(tx_devgrp_cfg) # configure the rx device(s) - devgrp_cfg = ost_pb.DeviceGroupConfigList() - devgrp_cfg.port_id.CopyFrom(ports.rx.port_id[0]) - dg = devgrp_cfg.device_group.add() + rx_devgrp_cfg = ost_pb.DeviceGroupConfigList() + rx_devgrp_cfg.port_id.CopyFrom(ports.rx.port_id[0]) + dg = rx_devgrp_cfg.device_group.add() dg.device_group_id.id = dgid_list.rx.device_group_id[0].id dg.core.name = "Host1" dg.device_count = num_devs @@ -434,7 +535,25 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, ip.step.CopyFrom(ip6_address(ip_step).ip6) ip.default_gateway.CopyFrom(ip6addr.gateway) - drone.modifyDeviceGroup(devgrp_cfg) + drone.modifyDeviceGroup(rx_devgrp_cfg) + + # test end-to-end reachability - after resolving ARP/NDP + # FIXME: if and when ping RPC is added, move to where we ping DUT below + # FIXME: also add ping test to vlan case + time.sleep(10) # wait for DAD? otherwise we don't get replies for NS + drone.clearDeviceNeighbors(emul_ports) + drone.resolveDeviceNeighbors(emul_ports) + time.sleep(3) # wait for ARP resolution + # FIXME: if ping6/ping4 order is swapped, DUT does not send NS - have + # spent several hours trying to figure out why - to no avail :( + if has_ip6: + assert ping(request, drone, 6, emul_ports.port_id[0], + tx_devgrp_cfg.device_group[0].Extensions[emul.ip6].address, + rx_devgrp_cfg.device_group[0].Extensions[emul.ip6].address) + if has_ip4: + assert ping(request, drone, 4, emul_ports.port_id[0], + tx_devgrp_cfg.device_group[0].Extensions[emul.ip4].address, + rx_devgrp_cfg.device_group[0].Extensions[emul.ip4].address) # add the tx stream(s) - we may need more than one stream_id = ost_pb.StreamIdList() From 523258442c2e03a2c81a17b16d1e794e8837c76f Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sat, 17 Sep 2016 14:34:44 +0530 Subject: [PATCH 02/49] Precalculate mask and subnet for use during emulation rx/tx --- server/device.cpp | 58 ++++++++++++++++------------------------------- server/device.h | 4 ++++ 2 files changed, 24 insertions(+), 38 deletions(-) diff --git a/server/device.cpp b/server/device.cpp index bb3eeb6..56a7c1a 100644 --- a/server/device.cpp +++ b/server/device.cpp @@ -111,6 +111,10 @@ void Device::setIp4(quint32 address, int prefixLength, quint32 gateway) ip4PrefixLength_ = prefixLength; ip4Gateway_ = gateway; hasIp4_ = true; + + // Precalculate our mask 'n subnet to avoid doing so at pkt rx/tx time + ip4Mask_ = ~0 << (32 - ip4PrefixLength_); + ip4Subnet_ = ip4_ & ip4Mask_; } void Device::setIp6(UInt128 address, int prefixLength, UInt128 gateway) @@ -119,6 +123,10 @@ void Device::setIp6(UInt128 address, int prefixLength, UInt128 gateway) ip6PrefixLength_ = prefixLength; ip6Gateway_ = gateway; hasIp6_ = true; + + // Precalculate our mask 'n subnet to avoid doing so at pkt rx/tx time + ip6Mask_ = ~UInt128(0, 0) << (128 - ip6PrefixLength_); + ip6Subnet_ = ip6_ & ip6Mask_; } void Device::getConfig(OstEmul::Device *deviceConfig) @@ -405,7 +413,7 @@ quint64 Device::neighborMac(const PacketBuffer *pktBuf) // We know only about IP packets if ((ethType == 0x0800) && hasIp4_) { // IPv4 int ipHdrLen = (pktData[0] & 0x0F) << 2; - quint32 dstIp, tgtIp, mask; + quint32 dstIp, tgtIp; if (pktBuf->length() < ipHdrLen) { qDebug("incomplete IPv4 header: expected %d, actual %d", @@ -418,14 +426,12 @@ quint64 Device::neighborMac(const PacketBuffer *pktBuf) qDebug("mcast dst %x", dstIp); return (quint64(0x01005e) << 24) | (dstIp & 0x7FFFFF); } - mask = ~0 << (32 - ip4PrefixLength_); - qDebug("dst %x mask %x self %x", dstIp, mask, ip4_); - tgtIp = ((dstIp & mask) == (ip4_ & mask)) ? dstIp : ip4Gateway_; + tgtIp = ((dstIp & ip4Mask_) == ip4Subnet_) ? dstIp : ip4Gateway_; return arpTable_.value(tgtIp); } else if ((ethType == kEthTypeIp6) && hasIp6_) { // IPv6 - UInt128 dstIp, tgtIp, mask; + UInt128 dstIp, tgtIp; if (pktBuf->length() < (kIp6HdrLen+2)) { qDebug("incomplete IPv6 header: expected %d, actual %d", @@ -439,12 +445,7 @@ quint64 Device::neighborMac(const PacketBuffer *pktBuf) qPrintable(QHostAddress(dstIp.toArray()).toString())); return (quint64(0x3333) << 32) | (dstIp.lo64() & 0xFFFFFFFF); } - mask = ~UInt128(0, 0) << (128 - ip6PrefixLength_); - qDebug("dst %s mask %s self %s", - qPrintable(QHostAddress(dstIp.toArray()).toString()), - qPrintable(QHostAddress(mask.toArray()).toString()), - qPrintable(QHostAddress(ip6_.toArray()).toString())); - tgtIp = ((dstIp & mask) == (ip6_ & mask)) ? dstIp : ip6Gateway_; + tgtIp = ((dstIp & ip6Mask_) == ip6Subnet_) ? dstIp : ip6Gateway_; return ndpTable_.value(tgtIp); } @@ -568,7 +569,7 @@ void Device::sendArpRequest(PacketBuffer *pktBuf) { uchar *pktData = pktBuf->data(); int ipHdrLen = (pktData[0] & 0x0F) << 2; - quint32 srcIp = ip4_, dstIp, mask, tgtIp; + quint32 dstIp, tgtIp; if (pktBuf->length() < ipHdrLen) { qDebug("incomplete IPv4 header: expected %d, actual %d", @@ -578,9 +579,7 @@ void Device::sendArpRequest(PacketBuffer *pktBuf) dstIp = qFromBigEndian(pktData + ipHdrLen - 4); - mask = ~0 << (32 - ip4PrefixLength_); - qDebug("dst %x src %x mask %x", dstIp, srcIp, mask); - tgtIp = ((dstIp & mask) == (srcIp & mask)) ? dstIp : ip4Gateway_; + tgtIp = ((dstIp & ip4Mask_) == ip4Subnet_) ? dstIp : ip4Gateway_; sendArpRequest(tgtIp); @@ -691,9 +690,7 @@ void Device::sendIp4Reply(PacketBuffer *pktBuf) dstIp = qFromBigEndian(pktData + 12); // srcIp in original pkt srcIp = qFromBigEndian(pktData + 16); // dstIp in original pkt - mask = ~0 << (32 - ip4PrefixLength_); - qDebug("dst %x mask %x self %x", dstIp, mask, ip4_); - tgtIp = ((dstIp & mask) == (ip4_ & mask)) ? dstIp : ip4Gateway_; + tgtIp = ((dstIp & ip4Mask_) == ip4Subnet_) ? dstIp : ip4Gateway_; if (!arpTable_.contains(tgtIp)) { qWarning("%s: mac not found for %s; unable to send IPv4 packet", @@ -812,12 +809,7 @@ bool Device::sendIp6(PacketBuffer *pktBuf, UInt128 dstIp, quint8 protocol) if ((dstIp.hi64() >> 56) == 0xff) dstMac = (quint64(0x3333) << 32) | (dstIp.lo64() & 0xffffffff); else { - UInt128 mask = ~UInt128(0, 0) << (128 - ip6PrefixLength_); - UInt128 tgtIp = ((dstIp & mask) == (ip6_ & mask)) ? dstIp : ip6Gateway_; - qDebug("dst %s mask %s self %s", - qPrintable(QHostAddress(dstIp.toArray()).toString()), - qPrintable(QHostAddress(mask.toArray()).toString()), - qPrintable(QHostAddress(ip6_.toArray()).toString())); + UInt128 tgtIp = ((dstIp & ip6Mask_) == ip6Subnet_)? dstIp : ip6Gateway_; dstMac = ndpTable_.value(tgtIp); } @@ -854,18 +846,13 @@ _error_exit: void Device::sendIp6Reply(PacketBuffer *pktBuf) { uchar *pktData = pktBuf->push(kIp6HdrLen); - UInt128 srcIp, dstIp, tgtIp, mask; + UInt128 srcIp, dstIp, tgtIp; // Swap src/dst IP addresses dstIp = qFromBigEndian(pktData + 8); // srcIp in original pkt srcIp = qFromBigEndian(pktData + 24); // dstIp in original pkt - mask = ~UInt128(0, 0) << (128 - ip6PrefixLength_); - qDebug("dst %s mask %s self %s", - qPrintable(QHostAddress(dstIp.toArray()).toString()), - qPrintable(QHostAddress(mask.toArray()).toString()), - qPrintable(QHostAddress(ip6_.toArray()).toString())); - tgtIp = ((dstIp & mask) == (ip6_ & mask)) ? dstIp : ip6Gateway_; + tgtIp = ((dstIp & ip6Mask_) == ip6Subnet_) ? dstIp : ip6Gateway_; if (!ndpTable_.contains(tgtIp)) { qWarning("%s: mac not found for %s; unable to send IPv6 packet", __FUNCTION__, @@ -969,7 +956,7 @@ _invalid_exit: void Device::sendNeighborSolicit(PacketBuffer *pktBuf) { uchar *pktData = pktBuf->data(); - UInt128 srcIp = ip6_, dstIp, mask, tgtIp; + UInt128 dstIp, tgtIp; if (pktBuf->length() < kIp6HdrLen) { qDebug("incomplete IPv6 header: expected %d, actual %d", @@ -979,12 +966,7 @@ void Device::sendNeighborSolicit(PacketBuffer *pktBuf) dstIp = qFromBigEndian(pktData + 24); - mask = ~UInt128(0, 0) << (128 - ip6PrefixLength_); - qDebug("%s: dst %s src %s mask %s", __FUNCTION__, - qPrintable(QHostAddress(dstIp.toArray()).toString()), - qPrintable(QHostAddress(srcIp.toArray()).toString()), - qPrintable(QHostAddress(mask.toArray()).toString())); - tgtIp = ((dstIp & mask) == (srcIp & mask)) ? dstIp : ip6Gateway_; + tgtIp = ((dstIp & ip6Mask_) == ip6Subnet_) ? dstIp : ip6Gateway_; sendNeighborSolicit(tgtIp); } diff --git a/server/device.h b/server/device.h index f77615b..cdc4cec 100644 --- a/server/device.h +++ b/server/device.h @@ -107,11 +107,15 @@ private: // data quint32 ip4_; int ip4PrefixLength_; quint32 ip4Gateway_; + quint32 ip4Mask_; + quint32 ip4Subnet_; bool hasIp6_; UInt128 ip6_; int ip6PrefixLength_; UInt128 ip6Gateway_; + UInt128 ip6Mask_; + UInt128 ip6Subnet_; DeviceKey key_; From a8f9795c859f278037a1b0f8d6d58859eb87db78 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sat, 17 Sep 2016 15:21:31 +0530 Subject: [PATCH 03/49] Fix incorect logic of isNewerVersion --- common/updater.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/common/updater.cpp b/common/updater.cpp index 69436b4..fd02cab 100644 --- a/common/updater.cpp +++ b/common/updater.cpp @@ -37,6 +37,7 @@ Updater::Updater() Q_ASSERT(isVersionNewer("10.1", "2") == true); Q_ASSERT(isVersionNewer("0.10", "0.2") == true); Q_ASSERT(isVersionNewer("1.10.1", "1.2.3") == true); + Q_ASSERT(isVersionNewer("0.7.1", "0.8") == false); #endif } @@ -120,8 +121,12 @@ bool Updater::isVersionNewer(QString newVersion, QString curVersion) for (int i = 0; i < qMin(curVer.size(), newVer.size()); i++) { bool isOk; - if (newVer.at(i).toUInt(&isOk) > curVer.at(i).toUInt(&isOk)) + uint n = newVer.at(i).toUInt(&isOk); + uint c = curVer.at(i).toUInt(&isOk); + if (n > c) return true; + else if (n < c) + return false; } if (newVer.size() > curVer.size()) From 08fc0a116fbea40142d98bbd0c1a1ab85e42f3b9 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sat, 17 Sep 2016 15:27:01 +0530 Subject: [PATCH 04/49] Mention python API in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 998e57c..2854442 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Build Status](https://travis-ci.org/pstavirs/ostinato.svg?branch=master)](https://travis-ci.org/pstavirs/ostinato) -Ostinato is an open-source, cross-platform network packet crafter/traffic generator and analyzer with a friendly GUI. Craft and send packets of several streams with different protocols at different rates. +Ostinato is an open-source, cross-platform network packet crafter/traffic generator and analyzer with a friendly GUI and powerful python API. Craft and send packets of several streams with different protocols at different rates. Ostinato aims to be "Wireshark in Reverse" and become complementary to Wireshark. From 831b5c0916ac3e36b6f636878cb6e4bd69f55b48 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Wed, 21 Sep 2016 20:26:08 +0530 Subject: [PATCH 05/49] Interleaved mode updatePacketList - just clear list if there are no streams. Fixes #195 --- server/abstractport.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/server/abstractport.cpp b/server/abstractport.cpp index 0a97ff9..5d36895 100644 --- a/server/abstractport.cpp +++ b/server/abstractport.cpp @@ -377,11 +377,16 @@ void AbstractPort::updatePacketListInterleaved() qDebug("In %s", __FUNCTION__); + clearPacketList(); + if (streamList_.size() == 0) + { + isSendQueueDirty_ = false; + return; + } + // First sort the streams by ordinalValue qSort(streamList_.begin(), streamList_.end(), StreamBase::StreamLessThan); - clearPacketList(); - for (int i = 0; i < streamList_.size(); i++) { if (!streamList_[i]->isEnabled()) From 6a426a7a7e73161e87ab6674c12ade8161c8b668 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Wed, 21 Sep 2016 20:41:32 +0530 Subject: [PATCH 06/49] Add icon for Help menu item --- client/icons/help.png | Bin 0 -> 786 bytes client/mainwindow.ui | 3 +++ client/ostinato.qrc | 1 + 3 files changed, 4 insertions(+) create mode 100644 client/icons/help.png diff --git a/client/icons/help.png b/client/icons/help.png new file mode 100644 index 0000000000000000000000000000000000000000..5c870176d4dea68aab9e51166cc3d7a582f326d6 GIT binary patch literal 786 zcmV+t1MU2YP)$XgYMs^AIOw1Qr{*Wn)N-{9ma}x2(<~`9Go1=*>YR!KZvrBS zCd!u}@M0og%Ev@_;Z?Kk>Wwv=%h_57zmt2<_1msz_niYE=YRNPpd%02TK9oK1z z>ooPno}v^sikz_|1XHFx_L%~;ljh7i(jiay5F0x*+(9aXXFCl?AdQj5XlQ65%sEv+ ztfe?|YcjPN*@yYtE~ImQh{l|#A6Z8iu>pf43Rj52CzU_dMQm|S2xR62YjQOn+z8WH zaK=!}ggOZi{4pB7SQ=xC0n|vXP_Bkx_a)FeNd}w8U97BNbSWxa^QW-li9BZ#M1!_xE*?wzt^GcoeoL*JGLSe_+l-JT2#2tz!z&^ z_s5anq&^nBklIMwRvcoP3%qs%%Ea?1c{_*V*Xj&~uLu-2Dp1fUN4<0zMo$EH>*U83 zm_9;Vt%-bE{_J_!If!1y=c+`QVZ>0_BPy z+%^pgnv`f8H)Z%0&Tp8&u*MCIC4igNW5MeWM_DHpDNi)Zxz|9XboOnitwFq$ETN=X zj-tkCJnz**Y4k#6_Ty^B=hWo~L!47r`HoP=x&3T1)JLr2t2+#fH + + :/icons/help.png + Help (Online) diff --git a/client/ostinato.qrc b/client/ostinato.qrc index a8c6c5c..91d070b 100644 --- a/client/ostinato.qrc +++ b/client/ostinato.qrc @@ -20,6 +20,7 @@ icons/devicegroup_edit.png icons/exit.png icons/gaps.png + icons/help.png icons/logo.png icons/magnifier.png icons/name.png From 708aed9135850f4f3e4291678a22e1534ca20b04 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Thu, 13 Oct 2016 18:50:33 +0530 Subject: [PATCH 07/49] Added ostinato cmdline option -s to suppress starting local drone --- client/main.cpp | 4 +++ client/mainwindow.cpp | 38 ++++++++++++++--------- client/ostinato.pro | 1 + client/params.cpp | 65 ++++++++++++++++++++++++++++++++++++++++ client/params.h | 43 ++++++++++++++++++++++++++ client/portgrouplist.cpp | 10 ++++--- 6 files changed, 143 insertions(+), 18 deletions(-) create mode 100644 client/params.cpp create mode 100644 client/params.h diff --git a/client/main.cpp b/client/main.cpp index 34f689e..1507eef 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -21,6 +21,7 @@ along with this program. If not, see #include "../common/ostprotolib.h" #include "../common/protocolmanager.h" #include "../common/protocolwidgetfactory.h" +#include "params.h" #include "preferences.h" #include "settings.h" @@ -37,6 +38,7 @@ extern const char* revision; extern ProtocolManager *OstProtocolManager; extern ProtocolWidgetFactory *OstProtocolWidgetFactory; +Params appParams; QSettings *appSettings; QMainWindow *mainWindow; @@ -50,6 +52,8 @@ int main(int argc, char* argv[]) app.setProperty("version", version); app.setProperty("revision", revision); + appParams.parseCommandLine(argc, argv); + OstProtocolManager = new ProtocolManager(); OstProtocolWidgetFactory = new ProtocolWidgetFactory(); diff --git a/client/mainwindow.cpp b/client/mainwindow.cpp index 212fdcf..8c881d7 100644 --- a/client/mainwindow.cpp +++ b/client/mainwindow.cpp @@ -23,6 +23,7 @@ along with this program. If not, see #include "dbgthread.h" #endif +#include "params.h" #include "portgrouplist.h" #include "portstatswindow.h" #include "portswindow.h" @@ -50,23 +51,28 @@ PortGroupList *pgl; MainWindow::MainWindow(QWidget *parent) : QMainWindow (parent) { - QString serverApp = QCoreApplication::applicationDirPath(); Updater *updater = new Updater(); + if (appParams.optLocalDrone()) { + QString serverApp = QCoreApplication::applicationDirPath(); #ifdef Q_OS_MAC - // applicationDirPath() does not return bundle, but executable inside bundle - serverApp.replace("Ostinato.app", "drone.app"); + // applicationDirPath() does not return bundle, + // but executable inside bundle + serverApp.replace("Ostinato.app", "drone.app"); #endif - #ifdef Q_OS_WIN32 - serverApp.append("/drone.exe"); + serverApp.append("/drone.exe"); #else - serverApp.append("/drone"); + serverApp.append("/drone"); #endif - localServer_ = new QProcess(this); - localServer_->setProcessChannelMode(QProcess::ForwardedChannels); - localServer_->start(serverApp, QStringList()); + qDebug("staring local server - %s", qPrintable(serverApp)); + localServer_ = new QProcess(this); + localServer_->setProcessChannelMode(QProcess::ForwardedChannels); + localServer_->start(serverApp, QStringList()); + } + else + localServer_ = NULL; pgl = new PortGroupList; @@ -124,12 +130,14 @@ MainWindow::MainWindow(QWidget *parent) MainWindow::~MainWindow() { + if (localServer_) { #ifdef Q_OS_WIN32 - //! \todo - find a way to terminate cleanly - localServer_->kill(); + //! \todo - find a way to terminate cleanly + localServer_->kill(); #else - localServer_->terminate(); + localServer_->terminate(); #endif + } delete pgl; @@ -137,8 +145,10 @@ MainWindow::~MainWindow() appSettings->setValue(kApplicationWindowLayout, layout); appSettings->setValue(kApplicationWindowGeometryKey, geometry()); - localServer_->waitForFinished(); - delete localServer_; + if (localServer_) { + localServer_->waitForFinished(); + delete localServer_; + } } void MainWindow::on_actionOpenSession_triggered() diff --git a/client/ostinato.pro b/client/ostinato.pro index e4773ea..aca28e4 100644 --- a/client/ostinato.pro +++ b/client/ostinato.pro @@ -86,6 +86,7 @@ SOURCES += \ mainwindow.cpp \ ndpstatusmodel.cpp \ packetmodel.cpp \ + params.cpp \ port.cpp \ portconfigdialog.cpp \ portgroup.cpp \ diff --git a/client/params.cpp b/client/params.cpp new file mode 100644 index 0000000..8af44f2 --- /dev/null +++ b/client/params.cpp @@ -0,0 +1,65 @@ +/* +Copyright (C) 2016 Srivats P. + +This file is part of "Ostinato" + +This is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see +*/ + +#include "params.h" + +#include + +Params::Params() +{ + localDrone_ = true; +} + +int Params::parseCommandLine(int argc, char* argv[]) +{ + int c, n = 0; + + opterr = 0; + while ((c = getopt (argc, argv, "s")) != -1) { + switch (c) + { + case 's': + localDrone_ = false; + break; + default: + qDebug("ignoring unrecognized option (%c)", c); + } + n++; + } + + for (int i = optind; i < argc; i++, n++) + args_ << argv[i]; + + return n; +} + +bool Params::optLocalDrone() +{ + return localDrone_; +} + +int Params::argumentCount() +{ + return args_.size(); +} + +QString Params::argument(int index) +{ + return index < args_.size() ? args_.at(index) : QString(); +} diff --git a/client/params.h b/client/params.h new file mode 100644 index 0000000..a5ebc2f --- /dev/null +++ b/client/params.h @@ -0,0 +1,43 @@ +/* +Copyright (C) 2016 Srivats P. + +This file is part of "Ostinato" + +This is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see +*/ + +#ifndef _PARAMS_H +#define _PARAMS_H + +#include + +class Params { +public: + Params(); + int parseCommandLine(int argc, char* argv[]); + + bool optLocalDrone(); + + int argumentCount(); + QString argument(int index); + +private: + bool localDrone_; + QStringList args_; +}; + +extern Params appParams; + +#endif + diff --git a/client/portgrouplist.cpp b/client/portgrouplist.cpp index 6e380a0..79f500d 100644 --- a/client/portgrouplist.cpp +++ b/client/portgrouplist.cpp @@ -19,6 +19,8 @@ along with this program. If not, see #include "portgrouplist.h" +#include "params.h" + // TODO(LOW): Remove #include @@ -29,8 +31,6 @@ PortGroupList::PortGroupList() mDeviceGroupModel(this), mDeviceModel(this) { - PortGroup *pg; - #ifdef QT_NO_DEBUG streamModelTester_ = NULL; portModelTester_ = NULL; @@ -46,8 +46,10 @@ PortGroupList::PortGroupList() #endif // Add the "Local" Port Group - pg = new PortGroup; - addPortGroup(*pg); + if (appParams.optLocalDrone()) { + PortGroup *pg = new PortGroup; + addPortGroup(*pg); + } } PortGroupList::~PortGroupList() From b45720b566411a77fa18717f8ce82b77945184c2 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Thu, 13 Oct 2016 22:06:11 +0530 Subject: [PATCH 08/49] Ostinato starts with session file if provided on command line --- client/mainwindow.cpp | 19 ++++++++++++++++--- client/mainwindow.h | 2 +- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/client/mainwindow.cpp b/client/mainwindow.cpp index 8c881d7..240b410 100644 --- a/client/mainwindow.cpp +++ b/client/mainwindow.cpp @@ -120,6 +120,16 @@ MainWindow::MainWindow(QWidget *parent) connect(updater, SIGNAL(newVersionAvailable(QString)), this, SLOT(onNewVersion(QString))); updater->checkForNewVersion(); + + if (appParams.argumentCount()) { + QString fileName = appParams.argument(0); + if (QFile::exists(fileName)) + on_actionOpenSession_triggered(fileName); + else + QMessageBox::information(NULL, qApp->applicationName(), + QString("File not found: " + fileName)); + } + #if 0 { DbgThread *dbg = new DbgThread(pgl); @@ -151,18 +161,20 @@ MainWindow::~MainWindow() } } -void MainWindow::on_actionOpenSession_triggered() +void MainWindow::on_actionOpenSession_triggered(QString fileName) { - qDebug("Open Session Action"); + qDebug("Open Session Action (%s)", qPrintable(fileName)); static QString dirName; - QString fileName; QStringList fileTypes = SessionFileFormat::supportedFileTypes( SessionFileFormat::kOpenFile); QString fileType; QString errorStr; bool ret; + if (!fileName.isEmpty()) + goto _skip_prompt; + if (portsWindow->portGroupCount()) { if (QMessageBox::question(this, tr("Open Session"), @@ -180,6 +192,7 @@ void MainWindow::on_actionOpenSession_triggered() if (fileName.isEmpty()) goto _exit; +_skip_prompt: ret = openSession(fileName, errorStr); if (!ret || !errorStr.isEmpty()) { QMessageBox msgBox(this); diff --git a/client/mainwindow.h b/client/mainwindow.h index ba16ed6..1b37798 100644 --- a/client/mainwindow.h +++ b/client/mainwindow.h @@ -51,7 +51,7 @@ public: ~MainWindow(); public slots: - void on_actionOpenSession_triggered(); + void on_actionOpenSession_triggered(QString fileName = QString()); void on_actionSaveSession_triggered(); void on_actionPreferences_triggered(); void on_actionViewRestoreDefaults_triggered(); From c2f36c5cb3a923412262658d7c4226ee8c3a3f8c Mon Sep 17 00:00:00 2001 From: Srivats P Date: Thu, 27 Oct 2016 23:50:24 +0530 Subject: [PATCH 09/49] IPv4 now supports options - configured as a hex string for now; fixes #120 --- common/ip4.cpp | 48 ++++++++++++++++++++++++++++++++++++++++---- common/ip4.h | 1 + common/ip4.proto | 2 +- common/ip4.ui | 9 +-------- common/ip4config.cpp | 10 +++++++++ 5 files changed, 57 insertions(+), 13 deletions(-) diff --git a/common/ip4.cpp b/common/ip4.cpp index 6377bd9..cdeea98 100644 --- a/common/ip4.cpp +++ b/common/ip4.cpp @@ -112,6 +112,7 @@ AbstractProtocol::FieldFlags Ip4Protocol::fieldFlags(int index) const case ip4_srcAddr: case ip4_dstAddr: + case ip4_options: break; case ip4_isOverrideVer: @@ -168,7 +169,9 @@ QVariant Ip4Protocol::fieldData(int index, FieldAttrib attrib, { int hdrlen; - hdrlen = data.is_override_hdrlen() ? data.ver_hdrlen() & 0x0F : 5; + hdrlen = data.is_override_hdrlen() ? + data.ver_hdrlen() : 5 + data.options().length()/4; + hdrlen &= 0x0F; switch(attrib) { @@ -205,6 +208,7 @@ QVariant Ip4Protocol::fieldData(int index, FieldAttrib attrib, break; case ip4_totLen: { + int ipLen = 20 + data.options().length(); switch(attrib) { case FieldName: @@ -213,7 +217,7 @@ QVariant Ip4Protocol::fieldData(int index, FieldAttrib attrib, { int totlen; totlen = data.is_override_totlen() ? data.totlen() : - (protocolFramePayloadSize(streamIndex) + 20); + (protocolFramePayloadSize(streamIndex) + ipLen); return totlen; } case FieldFrameValue: @@ -221,7 +225,7 @@ QVariant Ip4Protocol::fieldData(int index, FieldAttrib attrib, QByteArray fv; int totlen; totlen = data.is_override_totlen() ? data.totlen() : - (protocolFramePayloadSize(streamIndex) + 20); + (protocolFramePayloadSize(streamIndex) + ipLen); fv.resize(2); qToBigEndian((quint16) totlen, (uchar*) fv.data()); return fv; @@ -230,7 +234,7 @@ QVariant Ip4Protocol::fieldData(int index, FieldAttrib attrib, { int totlen; totlen = data.is_override_totlen() ? data.totlen() : - (protocolFramePayloadSize(streamIndex) + 20); + (protocolFramePayloadSize(streamIndex) + ipLen); return QString("%1").arg(totlen); } case FieldBitSize: @@ -509,6 +513,32 @@ QVariant Ip4Protocol::fieldData(int index, FieldAttrib attrib, } break; } + case ip4_options: + { + QByteArray ba; + switch(attrib) + { + case FieldValue: + case FieldFrameValue: + case FieldTextValue: + ba.append(QString().fromStdString(data.options())); + default: + break; + } + switch(attrib) + { + case FieldName: + return QString("Options"); + case FieldValue: + case FieldFrameValue: + return ba; + case FieldTextValue: + return ba.toHex(); + default: + break; + } + break; + } // Meta fields case ip4_isOverrideVer: @@ -695,6 +725,16 @@ bool Ip4Protocol::setFieldData(int index, const QVariant &value, data.set_dst_ip(dstIp); break; } + case ip4_options: + { + QByteArray ba = value.toByteArray(); + int pad = (4 - (ba.size() % 4)) % 4; + if (pad) + ba.append(QByteArray(pad, 0)); + data.set_options(ba.constData(), ba.size()); + isOk = true; + break; + } // Meta-fields case ip4_isOverrideVer: diff --git a/common/ip4.h b/common/ip4.h index 1d4dd39..5666a9c 100644 --- a/common/ip4.h +++ b/common/ip4.h @@ -44,6 +44,7 @@ public: ip4_cksum, ip4_srcAddr, ip4_dstAddr, + ip4_options, // Meta-fields ip4_isOverrideVer, diff --git a/common/ip4.proto b/common/ip4.proto index be7391d..33ebf5c 100644 --- a/common/ip4.proto +++ b/common/ip4.proto @@ -58,7 +58,7 @@ message Ip4 { optional uint32 dst_ip_count = 20 [default = 16]; optional fixed32 dst_ip_mask = 21 [default = 0xFFFFFF00]; - //! \todo (LOW) IPv4 Options + optional bytes options = 22; } extend Protocol { diff --git a/common/ip4.ui b/common/ip4.ui index 3e98d7c..8d31ed6 100644 --- a/common/ip4.ui +++ b/common/ip4.ui @@ -366,14 +366,7 @@ Length (x4) - - - false - - - TODO - - + diff --git a/common/ip4config.cpp b/common/ip4config.cpp index 261da7a..1e7d743 100644 --- a/common/ip4config.cpp +++ b/common/ip4config.cpp @@ -28,6 +28,8 @@ Ip4ConfigForm::Ip4ConfigForm(QWidget *parent) setupUi(this); leIpVersion->setValidator(new QIntValidator(0, 15, this)); + leIpOptions->setValidator(new QRegExpValidator(QRegExp("[0-9a-fA-F]*"), + this)); connect(cmbIpSrcAddrMode, SIGNAL(currentIndexChanged(int)), this, SLOT(on_cmbIpSrcAddrMode_currentIndexChanged(int))); @@ -176,6 +178,11 @@ void Ip4ConfigForm::loadWidget(AbstractProtocol *proto) Ip4Protocol::ip4_dstAddrMask, AbstractProtocol::FieldValue ).toUInt()).toString()); + leIpOptions->setText( + proto->fieldData( + Ip4Protocol::ip4_options, + AbstractProtocol::FieldValue + ).toByteArray().toHex()); } void Ip4ConfigForm::storeWidget(AbstractProtocol *proto) @@ -263,6 +270,9 @@ void Ip4ConfigForm::storeWidget(AbstractProtocol *proto) proto->setFieldData( Ip4Protocol::ip4_dstAddrMask, QHostAddress(leIpDstAddrMask->text()).toIPv4Address()); + proto->setFieldData( + Ip4Protocol::ip4_options, + QByteArray::fromHex(QByteArray().append(leIpOptions->text()))); } /* From 15b88a480b20371a9418817d57194bbf122e8e24 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sat, 29 Oct 2016 12:26:42 +0530 Subject: [PATCH 10/49] Fix User-Agent header --- common/updater.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/updater.cpp b/common/updater.cpp index fd02cab..b758b3a 100644 --- a/common/updater.cpp +++ b/common/updater.cpp @@ -55,7 +55,7 @@ void Updater::checkForNewVersion() file_ = new QTemporaryFile(); reqHdr.setValue("Host", host); - reqHdr.setValue("UserAgent", userAgent()); + reqHdr.setValue("User-Agent", userAgent()); connect(http_, SIGNAL(responseHeaderReceived(QHttpResponseHeader)), this, SLOT(responseReceived(QHttpResponseHeader))); From 81bb5f54bae95611db9288bc07c04eeff2a88342 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sat, 28 Jan 2017 10:55:07 +0530 Subject: [PATCH 11/49] Bugfix: Fix failure parsing Ostinato file format --- common/fileformat.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/fileformat.proto b/common/fileformat.proto index 10339d7..36e6cce 100644 --- a/common/fileformat.proto +++ b/common/fileformat.proto @@ -88,7 +88,7 @@ message File { // FieldNumber 1 is reserved and MUST not be used! required bytes magic_value = 2; required FileMetaData meta_data = 3; - optional FileContent content_matter = 9; + optional FileContentMatter content_matter = 9; required fixed32 checksum_value = 15; } From 756197a69cd5f622260cb1d49313e67a14a8aea5 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sat, 28 Jan 2017 11:23:59 +0530 Subject: [PATCH 12/49] Squashed commit of the following: commit 33ad1abb1e3469eac489590d9b79bd5c282d7440 Author: Srivats P Date: Wed Jan 4 18:21:07 2017 +0530 Make osx_image global commit d90a99f0523f2bd01a817d81dc42c8e952afb39c Author: Srivats P Date: Tue Jan 3 22:21:02 2017 +0530 include not exclude the osx image commit 4038ca84bd6bec03ff92fc82897ccd14b3ebf40a Author: Srivats P Date: Tue Jan 3 21:36:46 2017 +0530 Fix osx_image location again commit 232dfdb0bcc93438e02b03364faef889caf37f8a Author: Srivats P Date: Tue Jan 3 21:16:15 2017 +0530 Fix incorrect location of overriding osx_image commit 00a487cb6fce8e5da302f2dd9b64df3558f2e4bb Author: Srivats P Date: Tue Jan 3 20:58:09 2017 +0530 Force sierra instead of el-capitan See https://github.com/cartr/homebrew-qt4/issues/7 commit 33c39b8e240958c8f277fadcbb4a0da540134a4e Author: Srivats P Date: Tue Jan 3 20:37:26 2017 +0530 Troubleshoot osx build break changeset #4 commit 8d168099fb1dabaf61b6d7cd3a391a6285a4bb9c Author: Srivats P Date: Tue Jan 3 19:03:56 2017 +0530 Troubleshoot Travis OSX build break changeset #3 commit ae149d0263a73da515bd81b965828c047d68d401 Author: Srivats P Date: Tue Jan 3 18:24:51 2017 +0530 troubleshoot osx build break - changeset #2 commit 262633967951ac2e490426cc29b06d5569e44fd2 Author: Srivats P Date: Tue Jan 3 18:11:46 2017 +0530 debug changes to troubleshoot osx build break commit f3423b7d84929d98794dfd95009b6bbbedacf07a Author: Srivats P Date: Sun Jan 1 12:49:57 2017 +0530 Use latest protobuf version in HomeBrew We had reverted to using protobuf 2.6 when protobuf 3.0 had a regression issue with respect to RPCs - this has been fixed in v3.1.0 --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5efa3f5..535c0eb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ language: cpp +osx_image: xcode8.2 os: - linux @@ -14,7 +15,7 @@ matrix: compiler: gcc before_install: - - "if [ $TRAVIS_OS_NAME = 'osx' ]; then brew update && brew install qt && brew install protobuf260; fi" + - "if [ $TRAVIS_OS_NAME = 'osx' ]; then brew update && brew tap cartr/qt4 && brew tap-pin cartr/qt4 && brew install qt && brew install protobuf && ls -lR /usr/local/include; fi" addons: apt: From a27634ab66d1b009b99bcfe0905500d9e7776e6a Mon Sep 17 00:00:00 2001 From: Steven Date: Thu, 23 Feb 2017 11:33:01 -0500 Subject: [PATCH 13/49] Corrected spelling of Destination --- common/mac.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/mac.cpp b/common/mac.cpp index 3a7df21..c1daf43 100644 --- a/common/mac.cpp +++ b/common/mac.cpp @@ -143,7 +143,7 @@ QVariant MacProtocol::fieldData(int index, FieldAttrib attrib, switch(attrib) { case FieldName: - return QString("Desination"); + return QString("Destination"); case FieldValue: return dstMac; case FieldTextValue: From 6e0676881a06d5c1d9e6accea6c54eca192fe660 Mon Sep 17 00:00:00 2001 From: Steven Date: Thu, 23 Feb 2017 11:41:59 -0500 Subject: [PATCH 14/49] Changed range of printable characters Made matches inclusive and increased range to match more printable characters. This better matches the Hex Dump payload view, which displays all 0x20 to 0x7D. --- client/dumpview.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/dumpview.h b/client/dumpview.h index b170cd0..c99e34a 100644 --- a/client/dumpview.h +++ b/client/dumpview.h @@ -48,7 +48,7 @@ private: void populateDump(QByteArray &dump, int &selOfs, int &selSize, QModelIndex parent = QModelIndex()); bool inline isPrintable(char c) - {if ((c > 48) && (c < 126)) return true; else return false; } + {if ((c >= 32) && (c <= 126)) return true; else return false; } private: QRect mOffsetPaneTopRect; From 5c1aa6f1c37a7e8b88a28aa2c052e07163a0e634 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Mon, 13 Mar 2017 20:12:14 +0530 Subject: [PATCH 15/49] Updated QHexEdit to version 0.8.4 Fixes #103 Fixes #202 --- extra/qhexedit2/qhexedit2.pro | 19 +- extra/qhexedit2/src/VERSION | 1 + extra/qhexedit2/src/chunks.cpp | 323 ++++++++ extra/qhexedit2/src/chunks.h | 77 ++ extra/qhexedit2/src/commands.cpp | 164 ++-- extra/qhexedit2/src/commands.h | 67 +- extra/qhexedit2/src/qhexedit.cpp | 1163 +++++++++++++++++++++++++--- extra/qhexedit2/src/qhexedit.h | 394 +++++++--- extra/qhexedit2/src/qhexedit_p.cpp | 800 ------------------- extra/qhexedit2/src/qhexedit_p.h | 120 --- extra/qhexedit2/src/xbytearray.cpp | 167 ---- extra/qhexedit2/src/xbytearray.h | 66 -- 12 files changed, 1913 insertions(+), 1448 deletions(-) create mode 100644 extra/qhexedit2/src/VERSION create mode 100644 extra/qhexedit2/src/chunks.cpp create mode 100644 extra/qhexedit2/src/chunks.h delete mode 100644 extra/qhexedit2/src/qhexedit_p.cpp delete mode 100644 extra/qhexedit2/src/qhexedit_p.h delete mode 100644 extra/qhexedit2/src/xbytearray.cpp delete mode 100644 extra/qhexedit2/src/xbytearray.h diff --git a/extra/qhexedit2/qhexedit2.pro b/extra/qhexedit2/qhexedit2.pro index 89479e7..7870031 100644 --- a/extra/qhexedit2/qhexedit2.pro +++ b/extra/qhexedit2/qhexedit2.pro @@ -1,12 +1,17 @@ TEMPLATE = lib CONFIG += qt staticlib warn_on +QT += core gui -HEADERS = src/commands.h\ +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +VERSION = 4.0.0 + +DEFINES += QHEXEDIT_EXPORTS + +HEADERS = src/chunks.h\ + src/commands.h \ src/qhexedit.h \ - src/qhexedit_p.h \ - src/xbytearray.h -SOURCES = src/commands.cpp \ - src/qhexedit.cpp \ - src/qhexedit_p.cpp \ - src/xbytearray.cpp +SOURCES = src/chunks.cpp \ + src/commands.cpp \ + src/qhexedit.cpp diff --git a/extra/qhexedit2/src/VERSION b/extra/qhexedit2/src/VERSION new file mode 100644 index 0000000..8caded8 --- /dev/null +++ b/extra/qhexedit2/src/VERSION @@ -0,0 +1 @@ +Release 0.8.4, 2017-01-16 diff --git a/extra/qhexedit2/src/chunks.cpp b/extra/qhexedit2/src/chunks.cpp new file mode 100644 index 0000000..a4e420b --- /dev/null +++ b/extra/qhexedit2/src/chunks.cpp @@ -0,0 +1,323 @@ +#include "chunks.h" +#include + +#define NORMAL 0 +#define HIGHLIGHTED 1 + +#define BUFFER_SIZE 0x10000 +#define CHUNK_SIZE 0x1000 +#define READ_CHUNK_MASK Q_INT64_C(0xfffffffffffff000) + +// ***************************************** Constructors and file settings + +Chunks::Chunks(QObject *parent): QObject(parent) +{ + QBuffer *buf = new QBuffer(this); + setIODevice(*buf); +} + +Chunks::Chunks(QIODevice &ioDevice, QObject *parent): QObject(parent) +{ + setIODevice(ioDevice); +} + +bool Chunks::setIODevice(QIODevice &ioDevice) +{ + _ioDevice = &ioDevice; + bool ok = _ioDevice->open(QIODevice::ReadOnly); + if (ok) // Try to open IODevice + { + _size = _ioDevice->size(); + _ioDevice->close(); + } + else // Fallback is an empty buffer + { + QBuffer *buf = new QBuffer(this); + _ioDevice = buf; + _size = 0; + } + _chunks.clear(); + _pos = 0; + return ok; +} + + +// ***************************************** Getting data out of Chunks + +QByteArray Chunks::data(qint64 pos, qint64 maxSize, QByteArray *highlighted) +{ + qint64 ioDelta = 0; + int chunkIdx = 0; + + Chunk chunk; + QByteArray buffer; + + // Do some checks and some arrangements + if (highlighted) + highlighted->clear(); + + if (pos >= _size) + return buffer; + + if (maxSize < 0) + maxSize = _size; + else + if ((pos + maxSize) > _size) + maxSize = _size - pos; + + _ioDevice->open(QIODevice::ReadOnly); + + while (maxSize > 0) + { + chunk.absPos = LLONG_MAX; + bool chunksLoopOngoing = true; + while ((chunkIdx < _chunks.count()) && chunksLoopOngoing) + { + // In this section, we track changes before our required data and + // we take the editdet data, if availible. ioDelta is a difference + // counter to justify the read pointer to the original data, if + // data in between was deleted or inserted. + + chunk = _chunks[chunkIdx]; + if (chunk.absPos > pos) + chunksLoopOngoing = false; + else + { + chunkIdx += 1; + qint64 count; + qint64 chunkOfs = pos - chunk.absPos; + if (maxSize > ((qint64)chunk.data.size() - chunkOfs)) + { + count = (qint64)chunk.data.size() - chunkOfs; + ioDelta += CHUNK_SIZE - chunk.data.size(); + } + else + count = maxSize; + if (count > 0) + { + buffer += chunk.data.mid(chunkOfs, (int)count); + maxSize -= count; + pos += count; + if (highlighted) + *highlighted += chunk.dataChanged.mid(chunkOfs, (int)count); + } + } + } + + if ((maxSize > 0) && (pos < chunk.absPos)) + { + // In this section, we read data from the original source. This only will + // happen, whe no copied data is available + + qint64 byteCount; + QByteArray readBuffer; + if ((chunk.absPos - pos) > maxSize) + byteCount = maxSize; + else + byteCount = chunk.absPos - pos; + + maxSize -= byteCount; + _ioDevice->seek(pos + ioDelta); + readBuffer = _ioDevice->read(byteCount); + buffer += readBuffer; + if (highlighted) + *highlighted += QByteArray(readBuffer.size(), NORMAL); + pos += readBuffer.size(); + } + } + _ioDevice->close(); + return buffer; +} + +bool Chunks::write(QIODevice &iODevice, qint64 pos, qint64 count) +{ + if (count == -1) + count = _size; + bool ok = iODevice.open(QIODevice::WriteOnly); + if (ok) + { + for (qint64 idx=pos; idx < count; idx += BUFFER_SIZE) + { + QByteArray ba = data(idx, BUFFER_SIZE); + iODevice.write(ba); + } + iODevice.close(); + } + return ok; +} + + +// ***************************************** Set and get highlighting infos + +void Chunks::setDataChanged(qint64 pos, bool dataChanged) +{ + if ((pos < 0) || (pos >= _size)) + return; + int chunkIdx = getChunkIndex(pos); + qint64 posInBa = pos - _chunks[chunkIdx].absPos; + _chunks[chunkIdx].dataChanged[(int)posInBa] = char(dataChanged); +} + +bool Chunks::dataChanged(qint64 pos) +{ + QByteArray highlighted; + data(pos, 1, &highlighted); + return bool(highlighted.at(0)); +} + + +// ***************************************** Search API + +qint64 Chunks::indexOf(const QByteArray &ba, qint64 from) +{ + qint64 result = -1; + QByteArray buffer; + + for (qint64 pos=from; (pos < _size) && (result < 0); pos += BUFFER_SIZE) + { + buffer = data(pos, BUFFER_SIZE + ba.size() - 1); + int findPos = buffer.indexOf(ba); + if (findPos >= 0) + result = pos + (qint64)findPos; + } + return result; +} + +qint64 Chunks::lastIndexOf(const QByteArray &ba, qint64 from) +{ + qint64 result = -1; + QByteArray buffer; + + for (qint64 pos=from; (pos > 0) && (result < 0); pos -= BUFFER_SIZE) + { + qint64 sPos = pos - BUFFER_SIZE - (qint64)ba.size() + 1; + if (sPos < 0) + sPos = 0; + buffer = data(sPos, pos - sPos); + int findPos = buffer.lastIndexOf(ba); + if (findPos >= 0) + result = sPos + (qint64)findPos; + } + return result; +} + + +// ***************************************** Char manipulations + +bool Chunks::insert(qint64 pos, char b) +{ + if ((pos < 0) || (pos > _size)) + return false; + int chunkIdx; + if (pos == _size) + chunkIdx = getChunkIndex(pos-1); + else + chunkIdx = getChunkIndex(pos); + qint64 posInBa = pos - _chunks[chunkIdx].absPos; + _chunks[chunkIdx].data.insert(posInBa, b); + _chunks[chunkIdx].dataChanged.insert(posInBa, char(1)); + for (int idx=chunkIdx+1; idx < _chunks.size(); idx++) + _chunks[idx].absPos += 1; + _size += 1; + _pos = pos; + return true; +} + +bool Chunks::overwrite(qint64 pos, char b) +{ + if ((pos < 0) || (pos >= _size)) + return false; + int chunkIdx = getChunkIndex(pos); + qint64 posInBa = pos - _chunks[chunkIdx].absPos; + _chunks[chunkIdx].data[(int)posInBa] = b; + _chunks[chunkIdx].dataChanged[(int)posInBa] = char(1); + _pos = pos; + return true; +} + +bool Chunks::removeAt(qint64 pos) +{ + if ((pos < 0) || (pos >= _size)) + return false; + int chunkIdx = getChunkIndex(pos); + qint64 posInBa = pos - _chunks[chunkIdx].absPos; + _chunks[chunkIdx].data.remove(posInBa, 1); + _chunks[chunkIdx].dataChanged.remove(posInBa, 1); + for (int idx=chunkIdx+1; idx < _chunks.size(); idx++) + _chunks[idx].absPos -= 1; + _size -= 1; + _pos = pos; + return true; +} + + +// ***************************************** Utility functions + +char Chunks::operator[](qint64 pos) +{ + return data(pos, 1)[0]; +} + +qint64 Chunks::pos() +{ + return _pos; +} + +qint64 Chunks::size() +{ + return _size; +} + +int Chunks::getChunkIndex(qint64 absPos) +{ + // This routine checks, if there is already a copied chunk available. If os, it + // returns a reference to it. If there is no copied chunk available, original + // data will be copied into a new chunk. + + int foundIdx = -1; + int insertIdx = 0; + qint64 ioDelta = 0; + + + for (int idx=0; idx < _chunks.size(); idx++) + { + Chunk chunk = _chunks[idx]; + if ((absPos >= chunk.absPos) && (absPos < (chunk.absPos + chunk.data.size()))) + { + foundIdx = idx; + break; + } + if (absPos < chunk.absPos) + { + insertIdx = idx; + break; + } + ioDelta += chunk.data.size() - CHUNK_SIZE; + insertIdx = idx + 1; + } + + if (foundIdx == -1) + { + Chunk newChunk; + qint64 readAbsPos = absPos - ioDelta; + qint64 readPos = (readAbsPos & READ_CHUNK_MASK); + _ioDevice->open(QIODevice::ReadOnly); + _ioDevice->seek(readPos); + newChunk.data = _ioDevice->read(CHUNK_SIZE); + _ioDevice->close(); + newChunk.absPos = absPos - (readAbsPos - readPos); + newChunk.dataChanged = QByteArray(newChunk.data.size(), char(0)); + _chunks.insert(insertIdx, newChunk); + foundIdx = insertIdx; + } + return foundIdx; +} + + +#ifdef MODUL_TEST +int Chunks::chunkSize() +{ + return _chunks.size(); +} + +#endif diff --git a/extra/qhexedit2/src/chunks.h b/extra/qhexedit2/src/chunks.h new file mode 100644 index 0000000..43df76c --- /dev/null +++ b/extra/qhexedit2/src/chunks.h @@ -0,0 +1,77 @@ +#ifndef CHUNKS_H +#define CHUNKS_H + +/** \cond docNever */ + +/*! The Chunks class is the storage backend for QHexEdit. + * + * When QHexEdit loads data, Chunks access them using a QIODevice interface. When the app uses + * a QByteArray interface, QBuffer is used to provide again a QIODevice like interface. No data + * will be changed, therefore Chunks opens the QIODevice in QIODevice::ReadOnly mode. After every + * access Chunks closes the QIODevice, that's why external applications can overwrite files while + * QHexEdit shows them. + * + * When the the user starts to edit the data, Chunks creates a local copy of a chunk of data (4 + * kilobytes) and notes all changes there. Parallel to that chunk, there is a second chunk, + * which keep track of which bytes are changed and which not. + * + */ + +#include + +struct Chunk +{ + QByteArray data; + QByteArray dataChanged; + qint64 absPos; +}; + +class Chunks: public QObject +{ +Q_OBJECT +public: + // Constructors and file settings + Chunks(QObject *parent); + Chunks(QIODevice &ioDevice, QObject *parent); + bool setIODevice(QIODevice &ioDevice); + + // Getting data out of Chunks + QByteArray data(qint64 pos=0, qint64 count=-1, QByteArray *highlighted=0); + bool write(QIODevice &iODevice, qint64 pos=0, qint64 count=-1); + + // Set and get highlighting infos + void setDataChanged(qint64 pos, bool dataChanged); + bool dataChanged(qint64 pos); + + // Search API + qint64 indexOf(const QByteArray &ba, qint64 from); + qint64 lastIndexOf(const QByteArray &ba, qint64 from); + + // Char manipulations + bool insert(qint64 pos, char b); + bool overwrite(qint64 pos, char b); + bool removeAt(qint64 pos); + + // Utility functions + char operator[](qint64 pos); + qint64 pos(); + qint64 size(); + + +private: + int getChunkIndex(qint64 absPos); + + QIODevice * _ioDevice; + qint64 _pos; + qint64 _size; + QList _chunks; + +#ifdef MODUL_TEST +public: + int chunkSize(); +#endif +}; + +/** \endcond docNever */ + +#endif // CHUNKS_H diff --git a/extra/qhexedit2/src/commands.cpp b/extra/qhexedit2/src/commands.cpp index 303091d..e9530d5 100644 --- a/extra/qhexedit2/src/commands.cpp +++ b/extra/qhexedit2/src/commands.cpp @@ -1,9 +1,34 @@ #include "commands.h" +#include -CharCommand::CharCommand(XByteArray * xData, Cmd cmd, int charPos, char newChar, QUndoCommand *parent) + +// Helper class to store single byte commands +class CharCommand : public QUndoCommand +{ +public: + enum CCmd {insert, removeAt, overwrite}; + + CharCommand(Chunks * chunks, CCmd cmd, qint64 charPos, char newChar, + QUndoCommand *parent=0); + + void undo(); + void redo(); + bool mergeWith(const QUndoCommand *command); + int id() const { return 1234; } + +private: + Chunks * _chunks; + qint64 _charPos; + bool _wasChanged; + char _newChar; + char _oldChar; + CCmd _cmd; +}; + +CharCommand::CharCommand(Chunks * chunks, CCmd cmd, qint64 charPos, char newChar, QUndoCommand *parent) : QUndoCommand(parent) { - _xData = xData; + _chunks = chunks; _charPos = charPos; _newChar = newChar; _cmd = cmd; @@ -14,9 +39,9 @@ bool CharCommand::mergeWith(const QUndoCommand *command) const CharCommand *nextCommand = static_cast(command); bool result = false; - if (_cmd != remove) + if (_cmd != CharCommand::removeAt) { - if (nextCommand->_cmd == replace) + if (nextCommand->_cmd == overwrite) if (nextCommand->_charPos == _charPos) { _newChar = nextCommand->_newChar; @@ -31,15 +56,15 @@ void CharCommand::undo() switch (_cmd) { case insert: - _xData->remove(_charPos, 1); + _chunks->removeAt(_charPos); break; - case replace: - _xData->replace(_charPos, _oldChar); - _xData->setDataChanged(_charPos, _wasChanged); + case overwrite: + _chunks->overwrite(_charPos, _oldChar); + _chunks->setDataChanged(_charPos, _wasChanged); break; - case remove: - _xData->insert(_charPos, _oldChar); - _xData->setDataChanged(_charPos, _wasChanged); + case removeAt: + _chunks->insert(_charPos, _oldChar); + _chunks->setDataChanged(_charPos, _wasChanged); break; } } @@ -49,67 +74,92 @@ void CharCommand::redo() switch (_cmd) { case insert: - _xData->insert(_charPos, _newChar); + _chunks->insert(_charPos, _newChar); break; - case replace: - _oldChar = _xData->data()[_charPos]; - _wasChanged = _xData->dataChanged(_charPos); - _xData->replace(_charPos, _newChar); + case overwrite: + _oldChar = (*_chunks)[_charPos]; + _wasChanged = _chunks->dataChanged(_charPos); + _chunks->overwrite(_charPos, _newChar); break; - case remove: - _oldChar = _xData->data()[_charPos]; - _wasChanged = _xData->dataChanged(_charPos); - _xData->remove(_charPos, 1); + case removeAt: + _oldChar = (*_chunks)[_charPos]; + _wasChanged = _chunks->dataChanged(_charPos); + _chunks->removeAt(_charPos); break; } } - - -ArrayCommand::ArrayCommand(XByteArray * xData, Cmd cmd, int baPos, QByteArray newBa, int len, QUndoCommand *parent) - : QUndoCommand(parent) +UndoStack::UndoStack(Chunks * chunks, QObject * parent) + : QUndoStack(parent) { - _cmd = cmd; - _xData = xData; - _baPos = baPos; - _newBa = newBa; - _len = len; + _chunks = chunks; + _parent = parent; } -void ArrayCommand::undo() +void UndoStack::insert(qint64 pos, char c) { - switch (_cmd) + if ((pos >= 0) && (pos <= _chunks->size())) { - case insert: - _xData->remove(_baPos, _newBa.length()); - break; - case replace: - _xData->replace(_baPos, _oldBa); - _xData->setDataChanged(_baPos, _wasChanged); - break; - case remove: - _xData->insert(_baPos, _oldBa); - _xData->setDataChanged(_baPos, _wasChanged); - break; + QUndoCommand *cc = new CharCommand(_chunks, CharCommand::insert, pos, c); + this->push(cc); } } -void ArrayCommand::redo() +void UndoStack::insert(qint64 pos, const QByteArray &ba) { - switch (_cmd) + if ((pos >= 0) && (pos <= _chunks->size())) { - case insert: - _xData->insert(_baPos, _newBa); - break; - case replace: - _oldBa = _xData->data().mid(_baPos, _len); - _wasChanged = _xData->dataChanged(_baPos, _len); - _xData->replace(_baPos, _newBa); - break; - case remove: - _oldBa = _xData->data().mid(_baPos, _len); - _wasChanged = _xData->dataChanged(_baPos, _len); - _xData->remove(_baPos, _len); - break; + QString txt = QString(tr("Inserting %1 bytes")).arg(ba.size()); + beginMacro(txt); + for (int idx=0; idx < ba.size(); idx++) + { + QUndoCommand *cc = new CharCommand(_chunks, CharCommand::insert, pos + idx, ba.at(idx)); + this->push(cc); + } + endMacro(); + } +} + +void UndoStack::removeAt(qint64 pos, qint64 len) +{ + if ((pos >= 0) && (pos < _chunks->size())) + { + if (len==1) + { + QUndoCommand *cc = new CharCommand(_chunks, CharCommand::removeAt, pos, char(0)); + this->push(cc); + } + else + { + QString txt = QString(tr("Delete %1 chars")).arg(len); + beginMacro(txt); + for (qint64 cnt=0; cnt= 0) && (pos < _chunks->size())) + { + QUndoCommand *cc = new CharCommand(_chunks, CharCommand::overwrite, pos, c); + this->push(cc); + } +} + +void UndoStack::overwrite(qint64 pos, int len, const QByteArray &ba) +{ + if ((pos >= 0) && (pos < _chunks->size())) + { + QString txt = QString(tr("Overwrite %1 chars")).arg(len); + beginMacro(txt); + removeAt(pos, len); + insert(pos, ba); + endMacro(); } } diff --git a/extra/qhexedit2/src/commands.h b/extra/qhexedit2/src/commands.h index 9931b3f..9c34683 100644 --- a/extra/qhexedit2/src/commands.h +++ b/extra/qhexedit2/src/commands.h @@ -3,66 +3,43 @@ /** \cond docNever */ -#include +#include -#include "xbytearray.h" +#include "chunks.h" -/*! CharCommand is a class to prived undo/redo functionality in QHexEdit. +/*! CharCommand is a class to provid undo/redo functionality in QHexEdit. A QUndoCommand represents a single editing action on a document. CharCommand -is responsable for manipulations on single chars. It can insert. replace and -remove characters. A manipulation stores allways to actions +is responsable for manipulations on single chars. It can insert. overwrite and +remove characters. A manipulation stores allways two actions 1. redo (or do) action 2. undo action. CharCommand also supports command compression via mergeWidht(). This allows the user to execute a undo command contation e.g. 3 steps in a single command. If you for example insert a new byt "34" this means for the editor doing 3 -steps: insert a "00", replace it with "03" and the replace it with "34". These +steps: insert a "00", overwrite it with "03" and the overwrite it with "34". These 3 steps are combined into a single step, insert a "34". + +The byte array oriented commands are just put into a set of single byte commands, +which are pooled together with the macroBegin() and macroEnd() functionality of +Qt's QUndoStack. */ -class CharCommand : public QUndoCommand + +class UndoStack : public QUndoStack { + Q_OBJECT + public: - enum { Id = 1234 }; - enum Cmd {insert, remove, replace}; - - CharCommand(XByteArray * xData, Cmd cmd, int charPos, char newChar, - QUndoCommand *parent=0); - - void undo(); - void redo(); - bool mergeWith(const QUndoCommand *command); - int id() const { return Id; } + UndoStack(Chunks *chunks, QObject * parent=0); + void insert(qint64 pos, char c); + void insert(qint64 pos, const QByteArray &ba); + void removeAt(qint64 pos, qint64 len=1); + void overwrite(qint64 pos, char c); + void overwrite(qint64 pos, int len, const QByteArray &ba); private: - XByteArray * _xData; - int _charPos; - bool _wasChanged; - char _newChar; - char _oldChar; - Cmd _cmd; -}; - -/*! ArrayCommand provides undo/redo functionality for handling binary strings. It -can undo/redo insert, replace and remove binary strins (QByteArrays). -*/ -class ArrayCommand : public QUndoCommand -{ -public: - enum Cmd {insert, remove, replace}; - ArrayCommand(XByteArray * xData, Cmd cmd, int baPos, QByteArray newBa=QByteArray(), int len=0, - QUndoCommand *parent=0); - void undo(); - void redo(); - -private: - Cmd _cmd; - XByteArray * _xData; - int _baPos; - int _len; - QByteArray _wasChanged; - QByteArray _newBa; - QByteArray _oldBa; + Chunks * _chunks; + QObject * _parent; }; /** \endcond docNever */ diff --git a/extra/qhexedit2/src/qhexedit.cpp b/extra/qhexedit2/src/qhexedit.cpp index b6dd38d..df99332 100644 --- a/extra/qhexedit2/src/qhexedit.cpp +++ b/extra/qhexedit2/src/qhexedit.cpp @@ -1,152 +1,1123 @@ -#include +#include +#include +#include +#include +#include #include "qhexedit.h" +#include -QHexEdit::QHexEdit(QWidget *parent) : QScrollArea(parent) +// ********************************************************************** Constructor, destructor + +QHexEdit::QHexEdit(QWidget *parent) : QAbstractScrollArea(parent) { - qHexEdit_p = new QHexEditPrivate(this); - setWidget(qHexEdit_p); - setWidgetResizable(true); + _addressArea = true; + _addressWidth = 4; + _asciiArea = true; + _overwriteMode = true; + _highlighting = true; + _readOnly = false; + _cursorPosition = 0; + _lastEventSize = 0; + _hexCharsInLine = 47; + _bytesPerLine = 16; + _editAreaIsAscii = false; + _hexCaps = false; + _dynamicBytesPerLine = false; + + _chunks = new Chunks(this); + _undoStack = new UndoStack(_chunks, this); +#ifdef Q_OS_WIN32 + setFont(QFont("Courier", 10)); +#else + setFont(QFont("Monospace", 10)); +#endif + setAddressAreaColor(this->palette().alternateBase().color()); + setHighlightingColor(QColor(0xff, 0xff, 0x99, 0xff)); + setSelectionColor(this->palette().highlight().color()); + + connect(&_cursorTimer, SIGNAL(timeout()), this, SLOT(updateCursor())); + connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(adjust())); + connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(adjust())); + connect(_undoStack, SIGNAL(indexChanged(int)), this, SLOT(dataChangedPrivate(int))); + + _cursorTimer.setInterval(500); + _cursorTimer.start(); + + setAddressWidth(4); + setAddressArea(true); + setAsciiArea(true); + setOverwriteMode(true); + setHighlighting(true); + setReadOnly(false); + + init(); - connect(qHexEdit_p, SIGNAL(currentAddressChanged(int)), this, SIGNAL(currentAddressChanged(int))); - connect(qHexEdit_p, SIGNAL(currentSizeChanged(int)), this, SIGNAL(currentSizeChanged(int))); - connect(qHexEdit_p, SIGNAL(dataChanged()), this, SIGNAL(dataChanged())); - connect(qHexEdit_p, SIGNAL(overwriteModeChanged(bool)), this, SIGNAL(overwriteModeChanged(bool))); - setFocusPolicy(Qt::NoFocus); } -void QHexEdit::insert(int i, const QByteArray & ba) +QHexEdit::~QHexEdit() { - qHexEdit_p->insert(i, ba); } -void QHexEdit::insert(int i, char ch) -{ - qHexEdit_p->insert(i, ch); -} - -void QHexEdit::remove(int pos, int len) -{ - qHexEdit_p->remove(pos, len); -} - -QString QHexEdit::toReadableString() -{ - return qHexEdit_p->toRedableString(); -} - -QString QHexEdit::selectionToReadableString() -{ - return qHexEdit_p->selectionToReadableString(); -} +// ********************************************************************** Properties void QHexEdit::setAddressArea(bool addressArea) { - qHexEdit_p->setAddressArea(addressArea); + _addressArea = addressArea; + adjust(); + setCursorPosition(_cursorPosition); + viewport()->update(); } -void QHexEdit::redo() +bool QHexEdit::addressArea() { - qHexEdit_p->redo(); -} - -void QHexEdit::undo() -{ - qHexEdit_p->undo(); -} - -void QHexEdit::setAddressWidth(int addressWidth) -{ - qHexEdit_p->setAddressWidth(addressWidth); -} - -void QHexEdit::setAsciiArea(bool asciiArea) -{ - qHexEdit_p->setAsciiArea(asciiArea); -} - -void QHexEdit::setHighlighting(bool mode) -{ - qHexEdit_p->setHighlighting(mode); -} - -void QHexEdit::setAddressOffset(int offset) -{ - qHexEdit_p->setAddressOffset(offset); -} - -int QHexEdit::addressOffset() -{ - return qHexEdit_p->addressOffset(); -} - -void QHexEdit::setData(const QByteArray &data) -{ - qHexEdit_p->setData(data); -} - -QByteArray QHexEdit::data() -{ - return qHexEdit_p->data(); + return _addressArea; } void QHexEdit::setAddressAreaColor(const QColor &color) { - qHexEdit_p->setAddressAreaColor(color); + _addressAreaColor = color; + viewport()->update(); } QColor QHexEdit::addressAreaColor() { - return qHexEdit_p->addressAreaColor(); + return _addressAreaColor; +} + +void QHexEdit::setAddressOffset(qint64 addressOffset) +{ + _addressOffset = addressOffset; + adjust(); + setCursorPosition(_cursorPosition); + viewport()->update(); +} + +qint64 QHexEdit::addressOffset() +{ + return _addressOffset; +} + +void QHexEdit::setAddressWidth(int addressWidth) +{ + _addressWidth = addressWidth; + adjust(); + setCursorPosition(_cursorPosition); + viewport()->update(); +} + +int QHexEdit::addressWidth() +{ + qint64 size = _chunks->size(); + int n = 1; + if (size > Q_INT64_C(0x100000000)){ n += 8; size /= Q_INT64_C(0x100000000);} + if (size > 0x10000){ n += 4; size /= 0x10000;} + if (size > 0x100){ n += 2; size /= 0x100;} + if (size > 0x10){ n += 1; size /= 0x10;} + + if (n > _addressWidth) + return n; + else + return _addressWidth; +} + +void QHexEdit::setAsciiArea(bool asciiArea) +{ + if (!asciiArea) + _editAreaIsAscii = false; + _asciiArea = asciiArea; + adjust(); + setCursorPosition(_cursorPosition); + viewport()->update(); +} + +bool QHexEdit::asciiArea() +{ + return _asciiArea; +} + +void QHexEdit::setBytesPerLine(int count) +{ + _bytesPerLine = count; + _hexCharsInLine = count * 3 - 1; + + adjust(); + setCursorPosition(_cursorPosition); + viewport()->update(); +} + +int QHexEdit::bytesPerLine() +{ + return _bytesPerLine; +} + +void QHexEdit::setCursorPosition(qint64 position) +{ + // 1. delete old cursor + _blink = false; + viewport()->update(_cursorRect); + + // 2. Check, if cursor in range? + if (position > (_chunks->size() * 2 - 1)) + position = _chunks->size() * 2 - (_overwriteMode ? 1 : 0); + + if (position < 0) + position = 0; + + // 3. Calc new position of cursor + _bPosCurrent = position / 2; + _pxCursorY = ((position / 2 - _bPosFirst) / _bytesPerLine + 1) * _pxCharHeight; + int x = (position % (2 * _bytesPerLine)); + if (_editAreaIsAscii) + { + _pxCursorX = x / 2 * _pxCharWidth + _pxPosAsciiX; + _cursorPosition = position & 0xFFFFFFFFFFFFFFFELL; + } else { + _pxCursorX = (((x / 2) * 3) + (x % 2)) * _pxCharWidth + _pxPosHexX; + _cursorPosition = position; + } + + if (_overwriteMode) + _cursorRect = QRect(_pxCursorX - horizontalScrollBar()->value(), _pxCursorY + _pxCursorWidth, _pxCharWidth, _pxCursorWidth); + else + _cursorRect = QRect(_pxCursorX - horizontalScrollBar()->value(), _pxCursorY - _pxCharHeight + 4, _pxCursorWidth, _pxCharHeight); + + // 4. Immediately draw new cursor + _blink = true; + viewport()->update(_cursorRect); + emit currentAddressChanged(_bPosCurrent); +} + +qint64 QHexEdit::cursorPosition(QPoint pos) +{ + // Calc cursor position depending on a graphical position + qint64 result = -1; + int posX = pos.x() + horizontalScrollBar()->value(); + int posY = pos.y() - 3; + if ((posX >= _pxPosHexX) && (posX < (_pxPosHexX + (1 + _hexCharsInLine) * _pxCharWidth))) + { + _editAreaIsAscii = false; + int x = (posX - _pxPosHexX) / _pxCharWidth; + x = (x / 3) * 2 + x % 3; + int y = (posY / _pxCharHeight) * 2 * _bytesPerLine; + result = _bPosFirst * 2 + x + y; + } + else + if (_asciiArea && (posX >= _pxPosAsciiX) && (posX < (_pxPosAsciiX + (1 + _bytesPerLine) * _pxCharWidth))) + { + _editAreaIsAscii = true; + int x = 2 * (posX - _pxPosAsciiX) / _pxCharWidth; + int y = (posY / _pxCharHeight) * 2 * _bytesPerLine; + result = _bPosFirst * 2 + x + y; + } + return result; +} + +qint64 QHexEdit::cursorPosition() +{ + return _cursorPosition; +} + +void QHexEdit::setData(const QByteArray &ba) +{ + _data = ba; + _bData.setData(_data); + setData(_bData); +} + +QByteArray QHexEdit::data() +{ + return _chunks->data(0, -1); +} + +void QHexEdit::setHighlighting(bool highlighting) +{ + _highlighting = highlighting; + viewport()->update(); +} + +bool QHexEdit::highlighting() +{ + return _highlighting; } void QHexEdit::setHighlightingColor(const QColor &color) { - qHexEdit_p->setHighlightingColor(color); + _brushHighlighted = QBrush(color); + _penHighlighted = QPen(viewport()->palette().color(QPalette::WindowText)); + viewport()->update(); } QColor QHexEdit::highlightingColor() { - return qHexEdit_p->highlightingColor(); -} - -void QHexEdit::setSelectionColor(const QColor &color) -{ - qHexEdit_p->setSelectionColor(color); -} - -QColor QHexEdit::selectionColor() -{ - return qHexEdit_p->selectionColor(); + return _brushHighlighted.color(); } void QHexEdit::setOverwriteMode(bool overwriteMode) { - qHexEdit_p->setOverwriteMode(overwriteMode); + _overwriteMode = overwriteMode; + emit overwriteModeChanged(overwriteMode); } bool QHexEdit::overwriteMode() { - return qHexEdit_p->overwriteMode(); + return _overwriteMode; } -void QHexEdit::setReadOnly(bool readOnly) +void QHexEdit::setSelectionColor(const QColor &color) { - qHexEdit_p->setReadOnly(readOnly); + _brushSelection = QBrush(color); + _penSelection = QPen(Qt::white); + viewport()->update(); +} + +QColor QHexEdit::selectionColor() +{ + return _brushSelection.color(); } bool QHexEdit::isReadOnly() { - return qHexEdit_p->isReadOnly(); + return _readOnly; +} + +void QHexEdit::setReadOnly(bool readOnly) +{ + _readOnly = readOnly; +} + +void QHexEdit::setHexCaps(const bool isCaps) +{ + if (_hexCaps != isCaps) + { + _hexCaps = isCaps; + viewport()->update(); + } +} + +bool QHexEdit::hexCaps() +{ + return _hexCaps; +} + +void QHexEdit::setDynamicBytesPerLine(const bool isDynamic) +{ + _dynamicBytesPerLine = isDynamic; + resizeEvent(NULL); +} + +bool QHexEdit::dynamicBytesPerLine() +{ + return _dynamicBytesPerLine; +} + +// ********************************************************************** Access to data of qhexedit +bool QHexEdit::setData(QIODevice &iODevice) +{ + bool ok = _chunks->setIODevice(iODevice); + init(); + dataChangedPrivate(); + return ok; +} + +QByteArray QHexEdit::dataAt(qint64 pos, qint64 count) +{ + return _chunks->data(pos, count); +} + +bool QHexEdit::write(QIODevice &iODevice, qint64 pos, qint64 count) +{ + return _chunks->write(iODevice, pos, count); +} + +// ********************************************************************** Char handling +void QHexEdit::insert(qint64 index, char ch) +{ + _undoStack->insert(index, ch); + refresh(); +} + +void QHexEdit::remove(qint64 index, qint64 len) +{ + _undoStack->removeAt(index, len); + refresh(); +} + +void QHexEdit::replace(qint64 index, char ch) +{ + _undoStack->overwrite(index, ch); + refresh(); +} + +// ********************************************************************** ByteArray handling +void QHexEdit::insert(qint64 pos, const QByteArray &ba) +{ + _undoStack->insert(pos, ba); + refresh(); +} + +void QHexEdit::replace(qint64 pos, qint64 len, const QByteArray &ba) +{ + _undoStack->overwrite(pos, len, ba); + refresh(); +} + +// ********************************************************************** Utility functions +void QHexEdit::ensureVisible() +{ + if (_cursorPosition < (_bPosFirst * 2)) + verticalScrollBar()->setValue((int)(_cursorPosition / 2 / _bytesPerLine)); + if (_cursorPosition > ((_bPosFirst + (_rowsShown - 1)*_bytesPerLine) * 2)) + verticalScrollBar()->setValue((int)(_cursorPosition / 2 / _bytesPerLine) - _rowsShown + 1); + if (_pxCursorX < horizontalScrollBar()->value()) + horizontalScrollBar()->setValue(_pxCursorX); + if ((_pxCursorX + _pxCharWidth) > (horizontalScrollBar()->value() + viewport()->width())) + horizontalScrollBar()->setValue(_pxCursorX + _pxCharWidth - viewport()->width()); + viewport()->update(); +} + +qint64 QHexEdit::indexOf(const QByteArray &ba, qint64 from) +{ + qint64 pos = _chunks->indexOf(ba, from); + if (pos > -1) + { + qint64 curPos = pos*2; + setCursorPosition(curPos + ba.length()*2); + resetSelection(curPos); + setSelection(curPos + ba.length()*2); + ensureVisible(); + } + return pos; +} + +bool QHexEdit::isModified() +{ + return _modified; +} + +qint64 QHexEdit::lastIndexOf(const QByteArray &ba, qint64 from) +{ + qint64 pos = _chunks->lastIndexOf(ba, from); + if (pos > -1) + { + qint64 curPos = pos*2; + setCursorPosition(curPos - 1); + resetSelection(curPos); + setSelection(curPos + ba.length()*2); + ensureVisible(); + } + return pos; +} + +void QHexEdit::redo() +{ + _undoStack->redo(); + setCursorPosition(_chunks->pos()*(_editAreaIsAscii ? 1 : 2)); + refresh(); +} + +QString QHexEdit::selectionToReadableString() +{ + QByteArray ba = _chunks->data(getSelectionBegin(), getSelectionEnd() - getSelectionBegin()); + return toReadable(ba); } void QHexEdit::setFont(const QFont &font) { - qHexEdit_p->setFont(font); + QWidget::setFont(font); + _pxCharWidth = fontMetrics().width(QLatin1Char('2')); + _pxCharHeight = fontMetrics().height(); + _pxGapAdr = _pxCharWidth / 2; + _pxGapAdrHex = _pxCharWidth; + _pxGapHexAscii = 2 * _pxCharWidth; + _pxCursorWidth = _pxCharHeight / 7; + _pxSelectionSub = _pxCharHeight / 5; + viewport()->update(); } -const QFont & QHexEdit::font() const +QString QHexEdit::toReadableString() { - return qHexEdit_p->font(); + QByteArray ba = _chunks->data(); + return toReadable(ba); +} + +void QHexEdit::undo() +{ + _undoStack->undo(); + setCursorPosition(_chunks->pos()*(_editAreaIsAscii ? 1 : 2)); + refresh(); +} + +// ********************************************************************** Handle events +void QHexEdit::keyPressEvent(QKeyEvent *event) +{ + // Cursor movements + if (event->matches(QKeySequence::MoveToNextChar)) + { + qint64 pos = _cursorPosition + 1; + if (_editAreaIsAscii) + pos += 1; + setCursorPosition(pos); + resetSelection(pos); + } + if (event->matches(QKeySequence::MoveToPreviousChar)) + { + qint64 pos = _cursorPosition - 1; + if (_editAreaIsAscii) + pos -= 1; + setCursorPosition(pos); + resetSelection(pos); + } + if (event->matches(QKeySequence::MoveToEndOfLine)) + { + qint64 pos = _cursorPosition - (_cursorPosition % (2 * _bytesPerLine)) + (2 * _bytesPerLine) - 1; + setCursorPosition(pos); + resetSelection(_cursorPosition); + } + if (event->matches(QKeySequence::MoveToStartOfLine)) + { + qint64 pos = _cursorPosition - (_cursorPosition % (2 * _bytesPerLine)); + setCursorPosition(pos); + resetSelection(_cursorPosition); + } + if (event->matches(QKeySequence::MoveToPreviousLine)) + { + setCursorPosition(_cursorPosition - (2 * _bytesPerLine)); + resetSelection(_cursorPosition); + } + if (event->matches(QKeySequence::MoveToNextLine)) + { + setCursorPosition(_cursorPosition + (2 * _bytesPerLine)); + resetSelection(_cursorPosition); + } + if (event->matches(QKeySequence::MoveToNextPage)) + { + setCursorPosition(_cursorPosition + (((_rowsShown - 1) * 2 * _bytesPerLine))); + resetSelection(_cursorPosition); + } + if (event->matches(QKeySequence::MoveToPreviousPage)) + { + setCursorPosition(_cursorPosition - (((_rowsShown - 1) * 2 * _bytesPerLine))); + resetSelection(_cursorPosition); + } + if (event->matches(QKeySequence::MoveToEndOfDocument)) + { + setCursorPosition(_chunks->size() * 2 ); + resetSelection(_cursorPosition); + } + if (event->matches(QKeySequence::MoveToStartOfDocument)) + { + setCursorPosition(0); + resetSelection(_cursorPosition); + } + + // Select commands + if (event->matches(QKeySequence::SelectAll)) + { + resetSelection(0); + setSelection(2 * _chunks->size() + 1); + } + if (event->matches(QKeySequence::SelectNextChar)) + { + qint64 pos = _cursorPosition + 1; + if (_editAreaIsAscii) + pos += 1; + setCursorPosition(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectPreviousChar)) + { + qint64 pos = _cursorPosition - 1; + if (_editAreaIsAscii) + pos -= 1; + setSelection(pos); + setCursorPosition(pos); + } + if (event->matches(QKeySequence::SelectEndOfLine)) + { + qint64 pos = _cursorPosition - (_cursorPosition % (2 * _bytesPerLine)) + (2 * _bytesPerLine) - 1; + setCursorPosition(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectStartOfLine)) + { + qint64 pos = _cursorPosition - (_cursorPosition % (2 * _bytesPerLine)); + setCursorPosition(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectPreviousLine)) + { + qint64 pos = _cursorPosition - (2 * _bytesPerLine); + setCursorPosition(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectNextLine)) + { + qint64 pos = _cursorPosition + (2 * _bytesPerLine); + setCursorPosition(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectNextPage)) + { + qint64 pos = _cursorPosition + (((viewport()->height() / _pxCharHeight) - 1) * 2 * _bytesPerLine); + setCursorPosition(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectPreviousPage)) + { + qint64 pos = _cursorPosition - (((viewport()->height() / _pxCharHeight) - 1) * 2 * _bytesPerLine); + setCursorPosition(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectEndOfDocument)) + { + qint64 pos = _chunks->size() * 2; + setCursorPosition(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectStartOfDocument)) + { + qint64 pos = 0; + setCursorPosition(pos); + setSelection(pos); + } + + // Edit Commands + if (!_readOnly) + { + /* Cut */ + if (event->matches(QKeySequence::Cut)) + { + QByteArray ba = _chunks->data(getSelectionBegin(), getSelectionEnd() - getSelectionBegin()).toHex(); + for (qint64 idx = 32; idx < ba.size(); idx +=33) + ba.insert(idx, "\n"); + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(ba); + if (_overwriteMode) + { + qint64 len = getSelectionEnd() - getSelectionBegin(); + replace(getSelectionBegin(), (int)len, QByteArray((int)len, char(0))); + } + else + { + remove(getSelectionBegin(), getSelectionEnd() - getSelectionBegin()); + } + setCursorPosition(2 * getSelectionBegin()); + resetSelection(2 * getSelectionBegin()); + } else + + /* Paste */ + if (event->matches(QKeySequence::Paste)) + { + QClipboard *clipboard = QApplication::clipboard(); + QByteArray ba = QByteArray().fromHex(clipboard->text().toLatin1()); + if (_overwriteMode) + { + ba = ba.left(std::min(ba.size(), (_chunks->size() - _bPosCurrent))); + replace(_bPosCurrent, ba.size(), ba); + } + else + insert(_bPosCurrent, ba); + setCursorPosition(_cursorPosition + 2 * ba.size()); + resetSelection(getSelectionBegin()); + } else + + /* Delete char */ + if (event->matches(QKeySequence::Delete)) + { + if (getSelectionBegin() != getSelectionEnd()) + { + _bPosCurrent = getSelectionBegin(); + if (_overwriteMode) + { + QByteArray ba = QByteArray(getSelectionEnd() - getSelectionBegin(), char(0)); + replace(_bPosCurrent, ba.size(), ba); + } + else + { + remove(_bPosCurrent, getSelectionEnd() - getSelectionBegin()); + } + } + else + { + if (_overwriteMode) + replace(_bPosCurrent, char(0)); + else + remove(_bPosCurrent, 1); + } + setCursorPosition(2 * _bPosCurrent); + resetSelection(2 * _bPosCurrent); + } else + + /* Backspace */ + if ((event->key() == Qt::Key_Backspace) && (event->modifiers() == Qt::NoModifier)) + { + if (getSelectionBegin() != getSelectionEnd()) + { + _bPosCurrent = getSelectionBegin(); + setCursorPosition(2 * _bPosCurrent); + if (_overwriteMode) + { + QByteArray ba = QByteArray(getSelectionEnd() - getSelectionBegin(), char(0)); + replace(_bPosCurrent, ba.size(), ba); + } + else + { + remove(_bPosCurrent, getSelectionEnd() - getSelectionBegin()); + } + resetSelection(2 * _bPosCurrent); + } + else + { + bool behindLastByte = false; + if ((_cursorPosition / 2) == _chunks->size()) + behindLastByte = true; + + _bPosCurrent -= 1; + if (_overwriteMode) + replace(_bPosCurrent, char(0)); + else + remove(_bPosCurrent, 1); + + if (!behindLastByte) + _bPosCurrent -= 1; + + setCursorPosition(2 * _bPosCurrent); + resetSelection(2 * _bPosCurrent); + } + } else + + /* undo */ + if (event->matches(QKeySequence::Undo)) + { + undo(); + } else + + /* redo */ + if (event->matches(QKeySequence::Redo)) + { + redo(); + } else + + if ((QApplication::keyboardModifiers() == Qt::NoModifier) || + (QApplication::keyboardModifiers() == Qt::KeypadModifier) || + (QApplication::keyboardModifiers() == Qt::ShiftModifier) || + (QApplication::keyboardModifiers() == (Qt::AltModifier | Qt::ControlModifier)) || + (QApplication::keyboardModifiers() == Qt::GroupSwitchModifier)) + { + /* Hex and ascii input */ + int key; + if (_editAreaIsAscii) + key = (uchar)event->text()[0].toLatin1(); + else + key = int(event->text()[0].toLower().toLatin1()); + + if ((((key >= '0' && key <= '9') || (key >= 'a' && key <= 'f')) && _editAreaIsAscii == false) + || (key >= ' ' && _editAreaIsAscii)) + { + if (getSelectionBegin() != getSelectionEnd()) + { + if (_overwriteMode) + { + qint64 len = getSelectionEnd() - getSelectionBegin(); + replace(getSelectionBegin(), (int)len, QByteArray((int)len, char(0))); + } else + { + remove(getSelectionBegin(), getSelectionEnd() - getSelectionBegin()); + _bPosCurrent = getSelectionBegin(); + } + setCursorPosition(2 * _bPosCurrent); + resetSelection(2 * _bPosCurrent); + } + + // If insert mode, then insert a byte + if (_overwriteMode == false) + if ((_cursorPosition % 2) == 0) + insert(_bPosCurrent, char(0)); + + // Change content + if (_chunks->size() > 0) + { + char ch = key; + if (!_editAreaIsAscii){ + QByteArray hexValue = _chunks->data(_bPosCurrent, 1).toHex(); + if ((_cursorPosition % 2) == 0) + hexValue[0] = key; + else + hexValue[1] = key; + ch = QByteArray().fromHex(hexValue)[0]; + } + replace(_bPosCurrent, ch); + if (_editAreaIsAscii) + setCursorPosition(_cursorPosition + 2); + else + setCursorPosition(_cursorPosition + 1); + resetSelection(_cursorPosition); + } + } + } + + + } + + /* Copy */ + if (event->matches(QKeySequence::Copy)) + { + QByteArray ba = _chunks->data(getSelectionBegin(), getSelectionEnd() - getSelectionBegin()).toHex(); + for (qint64 idx = 32; idx < ba.size(); idx +=33) + ba.insert(idx, "\n"); + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(ba); + } + + // Switch between insert/overwrite mode + if ((event->key() == Qt::Key_Insert) && (event->modifiers() == Qt::NoModifier)) + { + setOverwriteMode(!overwriteMode()); + setCursorPosition(_cursorPosition); + } + + // switch from hex to ascii edit + if (event->key() == Qt::Key_Tab && !_editAreaIsAscii){ + _editAreaIsAscii = true; + setCursorPosition(_cursorPosition); + } + + // switch from ascii to hex edit + if (event->key() == Qt::Key_Backtab && _editAreaIsAscii){ + _editAreaIsAscii = false; + setCursorPosition(_cursorPosition); + } + + refresh(); +} + +void QHexEdit::mouseMoveEvent(QMouseEvent * event) +{ + _blink = false; + viewport()->update(); + qint64 actPos = cursorPosition(event->pos()); + if (actPos >= 0) + { + setCursorPosition(actPos); + setSelection(actPos); + } +} + +void QHexEdit::mousePressEvent(QMouseEvent * event) +{ + _blink = false; + viewport()->update(); + qint64 cPos = cursorPosition(event->pos()); + if (cPos >= 0) + { + resetSelection(cPos); + setCursorPosition(cPos); + } +} + +void QHexEdit::paintEvent(QPaintEvent *event) +{ + QPainter painter(viewport()); + int pxOfsX = horizontalScrollBar()->value(); + + if (event->rect() != _cursorRect) + { + int pxPosStartY = _pxCharHeight; + + // draw some patterns if needed + painter.fillRect(event->rect(), viewport()->palette().color(QPalette::Base)); + if (_addressArea) + painter.fillRect(QRect(-pxOfsX, event->rect().top(), _pxPosHexX - _pxGapAdrHex/2, height()), _addressAreaColor); + if (_asciiArea) + { + int linePos = _pxPosAsciiX - (_pxGapHexAscii / 2); + painter.setPen(Qt::gray); + painter.drawLine(linePos - pxOfsX, event->rect().top(), linePos - pxOfsX, height()); + } + + painter.setPen(viewport()->palette().color(QPalette::WindowText)); + + // paint address area + if (_addressArea) + { + QString address; + for (int row=0, pxPosY = _pxCharHeight; row <= (_dataShown.size()/_bytesPerLine); row++, pxPosY +=_pxCharHeight) + { + address = QString("%1").arg(_bPosFirst + row*_bytesPerLine + _addressOffset, _addrDigits, 16, QChar('0')); + painter.drawText(_pxPosAdrX - pxOfsX, pxPosY, address); + } + } + + // paint hex and ascii area + QPen colStandard = QPen(viewport()->palette().color(QPalette::WindowText)); + + painter.setBackgroundMode(Qt::TransparentMode); + + for (int row = 0, pxPosY = pxPosStartY; row <= _rowsShown; row++, pxPosY +=_pxCharHeight) + { + QByteArray hex; + int pxPosX = _pxPosHexX - pxOfsX; + int pxPosAsciiX2 = _pxPosAsciiX - pxOfsX; + qint64 bPosLine = row * _bytesPerLine; + for (int colIdx = 0; ((bPosLine + colIdx) < _dataShown.size() && (colIdx < _bytesPerLine)); colIdx++) + { + QColor c = viewport()->palette().color(QPalette::Base); + painter.setPen(colStandard); + + qint64 posBa = _bPosFirst + bPosLine + colIdx; + if ((getSelectionBegin() <= posBa) && (getSelectionEnd() > posBa)) + { + c = _brushSelection.color(); + painter.setPen(_penSelection); + } + else + { + if (_highlighting) + if (_markedShown.at((int)(posBa - _bPosFirst))) + { + c = _brushHighlighted.color(); + painter.setPen(_penHighlighted); + } + } + + // render hex value + QRect r; + if (colIdx == 0) + r.setRect(pxPosX, pxPosY - _pxCharHeight + _pxSelectionSub, 2*_pxCharWidth, _pxCharHeight); + else + r.setRect(pxPosX - _pxCharWidth, pxPosY - _pxCharHeight + _pxSelectionSub, 3*_pxCharWidth, _pxCharHeight); + painter.fillRect(r, c); + hex = _hexDataShown.mid((bPosLine + colIdx) * 2, 2); + painter.drawText(pxPosX, pxPosY, hexCaps()?hex.toUpper():hex); + pxPosX += 3*_pxCharWidth; + + // render ascii value + if (_asciiArea) + { + int ch = (uchar)_dataShown.at(bPosLine + colIdx); + if ( ch < 0x20 ) + ch = '.'; + r.setRect(pxPosAsciiX2, pxPosY - _pxCharHeight + _pxSelectionSub, _pxCharWidth, _pxCharHeight); + painter.fillRect(r, c); + painter.drawText(pxPosAsciiX2, pxPosY, QChar(ch)); + pxPosAsciiX2 += _pxCharWidth; + } + } + } + painter.setBackgroundMode(Qt::TransparentMode); + painter.setPen(viewport()->palette().color(QPalette::WindowText)); + } + + // paint cursor + if (_blink && !_readOnly && hasFocus()) + painter.fillRect(_cursorRect, this->palette().color(QPalette::WindowText)); + else + { + painter.fillRect(QRect(_pxCursorX - pxOfsX, _pxCursorY - _pxCharHeight, _pxCharWidth, _pxCharHeight), viewport()->palette().color(QPalette::Base)); + if (_editAreaIsAscii) { + QByteArray ba = _dataShown.mid((_cursorPosition - _bPosFirst) / 2, 1); + if (ba != "") + { + if (ba.at(0) <= ' ') + ba[0] = '.'; + painter.drawText(_pxCursorX - pxOfsX, _pxCursorY, ba); + } + } else { + painter.drawText(_pxCursorX - pxOfsX, _pxCursorY, _hexDataShown.mid(_cursorPosition - _bPosFirst, 1)); + } + } + + // emit event, if size has changed + if (_lastEventSize != _chunks->size()) + { + _lastEventSize = _chunks->size(); + emit currentSizeChanged(_lastEventSize); + } +} + +void QHexEdit::resizeEvent(QResizeEvent *) +{ + if (_dynamicBytesPerLine) + { + int pxFixGaps = 0; + if (_addressArea) + pxFixGaps = addressWidth() * _pxCharWidth + _pxGapAdr; + pxFixGaps += _pxGapAdrHex; + if (_asciiArea) + pxFixGaps += _pxGapHexAscii; + + // +1 because the last hex value do not have space. so it is effective one char more + int charWidth = (viewport()->width() - pxFixGaps ) / _pxCharWidth + 1; + + // 2 hex alfa-digits 1 space 1 ascii per byte = 4; if ascii is disabled then 3 + // to prevent devision by zero use the min value 1 + setBytesPerLine(std::max(charWidth / (_asciiArea ? 4 : 3),1)); + } + adjust(); +} + +bool QHexEdit::focusNextPrevChild(bool next) +{ + if (_addressArea) + { + if ( (next && _editAreaIsAscii) || (!next && !_editAreaIsAscii )) + return QWidget::focusNextPrevChild(next); + else + return false; + } + else + { + return QWidget::focusNextPrevChild(next); + } +} + +// ********************************************************************** Handle selections +void QHexEdit::resetSelection() +{ + _bSelectionBegin = _bSelectionInit; + _bSelectionEnd = _bSelectionInit; +} + +void QHexEdit::resetSelection(qint64 pos) +{ + pos = pos / 2 ; + if (pos < 0) + pos = 0; + if (pos > _chunks->size()) + pos = _chunks->size(); + + _bSelectionInit = pos; + _bSelectionBegin = pos; + _bSelectionEnd = pos; +} + +void QHexEdit::setSelection(qint64 pos) +{ + pos = pos / 2; + if (pos < 0) + pos = 0; + if (pos > _chunks->size()) + pos = _chunks->size(); + + if (pos >= _bSelectionInit) + { + _bSelectionEnd = pos; + _bSelectionBegin = _bSelectionInit; + } + else + { + _bSelectionBegin = pos; + _bSelectionEnd = _bSelectionInit; + } +} + +int QHexEdit::getSelectionBegin() +{ + return _bSelectionBegin; +} + +int QHexEdit::getSelectionEnd() +{ + return _bSelectionEnd; +} + +// ********************************************************************** Private utility functions +void QHexEdit::init() +{ + _undoStack->clear(); + setAddressOffset(0); + resetSelection(0); + setCursorPosition(0); + verticalScrollBar()->setValue(0); + _modified = false; +} + +void QHexEdit::adjust() +{ + // recalc Graphics + if (_addressArea) + { + _addrDigits = addressWidth(); + _pxPosHexX = _pxGapAdr + _addrDigits*_pxCharWidth + _pxGapAdrHex; + } + else + _pxPosHexX = _pxGapAdrHex; + _pxPosAdrX = _pxGapAdr; + _pxPosAsciiX = _pxPosHexX + _hexCharsInLine * _pxCharWidth + _pxGapHexAscii; + + // set horizontalScrollBar() + int pxWidth = _pxPosAsciiX; + if (_asciiArea) + pxWidth += _bytesPerLine*_pxCharWidth; + horizontalScrollBar()->setRange(0, pxWidth - viewport()->width()); + horizontalScrollBar()->setPageStep(viewport()->width()); + + // set verticalScrollbar() + _rowsShown = ((viewport()->height()-4)/_pxCharHeight); + int lineCount = (int)(_chunks->size() / (qint64)_bytesPerLine) + 1; + verticalScrollBar()->setRange(0, lineCount - _rowsShown); + verticalScrollBar()->setPageStep(_rowsShown); + + int value = verticalScrollBar()->value(); + _bPosFirst = (qint64)value * _bytesPerLine; + _bPosLast = _bPosFirst + (qint64)(_rowsShown * _bytesPerLine) - 1; + if (_bPosLast >= _chunks->size()) + _bPosLast = _chunks->size() - 1; + readBuffers(); + setCursorPosition(_cursorPosition); +} + +void QHexEdit::dataChangedPrivate(int) +{ + _modified = _undoStack->index() != 0; + adjust(); + emit dataChanged(); +} + +void QHexEdit::refresh() +{ + ensureVisible(); + readBuffers(); +} + +void QHexEdit::readBuffers() +{ + _dataShown = _chunks->data(_bPosFirst, _bPosLast - _bPosFirst + _bytesPerLine + 1, &_markedShown); + _hexDataShown = QByteArray(_dataShown.toHex()); +} + +QString QHexEdit::toReadable(const QByteArray &ba) +{ + QString result; + + for (int i=0; i < ba.size(); i += 16) + { + QString addrStr = QString("%1").arg(_addressOffset + i, addressWidth(), 16, QChar('0')); + QString hexStr; + QString ascStr; + for (int j=0; j<16; j++) + { + if ((i + j) < ba.size()) + { + hexStr.append(" ").append(ba.mid(i+j, 1).toHex()); + char ch = ba[i + j]; + if ((ch < 0x20) || (ch > 0x7e)) + ch = '.'; + ascStr.append(QChar(ch)); + } + } + result += addrStr + " " + QString("%1").arg(hexStr, -48) + " " + QString("%1").arg(ascStr, -17) + "\n"; + } + return result; +} + +void QHexEdit::updateCursor() +{ + if (_blink) + _blink = false; + else + _blink = true; + viewport()->update(_cursorRect); } diff --git a/extra/qhexedit2/src/qhexedit.h b/extra/qhexedit2/src/qhexedit.h index b5a9601..0bac26f 100644 --- a/extra/qhexedit2/src/qhexedit.h +++ b/extra/qhexedit2/src/qhexedit.h @@ -1,21 +1,33 @@ #ifndef QHEXEDIT_H #define QHEXEDIT_H -#include -#include "qhexedit_p.h" +#include +#include +#include + +#include "chunks.h" +#include "commands.h" + +#ifdef QHEXEDIT_EXPORTS +#define QHEXEDIT_API Q_DECL_EXPORT +#elif QHEXEDIT_IMPORTS +#define QHEXEDIT_API Q_DECL_IMPORT +#else +#define QHEXEDIT_API +#endif /** \mainpage QHexEdit is a binary editor widget for Qt. -\version Version 0.6.1 -\image html hexedit.png +\version Version 0.8.3 +\image html qhexedit.png */ -/*! QHexEdit is a hex editor widget written in C++ for the Qt (Qt4) framework. +/** QHexEdit is a hex editor widget written in C++ for the Qt (Qt4, Qt5) framework. It is a simple editor for binary data, just like QPlainTextEdit is for text data. There are sip configuration files included, so it is easy to create -bindings for PyQt and you can use this widget also in python. +bindings for PyQt and you can use this widget also in python 2 and 3. QHexEdit takes the data of a QByteArray (setData()) and shows it. You can use the mouse or the keyboard to navigate inside the widget. If you hit the keys @@ -36,44 +48,78 @@ characters will be ignored. QHexEdit comes with undo/redo functionality. All changes can be undone, by pressing the undo-key (usually ctr-z). They can also be redone afterwards. The undo/redo framework is cleared, when setData() sets up a new -content for the editor. +content for the editor. You can search data inside the content with indexOf() +and lastIndexOf(). The replace() function is to change located subdata. This +'replaced' data can also be undone by the undo/redo framework. -This widget can only handle small amounts of data. The size has to be below 10 -megabytes, otherwise the scroll sliders ard not shown and you can't scroll any -more. +QHexEdit is based on QIODevice, that's why QHexEdit can handle big amounts of +data. The size of edited data can be more then two gigabytes without any +restrictions. */ - class QHexEdit : public QScrollArea +class QHEXEDIT_API QHexEdit : public QAbstractScrollArea { Q_OBJECT - /*! Property data holds the content of QHexEdit. Call setData() to set the - content of QHexEdit, data() returns the actual content. - */ - Q_PROPERTY(QByteArray data READ data WRITE setData) - /*! Property addressOffset is added to the Numbers of the Address Area. - A offset in the address area (left side) is sometimes usefull, whe you show - only a segment of a complete memory picture. With setAddressOffset() you set - this property - with addressOffset() you get the actual value. + /*! Property address area switch the address area on or off. Set addressArea true + (show it), false (hide it). */ - Q_PROPERTY(int addressOffset READ addressOffset WRITE setAddressOffset) + Q_PROPERTY(bool addressArea READ addressArea WRITE setAddressArea) /*! Property address area color sets (setAddressAreaColor()) the backgorund color of address areas. You can also read the color (addressaAreaColor()). */ Q_PROPERTY(QColor addressAreaColor READ addressAreaColor WRITE setAddressAreaColor) + /*! Property addressOffset is added to the Numbers of the Address Area. + A offset in the address area (left side) is sometimes usefull, whe you show + only a segment of a complete memory picture. With setAddressOffset() you set + this property - with addressOffset() you get the current value. + */ + Q_PROPERTY(qint64 addressOffset READ addressOffset WRITE setAddressOffset) + + /*! Set and get the minimum width of the address area, width in characters. + */ + Q_PROPERTY(int addressWidth READ addressWidth WRITE setAddressWidth) + + /*! Switch the ascii area on (true, show it) or off (false, hide it). + */ + Q_PROPERTY(bool asciiArea READ asciiArea WRITE setAsciiArea) + + /*! Set and get bytes number per line.*/ + Q_PROPERTY(int bytesPerLine READ bytesPerLine WRITE setBytesPerLine) + + /*! Porperty cursorPosition sets or gets the position of the editor cursor + in QHexEdit. Every byte in data has to cursor positions: the lower and upper + Nibble. Maximum cursor position is factor two of data.size(). + */ + Q_PROPERTY(qint64 cursorPosition READ cursorPosition WRITE setCursorPosition) + + /*! Property data holds the content of QHexEdit. Call setData() to set the + content of QHexEdit, data() returns the actual content. When calling setData() + with a QByteArray as argument, QHexEdit creates a internal copy of the data + If you want to edit big files please use setData(), based on QIODevice. + */ + Q_PROPERTY(QByteArray data READ data WRITE setData NOTIFY dataChanged) + + /*! That property defines if the hex values looks as a-f if the value is false(default) + or A-F if value is true. + */ + Q_PROPERTY(bool hexCaps READ hexCaps WRITE setHexCaps) + + /*! Property defines the dynamic calculation of bytesPerLine parameter depends of width of widget. + set this property true to avoid horizontal scrollbars and show the maximal possible data. defalut value is false*/ + Q_PROPERTY(bool dynamicBytesPerLine READ dynamicBytesPerLine WRITE setDynamicBytesPerLine) + + /*! Switch the highlighting feature on or of: true (show it), false (hide it). + */ + Q_PROPERTY(bool highlighting READ highlighting WRITE setHighlighting) + /*! Property highlighting color sets (setHighlightingColor()) the backgorund color of highlighted text areas. You can also read the color (highlightingColor()). */ Q_PROPERTY(QColor highlightingColor READ highlightingColor WRITE setHighlightingColor) - /*! Property selection color sets (setSelectionColor()) the backgorund - color of selected text areas. You can also read the color - (selectionColor()). - */ - Q_PROPERTY(QColor selectionColor READ selectionColor WRITE setSelectionColor) - /*! Porperty overwrite mode sets (setOverwriteMode()) or gets (overwriteMode()) the mode in which the editor works. In overwrite mode the user will overwrite existing data. The size of data will be constant. In insert mode the size will grow, when inserting @@ -81,6 +127,12 @@ more. */ Q_PROPERTY(bool overwriteMode READ overwriteMode WRITE setOverwriteMode) + /*! Property selection color sets (setSelectionColor()) the backgorund + color of selected text areas. You can also read the color + (selectionColor()). + */ + Q_PROPERTY(QColor selectionColor READ selectionColor WRITE setSelectionColor) + /*! Porperty readOnly sets (setReadOnly()) or gets (isReadOnly) the mode in which the editor works. In readonly mode the the user can only navigate through the data and select data; modifying is not possible. This @@ -91,62 +143,115 @@ more. /*! Set the font of the widget. Please use fixed width fonts like Mono or Courier.*/ Q_PROPERTY(QFont font READ font WRITE setFont) - public: /*! Creates an instance of QHexEdit. \param parent Parent widget of QHexEdit. */ - QHexEdit(QWidget *parent = 0); + QHexEdit(QWidget *parent=0); - /*! Inserts a byte array. - \param i Index position, where to insert - \param ba byte array, which is to insert - In overwrite mode, the existing data will be overwritten, in insertmode ba will be - insertet and size of data grows. + // Access to data of qhexedit + + /*! Sets the data of QHexEdit. The QIODevice will be opend just before reading + and closed immediately afterwards. This is to allow other programs to rewrite + the file while editing it. */ - void insert(int i, const QByteArray & ba); + bool setData(QIODevice &iODevice); + + /*! Givs back the data as a QByteArray starting at position \param pos and + delivering \param count bytes. + */ + QByteArray dataAt(qint64 pos, qint64 count=-1); + + /*! Givs back the data into a \param iODevice starting at position \param pos + and delivering \param count bytes. + */ + bool write(QIODevice &iODevice, qint64 pos=0, qint64 count=-1); + + + // Char handling /*! Inserts a char. - \param i Index position, where to insert + \param pos Index position, where to insert \param ch Char, which is to insert - In overwrite mode, the existing data will be overwritten, in insertmode ba will be - insertet and size of data grows. + The char will be inserted and size of data grows. */ - void insert(int i, char ch); + void insert(qint64 pos, char ch); /*! Removes len bytes from the content. \param pos Index position, where to remove \param len Amount of bytes to remove - In overwrite mode, the existing bytes will be overwriten with 0x00. */ - void remove(int pos, int len=1); + void remove(qint64 pos, qint64 len=1); - /*! Gives back a formatted image of the content of QHexEdit + /*! Replaces a char. + \param pos Index position, where to overwrite + \param ch Char, which is to insert + The char will be overwritten and size remains constant. */ - QString toReadableString(); + void replace(qint64 pos, char ch); + + + // ByteArray handling + + /*! Inserts a byte array. + \param pos Index position, where to insert + \param ba QByteArray, which is to insert + The QByteArray will be inserted and size of data grows. + */ + void insert(qint64 pos, const QByteArray &ba); + + /*! Replaces \param len bytes with a byte array \param ba + \param pos Index position, where to overwrite + \param ba QByteArray, which is inserted + \param len count of bytes to overwrite + The data is overwritten and size of data may change. + */ + void replace(qint64 pos, qint64 len, const QByteArray &ba); + + + // Utility functioins + /*! Calc cursor position from graphics position + * \param point from where the cursor position should be calculated + * \return Cursor postioin + */ + qint64 cursorPosition(QPoint point); + + /*! Ensure the cursor to be visble + */ + void ensureVisible(); + + /*! Find first occurence of ba in QHexEdit data + * \param ba Data to find + * \param from Point where the search starts + * \return pos if fond, else -1 + */ + qint64 indexOf(const QByteArray &ba, qint64 from); + + /*! Returns if any changes where done on document + * \return true when document is modified else false + */ + bool isModified(); + + /*! Find last occurence of ba in QHexEdit data + * \param ba Data to find + * \param from Point where the search starts + * \return pos if fond, else -1 + */ + qint64 lastIndexOf(const QByteArray &ba, qint64 from); /*! Gives back a formatted image of the selected content of QHexEdit */ QString selectionToReadableString(); - /*! \cond docNever */ - void setAddressOffset(int offset); - int addressOffset(); - void setData(QByteArray const &data); - QByteArray data(); - void setAddressAreaColor(QColor const &color); - QColor addressAreaColor(); - void setHighlightingColor(QColor const &color); - QColor highlightingColor(); - void setSelectionColor(QColor const &color); - QColor selectionColor(); - void setOverwriteMode(bool); - bool overwriteMode(); - void setReadOnly(bool); - bool isReadOnly(); - const QFont &font() const; - void setFont(const QFont &); - /*! \endcond docNever */ + /*! Set Font of QHexEdit + * \param font + */ + void setFont(const QFont &font); + + /*! Gives back a formatted image of the content of QHexEdit + */ + QString toReadableString(); + public slots: /*! Redoes the last operation. If there is no operation to redo, i.e. @@ -154,26 +259,6 @@ public slots: */ void redo(); - /*! Set the minimum width of the address area. - \param addressWidth Width in characters. - */ - void setAddressWidth(int addressWidth); - - /*! Switch the address area on or off. - \param addressArea true (show it), false (hide it). - */ - void setAddressArea(bool addressArea); - - /*! Switch the ascii area on or off. - \param asciiArea true (show it), false (hide it). - */ - void setAsciiArea(bool asciiArea); - - /*! Switch the highlighting feature on or of. - \param mode true (show it), false (hide it). - */ - void setHighlighting(bool mode); - /*! Undoes the last operation. If there is no operation to undo, i.e. there is no undo step in the undo/redo history, nothing happens. */ @@ -182,24 +267,153 @@ public slots: signals: /*! Contains the address, where the cursor is located. */ - void currentAddressChanged(int address); + void currentAddressChanged(qint64 address); /*! Contains the size of the data to edit. */ - void currentSizeChanged(int size); + void currentSizeChanged(qint64 size); - /*! The signal is emited every time, the data is changed. */ + /*! The signal is emitted every time, the data is changed. */ void dataChanged(); - /*! The signal is emited every time, the overwrite mode is changed. */ + /*! The signal is emitted every time, the overwrite mode is changed. */ void overwriteModeChanged(bool state); + +/*! \cond docNever */ +public: + ~QHexEdit(); + + // Properties + bool addressArea(); + void setAddressArea(bool addressArea); + + QColor addressAreaColor(); + void setAddressAreaColor(const QColor &color); + + qint64 addressOffset(); + void setAddressOffset(qint64 addressArea); + + int addressWidth(); + void setAddressWidth(int addressWidth); + + bool asciiArea(); + void setAsciiArea(bool asciiArea); + + int bytesPerLine(); + void setBytesPerLine(int count); + + qint64 cursorPosition(); + void setCursorPosition(qint64 position); + + QByteArray data(); + void setData(const QByteArray &ba); + + void setHexCaps(const bool isCaps); + bool hexCaps(); + + void setDynamicBytesPerLine(const bool isDynamic); + bool dynamicBytesPerLine(); + + bool highlighting(); + void setHighlighting(bool mode); + + QColor highlightingColor(); + void setHighlightingColor(const QColor &color); + + bool overwriteMode(); + void setOverwriteMode(bool overwriteMode); + + bool isReadOnly(); + void setReadOnly(bool readOnly); + + QColor selectionColor(); + void setSelectionColor(const QColor &color); + +protected: + // Handle events + void keyPressEvent(QKeyEvent *event); + void mouseMoveEvent(QMouseEvent * event); + void mousePressEvent(QMouseEvent * event); + void paintEvent(QPaintEvent *event); + void resizeEvent(QResizeEvent *); + virtual bool focusNextPrevChild(bool next); private: - /*! \cond docNever */ - QHexEditPrivate *qHexEdit_p; - QHBoxLayout *layout; - QScrollArea *scrollArea; + // Handle selections + void resetSelection(qint64 pos); // set selectionStart and selectionEnd to pos + void resetSelection(); // set selectionEnd to selectionStart + void setSelection(qint64 pos); // set min (if below init) or max (if greater init) + int getSelectionBegin(); + int getSelectionEnd(); + + // Private utility functions + void init(); + void readBuffers(); + QString toReadable(const QByteArray &ba); + +private slots: + void adjust(); // recalc pixel positions + void dataChangedPrivate(int idx=0); // emit dataChanged() signal + void refresh(); // ensureVisible() and readBuffers() + void updateCursor(); // update blinking cursor + +private: + // Name convention: pixel positions start with _px + int _pxCharWidth, _pxCharHeight; // char dimensions (dpendend on font) + int _pxPosHexX; // X-Pos of HeaxArea + int _pxPosAdrX; // X-Pos of Address Area + int _pxPosAsciiX; // X-Pos of Ascii Area + int _pxGapAdr; // gap left from AddressArea + int _pxGapAdrHex; // gap between AddressArea and HexAerea + int _pxGapHexAscii; // gap between HexArea and AsciiArea + int _pxCursorWidth; // cursor width + int _pxSelectionSub; // offset selection rect + int _pxCursorX; // current cursor pos + int _pxCursorY; // current cursor pos + + // Name convention: absolute byte positions in chunks start with _b + qint64 _bSelectionBegin; // first position of Selection + qint64 _bSelectionEnd; // end of Selection + qint64 _bSelectionInit; // memory position of Selection + qint64 _bPosFirst; // position of first byte shown + qint64 _bPosLast; // position of last byte shown + qint64 _bPosCurrent; // current position + + // variables to store the property values + bool _addressArea; // left area of QHexEdit + QColor _addressAreaColor; + int _addressWidth; + bool _asciiArea; + qint64 _addressOffset; + int _bytesPerLine; + int _hexCharsInLine; + bool _highlighting; + bool _overwriteMode; + QBrush _brushSelection; + QPen _penSelection; + QBrush _brushHighlighted; + QPen _penHighlighted; + bool _readOnly; + bool _hexCaps; + bool _dynamicBytesPerLine; + + // other variables + bool _editAreaIsAscii; // flag about the ascii mode edited + int _addrDigits; // real no of addressdigits, may be > addressWidth + bool _blink; // help get cursor blinking + QBuffer _bData; // buffer, when setup with QByteArray + Chunks *_chunks; // IODevice based access to data + QTimer _cursorTimer; // for blinking cursor + qint64 _cursorPosition; // absolute positioin of cursor, 1 Byte == 2 tics + QRect _cursorRect; // physical dimensions of cursor + QByteArray _data; // QHexEdit's data, when setup with QByteArray + QByteArray _dataShown; // data in the current View + QByteArray _hexDataShown; // data in view, transformed to hex + qint64 _lastEventSize; // size, which was emitted last time + QByteArray _markedShown; // marked data in view + bool _modified; // Is any data in editor modified? + int _rowsShown; // lines of text shown + UndoStack * _undoStack; // Stack to store edit actions for undo/redo /*! \endcond docNever */ }; -#endif - +#endif // QHEXEDIT_H diff --git a/extra/qhexedit2/src/qhexedit_p.cpp b/extra/qhexedit2/src/qhexedit_p.cpp deleted file mode 100644 index 2f046bb..0000000 --- a/extra/qhexedit2/src/qhexedit_p.cpp +++ /dev/null @@ -1,800 +0,0 @@ -#include - -#include "qhexedit_p.h" -#include "commands.h" - -const int HEXCHARS_IN_LINE = 47; -const int GAP_ADR_HEX = 10; -const int GAP_HEX_ASCII = 16; -const int BYTES_PER_LINE = 16; - -QHexEditPrivate::QHexEditPrivate(QScrollArea *parent) : QWidget(parent) -{ - _undoStack = new QUndoStack(this); - - _scrollArea = parent; - setAddressWidth(4); - setAddressOffset(0); - setAddressArea(true); - setAsciiArea(true); - setHighlighting(true); - setOverwriteMode(true); - setReadOnly(false); - setAddressAreaColor(QColor(0xd4, 0xd4, 0xd4, 0xff)); - setHighlightingColor(QColor(0xff, 0xff, 0x99, 0xff)); - setSelectionColor(QColor(0x6d, 0x9e, 0xff, 0xff)); - setFont(QFont("Courier", 10)); - - _size = 0; - resetSelection(0); - - setFocusPolicy(Qt::StrongFocus); - - connect(&_cursorTimer, SIGNAL(timeout()), this, SLOT(updateCursor())); - _cursorTimer.setInterval(500); - _cursorTimer.start(); -} - -void QHexEditPrivate::setAddressOffset(int offset) -{ - _xData.setAddressOffset(offset); - adjust(); -} - -int QHexEditPrivate::addressOffset() -{ - return _xData.addressOffset(); -} - -void QHexEditPrivate::setData(const QByteArray &data) -{ - _xData.setData(data); - _undoStack->clear(); - adjust(); - setCursorPos(0); -} - -QByteArray QHexEditPrivate::data() -{ - return _xData.data(); -} - -void QHexEditPrivate::setAddressAreaColor(const QColor &color) -{ - _addressAreaColor = color; - update(); -} - -QColor QHexEditPrivate::addressAreaColor() -{ - return _addressAreaColor; -} - -void QHexEditPrivate::setHighlightingColor(const QColor &color) -{ - _highlightingColor = color; - update(); -} - -QColor QHexEditPrivate::highlightingColor() -{ - return _highlightingColor; -} - -void QHexEditPrivate::setSelectionColor(const QColor &color) -{ - _selectionColor = color; - update(); -} - -QColor QHexEditPrivate::selectionColor() -{ - return _selectionColor; -} - -void QHexEditPrivate::setReadOnly(bool readOnly) -{ - _readOnly = readOnly; -} - -bool QHexEditPrivate::isReadOnly() -{ - return _readOnly; -} - -XByteArray & QHexEditPrivate::xData() -{ - return _xData; -} - -void QHexEditPrivate::insert(int index, const QByteArray & ba) -{ - if (ba.length() > 0) - { - if (_overwriteMode) - { - QUndoCommand *arrayCommand= new ArrayCommand(&_xData, ArrayCommand::replace, index, ba, ba.length()); - _undoStack->push(arrayCommand); - emit dataChanged(); - } - else - { - QUndoCommand *arrayCommand= new ArrayCommand(&_xData, ArrayCommand::insert, index, ba, ba.length()); - _undoStack->push(arrayCommand); - emit dataChanged(); - } - } -} - -void QHexEditPrivate::insert(int index, char ch) -{ - QUndoCommand *charCommand = new CharCommand(&_xData, CharCommand::insert, index, ch); - _undoStack->push(charCommand); - emit dataChanged(); -} - -void QHexEditPrivate::remove(int index, int len) -{ - if (len > 0) - { - if (len == 1) - { - if (_overwriteMode) - { - QUndoCommand *charCommand = new CharCommand(&_xData, CharCommand::replace, index, char(0)); - _undoStack->push(charCommand); - emit dataChanged(); - } - else - { - QUndoCommand *charCommand = new CharCommand(&_xData, CharCommand::remove, index, char(0)); - _undoStack->push(charCommand); - emit dataChanged(); - } - } - else - { - QByteArray ba = QByteArray(len, char(0)); - if (_overwriteMode) - { - QUndoCommand *arrayCommand = new ArrayCommand(&_xData, ArrayCommand::replace, index, ba, ba.length()); - _undoStack->push(arrayCommand); - emit dataChanged(); - } - else - { - QUndoCommand *arrayCommand= new ArrayCommand(&_xData, ArrayCommand::remove, index, ba, len); - _undoStack->push(arrayCommand); - emit dataChanged(); - } - } - } -} - -void QHexEditPrivate::replace(int index, char ch) -{ - QUndoCommand *charCommand = new CharCommand(&_xData, CharCommand::replace, index, ch); - _undoStack->push(charCommand); - emit dataChanged(); -} - -void QHexEditPrivate::replace(int index, const QByteArray & ba) -{ - QUndoCommand *arrayCommand= new ArrayCommand(&_xData, ArrayCommand::replace, index, ba, ba.length()); - _undoStack->push(arrayCommand); - emit dataChanged(); -} - -void QHexEditPrivate::setAddressArea(bool addressArea) -{ - _addressArea = addressArea; - adjust(); - - setCursorPos(_cursorPosition); -} - -void QHexEditPrivate::setAddressWidth(int addressWidth) -{ - _xData.setAddressWidth(addressWidth); - - setCursorPos(_cursorPosition); -} - -void QHexEditPrivate::setAsciiArea(bool asciiArea) -{ - _asciiArea = asciiArea; - adjust(); -} - -void QHexEditPrivate::setFont(const QFont &font) -{ - QWidget::setFont(font); - adjust(); -} - -void QHexEditPrivate::setHighlighting(bool mode) -{ - _highlighting = mode; - update(); -} - -void QHexEditPrivate::setOverwriteMode(bool overwriteMode) -{ - _overwriteMode = overwriteMode; -} - -bool QHexEditPrivate::overwriteMode() -{ - return _overwriteMode; -} - -void QHexEditPrivate::redo() -{ - _undoStack->redo(); - emit dataChanged(); - setCursorPos(_cursorPosition); - update(); -} - -void QHexEditPrivate::undo() -{ - _undoStack->undo(); - emit dataChanged(); - setCursorPos(_cursorPosition); - update(); -} - -QString QHexEditPrivate::toRedableString() -{ - return _xData.toRedableString(); -} - - -QString QHexEditPrivate::selectionToReadableString() -{ - return _xData.toRedableString(getSelectionBegin(), getSelectionEnd()); -} - -void QHexEditPrivate::keyPressEvent(QKeyEvent *event) -{ - int charX = (_cursorX - _xPosHex) / _charWidth; - int posX = (charX / 3) * 2 + (charX % 3); - int posBa = (_cursorY / _charHeight) * BYTES_PER_LINE + posX / 2; - - -/*****************************************************************************/ -/* Cursor movements */ -/*****************************************************************************/ - - if (event->matches(QKeySequence::MoveToNextChar)) - { - setCursorPos(_cursorPosition + 1); - resetSelection(_cursorPosition); - } - if (event->matches(QKeySequence::MoveToPreviousChar)) - { - setCursorPos(_cursorPosition - 1); - resetSelection(_cursorPosition); - } - if (event->matches(QKeySequence::MoveToEndOfLine)) - { - setCursorPos(_cursorPosition | (2 * BYTES_PER_LINE -1)); - resetSelection(_cursorPosition); - } - if (event->matches(QKeySequence::MoveToStartOfLine)) - { - setCursorPos(_cursorPosition - (_cursorPosition % (2 * BYTES_PER_LINE))); - resetSelection(_cursorPosition); - } - if (event->matches(QKeySequence::MoveToPreviousLine)) - { - setCursorPos(_cursorPosition - (2 * BYTES_PER_LINE)); - resetSelection(_cursorPosition); - } - if (event->matches(QKeySequence::MoveToNextLine)) - { - setCursorPos(_cursorPosition + (2 * BYTES_PER_LINE)); - resetSelection(_cursorPosition); - } - - if (event->matches(QKeySequence::MoveToNextPage)) - { - setCursorPos(_cursorPosition + (((_scrollArea->viewport()->height() / _charHeight) - 1) * 2 * BYTES_PER_LINE)); - resetSelection(_cursorPosition); - } - if (event->matches(QKeySequence::MoveToPreviousPage)) - { - setCursorPos(_cursorPosition - (((_scrollArea->viewport()->height() / _charHeight) - 1) * 2 * BYTES_PER_LINE)); - resetSelection(_cursorPosition); - } - if (event->matches(QKeySequence::MoveToEndOfDocument)) - { - setCursorPos(_xData.size() * 2); - resetSelection(_cursorPosition); - } - if (event->matches(QKeySequence::MoveToStartOfDocument)) - { - setCursorPos(0); - resetSelection(_cursorPosition); - } - -/*****************************************************************************/ -/* Select commands */ -/*****************************************************************************/ - if (event->matches(QKeySequence::SelectAll)) - { - resetSelection(0); - setSelection(2*_xData.size() + 1); - } - if (event->matches(QKeySequence::SelectNextChar)) - { - int pos = _cursorPosition + 1; - setCursorPos(pos); - setSelection(pos); - } - if (event->matches(QKeySequence::SelectPreviousChar)) - { - int pos = _cursorPosition - 1; - setSelection(pos); - setCursorPos(pos); - } - if (event->matches(QKeySequence::SelectEndOfLine)) - { - int pos = _cursorPosition - (_cursorPosition % (2 * BYTES_PER_LINE)) + (2 * BYTES_PER_LINE); - setCursorPos(pos); - setSelection(pos); - } - if (event->matches(QKeySequence::SelectStartOfLine)) - { - int pos = _cursorPosition - (_cursorPosition % (2 * BYTES_PER_LINE)); - setCursorPos(pos); - setSelection(pos); - } - if (event->matches(QKeySequence::SelectPreviousLine)) - { - int pos = _cursorPosition - (2 * BYTES_PER_LINE); - setCursorPos(pos); - setSelection(pos); - } - if (event->matches(QKeySequence::SelectNextLine)) - { - int pos = _cursorPosition + (2 * BYTES_PER_LINE); - setCursorPos(pos); - setSelection(pos); - } - - if (event->matches(QKeySequence::SelectNextPage)) - { - int pos = _cursorPosition + (((_scrollArea->viewport()->height() / _charHeight) - 1) * 2 * BYTES_PER_LINE); - setCursorPos(pos); - setSelection(pos); - } - if (event->matches(QKeySequence::SelectPreviousPage)) - { - int pos = _cursorPosition - (((_scrollArea->viewport()->height() / _charHeight) - 1) * 2 * BYTES_PER_LINE); - setCursorPos(pos); - setSelection(pos); - } - if (event->matches(QKeySequence::SelectEndOfDocument)) - { - int pos = _xData.size() * 2; - setCursorPos(pos); - setSelection(pos); - } - if (event->matches(QKeySequence::SelectStartOfDocument)) - { - int pos = 0; - setCursorPos(pos); - setSelection(pos); - } - -/*****************************************************************************/ -/* Edit Commands */ -/*****************************************************************************/ -if (!_readOnly) -{ - /* Hex input */ - int key = int(event->text()[0].toAscii()); - if ((key>='0' && key<='9') || (key>='a' && key <= 'f')) - { - if (getSelectionBegin() != getSelectionEnd()) - { - posBa = getSelectionBegin(); - remove(posBa, getSelectionEnd() - posBa); - setCursorPos(2*posBa); - resetSelection(2*posBa); - } - - // If insert mode, then insert a byte - if (_overwriteMode == false) - if ((charX % 3) == 0) - { - insert(posBa, char(0)); - } - - // Change content - if (_xData.size() > 0) - { - QByteArray hexValue = _xData.data().mid(posBa, 1).toHex(); - if ((charX % 3) == 0) - hexValue[0] = key; - else - hexValue[1] = key; - - replace(posBa, QByteArray().fromHex(hexValue)[0]); - - setCursorPos(_cursorPosition + 1); - resetSelection(_cursorPosition); - } - } - - /* Cut & Paste */ - if (event->matches(QKeySequence::Cut)) - { - QString result = QString(); - for (int idx = getSelectionBegin(); idx < getSelectionEnd(); idx++) - { - result += _xData.data().mid(idx, 1).toHex() + " "; - if ((idx % 16) == 15) - result.append("\n"); - } - remove(getSelectionBegin(), getSelectionEnd() - getSelectionBegin()); - QClipboard *clipboard = QApplication::clipboard(); - clipboard->setText(result); - setCursorPos(getSelectionBegin()); - resetSelection(getSelectionBegin()); - } - - if (event->matches(QKeySequence::Paste)) - { - QClipboard *clipboard = QApplication::clipboard(); - QByteArray ba = QByteArray().fromHex(clipboard->text().toLatin1()); - insert(_cursorPosition / 2, ba); - setCursorPos(_cursorPosition + 2 * ba.length()); - resetSelection(getSelectionBegin()); - } - - - /* Delete char */ - if (event->matches(QKeySequence::Delete)) - { - if (getSelectionBegin() != getSelectionEnd()) - { - posBa = getSelectionBegin(); - remove(posBa, getSelectionEnd() - posBa); - setCursorPos(2*posBa); - resetSelection(2*posBa); - } - else - { - if (_overwriteMode) - replace(posBa, char(0)); - else - remove(posBa, 1); - } - } - - /* Backspace */ - if ((event->key() == Qt::Key_Backspace) && (event->modifiers() == Qt::NoModifier)) - { - if (getSelectionBegin() != getSelectionEnd()) - { - posBa = getSelectionBegin(); - remove(posBa, getSelectionEnd() - posBa); - setCursorPos(2*posBa); - resetSelection(2*posBa); - } - else - { - if (posBa > 0) - { - if (_overwriteMode) - replace(posBa - 1, char(0)); - else - remove(posBa - 1, 1); - setCursorPos(_cursorPosition - 2); - } - } - } - - /* undo */ - if (event->matches(QKeySequence::Undo)) - { - undo(); - } - - /* redo */ - if (event->matches(QKeySequence::Redo)) - { - redo(); - } - - } - - if (event->matches(QKeySequence::Copy)) - { - QString result = QString(); - for (int idx = getSelectionBegin(); idx < getSelectionEnd(); idx++) - { - result += _xData.data().mid(idx, 1).toHex() + " "; - if ((idx % 16) == 15) - result.append('\n'); - } - QClipboard *clipboard = QApplication::clipboard(); - clipboard->setText(result); - } - - // Switch between insert/overwrite mode - if ((event->key() == Qt::Key_Insert) && (event->modifiers() == Qt::NoModifier)) - { - _overwriteMode = !_overwriteMode; - setCursorPos(_cursorPosition); - overwriteModeChanged(_overwriteMode); - } - - _scrollArea->ensureVisible(_cursorX, _cursorY + _charHeight/2, 3, _charHeight/2 + 2); - update(); -} - -void QHexEditPrivate::mouseMoveEvent(QMouseEvent * event) -{ - _blink = false; - update(); - int actPos = cursorPos(event->pos()); - setCursorPos(actPos); - setSelection(actPos); -} - -void QHexEditPrivate::mousePressEvent(QMouseEvent * event) -{ - _blink = false; - update(); - int cPos = cursorPos(event->pos()); - resetSelection(cPos); - setCursorPos(cPos); -} - -void QHexEditPrivate::paintEvent(QPaintEvent *event) -{ - QPainter painter(this); - - // draw some patterns if needed - painter.fillRect(event->rect(), this->palette().color(QPalette::Base)); - if (_addressArea) - painter.fillRect(QRect(_xPosAdr, event->rect().top(), _xPosHex - GAP_ADR_HEX + 2, height()), _addressAreaColor); - if (_asciiArea) - { - int linePos = _xPosAscii - (GAP_HEX_ASCII / 2); - painter.setPen(Qt::gray); - painter.drawLine(linePos, event->rect().top(), linePos, height()); - } - - painter.setPen(this->palette().color(QPalette::WindowText)); - - // calc position - int firstLineIdx = ((event->rect().top()/ _charHeight) - _charHeight) * BYTES_PER_LINE; - if (firstLineIdx < 0) - firstLineIdx = 0; - int lastLineIdx = ((event->rect().bottom() / _charHeight) + _charHeight) * BYTES_PER_LINE; - if (lastLineIdx > _xData.size()) - lastLineIdx = _xData.size(); - int yPosStart = ((firstLineIdx) / BYTES_PER_LINE) * _charHeight + _charHeight; - - // paint address area - if (_addressArea) - { - for (int lineIdx = firstLineIdx, yPos = yPosStart; lineIdx < lastLineIdx; lineIdx += BYTES_PER_LINE, yPos +=_charHeight) - { - QString address = QString("%1") - .arg(lineIdx + _xData.addressOffset(), _xData.realAddressNumbers(), 16, QChar('0')); - painter.drawText(_xPosAdr, yPos, address); - } - } - - // paint hex area - QByteArray hexBa(_xData.data().mid(firstLineIdx, lastLineIdx - firstLineIdx + 1).toHex()); - QBrush highLighted = QBrush(_highlightingColor); - QPen colHighlighted = QPen(this->palette().color(QPalette::WindowText)); - QBrush selected = QBrush(_selectionColor); - QPen colSelected = QPen(Qt::white); - QPen colStandard = QPen(this->palette().color(QPalette::WindowText)); - - painter.setBackgroundMode(Qt::TransparentMode); - - for (int lineIdx = firstLineIdx, yPos = yPosStart; lineIdx < lastLineIdx; lineIdx += BYTES_PER_LINE, yPos +=_charHeight) - { - QByteArray hex; - int xPos = _xPosHex; - for (int colIdx = 0; ((lineIdx + colIdx) < _xData.size() and (colIdx < BYTES_PER_LINE)); colIdx++) - { - int posBa = lineIdx + colIdx; - if ((getSelectionBegin() <= posBa) && (getSelectionEnd() > posBa)) - { - painter.setBackground(selected); - painter.setBackgroundMode(Qt::OpaqueMode); - painter.setPen(colSelected); - } - else - { - if (_highlighting) - { - // hilight diff bytes - painter.setBackground(highLighted); - if (_xData.dataChanged(posBa)) - { - painter.setPen(colHighlighted); - painter.setBackgroundMode(Qt::OpaqueMode); - } - else - { - painter.setPen(colStandard); - painter.setBackgroundMode(Qt::TransparentMode); - } - } - } - - // render hex value - if (colIdx == 0) - { - hex = hexBa.mid((lineIdx - firstLineIdx) * 2, 2); - painter.drawText(xPos, yPos, hex); - xPos += 2 * _charWidth; - } else { - hex = hexBa.mid((lineIdx + colIdx - firstLineIdx) * 2, 2).prepend(" "); - painter.drawText(xPos, yPos, hex); - xPos += 3 * _charWidth; - } - - } - } - painter.setBackgroundMode(Qt::TransparentMode); - painter.setPen(this->palette().color(QPalette::WindowText)); - - // paint ascii area - if (_asciiArea) - { - for (int lineIdx = firstLineIdx, yPos = yPosStart; lineIdx < lastLineIdx; lineIdx += BYTES_PER_LINE, yPos +=_charHeight) - { - int xPosAscii = _xPosAscii; - for (int colIdx = 0; ((lineIdx + colIdx) < _xData.size() and (colIdx < BYTES_PER_LINE)); colIdx++) - { - painter.drawText(xPosAscii, yPos, _xData.asciiChar(lineIdx + colIdx)); - xPosAscii += _charWidth; - } - } - } - - // paint cursor - if (_blink) - { - if (_overwriteMode) - painter.fillRect(_cursorX, _cursorY + _charHeight - 2, _charWidth, 2, this->palette().color(QPalette::WindowText)); - else - painter.fillRect(_cursorX, _cursorY, 2, _charHeight, this->palette().color(QPalette::WindowText)); - } - - if (_size != _xData.size()) - { - _size = _xData.size(); - emit currentSizeChanged(_size); - } -} - -void QHexEditPrivate::setCursorPos(int position) -{ - // delete cursor - _blink = false; - update(); - - // cursor in range? - if (_overwriteMode) - { - if (position > (_xData.size() * 2 - 1)) - position = _xData.size() * 2 - 1; - } else { - if (position > (_xData.size() * 2)) - position = _xData.size() * 2; - } - - if (position < 0) - position = 0; - - // calc position - _cursorPosition = position; - _cursorY = (position / (2 * BYTES_PER_LINE)) * _charHeight + 4; - int x = (position % (2 * BYTES_PER_LINE)); - _cursorX = (((x / 2) * 3) + (x % 2)) * _charWidth + _xPosHex; - - // immiadately draw cursor - _blink = true; - update(); - emit currentAddressChanged(_cursorPosition/2); -} - -int QHexEditPrivate::cursorPos(QPoint pos) -{ - int result = -1; - // find char under cursor - if ((pos.x() >= _xPosHex) and (pos.x() < (_xPosHex + HEXCHARS_IN_LINE * _charWidth))) - { - int x = (pos.x() - _xPosHex) / _charWidth; - if ((x % 3) == 0) - x = (x / 3) * 2; - else - x = ((x / 3) * 2) + 1; - int y = ((pos.y() - 3) / _charHeight) * 2 * BYTES_PER_LINE; - result = x + y; - } - return result; -} - -int QHexEditPrivate::cursorPos() -{ - return _cursorPosition; -} - -void QHexEditPrivate::resetSelection(int pos) -{ - if (pos < 0) - pos = 0; - pos = pos / 2; - _selectionInit = pos; - _selectionBegin = pos; - _selectionEnd = pos; -} - -void QHexEditPrivate::setSelection(int pos) -{ - if (pos < 0) - pos = 0; - pos = pos / 2; - if (pos >= _selectionInit) - { - _selectionEnd = pos; - _selectionBegin = _selectionInit; - } - else - { - _selectionBegin = pos; - _selectionEnd = _selectionInit; - } -} - -int QHexEditPrivate::getSelectionBegin() -{ - return _selectionBegin; -} - -int QHexEditPrivate::getSelectionEnd() -{ - return _selectionEnd; -} - - -void QHexEditPrivate::updateCursor() -{ - if (_blink) - _blink = false; - else - _blink = true; - update(_cursorX, _cursorY, _charWidth, _charHeight); -} - -void QHexEditPrivate::adjust() -{ - _charWidth = fontMetrics().width(QLatin1Char('9')); - _charHeight = fontMetrics().height(); - - _xPosAdr = 0; - if (_addressArea) - _xPosHex = _xData.realAddressNumbers()*_charWidth + GAP_ADR_HEX; - else - _xPosHex = 0; - _xPosAscii = _xPosHex + HEXCHARS_IN_LINE * _charWidth + GAP_HEX_ASCII; - - // tell QAbstractScollbar, how big we are - setMinimumHeight(((_xData.size()/16 + 1) * _charHeight) + 5); - setMinimumWidth(_xPosAscii + (BYTES_PER_LINE * _charWidth)); - - update(); -} diff --git a/extra/qhexedit2/src/qhexedit_p.h b/extra/qhexedit2/src/qhexedit_p.h deleted file mode 100644 index b802af3..0000000 --- a/extra/qhexedit2/src/qhexedit_p.h +++ /dev/null @@ -1,120 +0,0 @@ -#ifndef QHEXEDIT_P_H -#define QHEXEDIT_P_H - -/** \cond docNever */ - - -#include -#include "xbytearray.h" - -class QHexEditPrivate : public QWidget -{ -Q_OBJECT - -public: - QHexEditPrivate(QScrollArea *parent); - - void setAddressAreaColor(QColor const &color); - QColor addressAreaColor(); - - void setAddressOffset(int offset); - int addressOffset(); - - void setCursorPos(int position); - int cursorPos(); - - void setData(QByteArray const &data); - QByteArray data(); - - void setHighlightingColor(QColor const &color); - QColor highlightingColor(); - - void setOverwriteMode(bool overwriteMode); - bool overwriteMode(); - - void setReadOnly(bool readOnly); - bool isReadOnly(); - - void setSelectionColor(QColor const &color); - QColor selectionColor(); - - XByteArray & xData(); - - void insert(int index, const QByteArray & ba); - void insert(int index, char ch); - void remove(int index, int len=1); - void replace(int index, char ch); - void replace(int index, const QByteArray & ba); - - void setAddressArea(bool addressArea); - void setAddressWidth(int addressWidth); - void setAsciiArea(bool asciiArea); - void setHighlighting(bool mode); - virtual void setFont(const QFont &font); - - void undo(); - void redo(); - - QString toRedableString(); - QString selectionToReadableString(); - -signals: - void currentAddressChanged(int address); - void currentSizeChanged(int size); - void dataChanged(); - void overwriteModeChanged(bool state); - -protected: - void keyPressEvent(QKeyEvent * event); - void mouseMoveEvent(QMouseEvent * event); - void mousePressEvent(QMouseEvent * event); - - void paintEvent(QPaintEvent *event); - - int cursorPos(QPoint pos); // calc cursorpos from graphics position. DOES NOT STORE POSITION - - void resetSelection(int pos); - void setSelection(int pos); // set min (if below init) or max (if greater init) - int getSelectionBegin(); - int getSelectionEnd(); - - -private slots: - void updateCursor(); - -private: - void adjust(); - - QColor _addressAreaColor; - QColor _highlightingColor; - QColor _selectionColor; - QScrollArea *_scrollArea; - QTimer _cursorTimer; - QUndoStack *_undoStack; - - XByteArray _xData; // Hält den Inhalt des Hex Editors - - bool _blink; // true: then cursor blinks - bool _renderingRequired; // Flag to store that rendering is necessary - bool _addressArea; // left area of QHexEdit - bool _asciiArea; // medium area - bool _highlighting; // highlighting of changed bytes - bool _overwriteMode; - bool _readOnly; // true: the user can only look and navigate - - int _charWidth, _charHeight; // char dimensions (dpendend on font) - int _cursorX, _cursorY; // graphics position of the cursor - int _cursorPosition; // charakter positioin in stream (on byte ends in to steps) - int _xPosAdr, _xPosHex, _xPosAscii; // graphics x-position of the areas - - int _selectionBegin; // First selected char - int _selectionEnd; // Last selected char - int _selectionInit; // That's, where we pressed the mouse button - - int _size; -}; - -/** \endcond docNever */ - -#endif - diff --git a/extra/qhexedit2/src/xbytearray.cpp b/extra/qhexedit2/src/xbytearray.cpp deleted file mode 100644 index ec8bf3d..0000000 --- a/extra/qhexedit2/src/xbytearray.cpp +++ /dev/null @@ -1,167 +0,0 @@ -#include "xbytearray.h" - -XByteArray::XByteArray() -{ - _oldSize = -99; - _addressNumbers = 4; - _addressOffset = 0; - -} - -int XByteArray::addressOffset() -{ - return _addressOffset; -} - -void XByteArray::setAddressOffset(int offset) -{ - _addressOffset = offset; -} - -int XByteArray::addressWidth() -{ - return _addressNumbers; -} - -void XByteArray::setAddressWidth(int width) -{ - if ((width >= 0) and (width<=6)) - { - _addressNumbers = width; - } -} - -QByteArray & XByteArray::data() -{ - return _data; -} - -void XByteArray::setData(QByteArray data) -{ - _data = data; - _changedData = QByteArray(data.length(), char(0)); -} - -bool XByteArray::dataChanged(int i) -{ - return bool(_changedData[i]); -} - -QByteArray XByteArray::dataChanged(int i, int len) -{ - return _changedData.mid(i, len); -} - -void XByteArray::setDataChanged(int i, bool state) -{ - _changedData[i] = char(state); -} - -void XByteArray::setDataChanged(int i, const QByteArray & state) -{ - int length = state.length(); - int len; - if ((i + length) > _changedData.length()) - len = _changedData.length() - i; - else - len = length; - _changedData.replace(i, len, state); -} - -int XByteArray::realAddressNumbers() -{ - if (_oldSize != _data.size()) - { - // is addressNumbers wide enought? - QString test = QString("%1") - .arg(_data.size() + _addressOffset, _addressNumbers, 16, QChar('0')); - _realAddressNumbers = test.size(); - } - return _realAddressNumbers; -} - -int XByteArray::size() -{ - return _data.size(); -} - -QByteArray & XByteArray::insert(int i, char ch) -{ - _data.insert(i, ch); - _changedData.insert(i, char(1)); - return _data; -} - -QByteArray & XByteArray::insert(int i, const QByteArray & ba) -{ - _data.insert(i, ba); - _changedData.insert(i, QByteArray(ba.length(), char(1))); - return _data; -} - -QByteArray & XByteArray::remove(int i, int len) -{ - _data.remove(i, len); - _changedData.remove(i, len); - return _data; -} - -QByteArray & XByteArray::replace(int index, char ch) -{ - _data[index] = ch; - _changedData[index] = char(1); - return _data; -} - -QByteArray & XByteArray::replace(int index, const QByteArray & ba) -{ - int len = ba.length(); - return replace(index, len, ba); -} - -QByteArray & XByteArray::replace(int index, int length, const QByteArray & ba) -{ - int len; - if ((index + length) > _data.length()) - len = _data.length() - index; - else - len = length; - _data.replace(index, len, ba.mid(0, len)); - _changedData.replace(index, len, QByteArray(len, char(1))); - return _data; -} - -QChar XByteArray::asciiChar(int index) -{ - char ch = _data[index]; - if ((ch < 0x20) or (ch > 0x7e)) - ch = '.'; - return QChar(ch); -} - -QString XByteArray::toRedableString(int start, int end) -{ - int adrWidth = realAddressNumbers(); - if (_addressNumbers > adrWidth) - adrWidth = _addressNumbers; - if (end < 0) - end = _data.size(); - - QString result; - for (int i=start; i < end; i += 16) - { - QString adrStr = QString("%1").arg(_addressOffset + i, adrWidth, 16, QChar('0')); - QString hexStr; - QString ascStr; - for (int j=0; j<16; j++) - { - if ((i + j) < _data.size()) - { - hexStr.append(" ").append(_data.mid(i+j, 1).toHex()); - ascStr.append(asciiChar(i+j)); - } - } - result += adrStr + " " + QString("%1").arg(hexStr, -48) + " " + QString("%1").arg(ascStr, -17) + "\n"; - } - return result; -} diff --git a/extra/qhexedit2/src/xbytearray.h b/extra/qhexedit2/src/xbytearray.h deleted file mode 100644 index 2b67c61..0000000 --- a/extra/qhexedit2/src/xbytearray.h +++ /dev/null @@ -1,66 +0,0 @@ -#ifndef XBYTEARRAY_H -#define XBYTEARRAY_H - -/** \cond docNever */ - -#include - -/*! XByteArray represents the content of QHexEcit. -XByteArray comprehend the data itself and informations to store if it was -changed. The QHexEdit component uses these informations to perform nice -rendering of the data - -XByteArray also provides some functionality to insert, replace and remove -single chars and QByteArras. Additionally some functions support rendering -and converting to readable strings. -*/ -class XByteArray -{ -public: - explicit XByteArray(); - - int addressOffset(); - void setAddressOffset(int offset); - - int addressWidth(); - void setAddressWidth(int width); - - QByteArray & data(); - void setData(QByteArray data); - - bool dataChanged(int i); - QByteArray dataChanged(int i, int len); - void setDataChanged(int i, bool state); - void setDataChanged(int i, const QByteArray & state); - - int realAddressNumbers(); - int size(); - - QByteArray & insert(int i, char ch); - QByteArray & insert(int i, const QByteArray & ba); - - QByteArray & remove(int pos, int len); - - QByteArray & replace(int index, char ch); - QByteArray & replace(int index, const QByteArray & ba); - QByteArray & replace(int index, int length, const QByteArray & ba); - - QChar asciiChar(int index); - QString toRedableString(int start=0, int end=-1); - -signals: - -public slots: - -private: - QByteArray _data; - QByteArray _changedData; - - int _addressNumbers; // wanted width of address area - int _addressOffset; // will be added to the real addres inside bytearray - int _realAddressNumbers; // real width of address area (can be greater then wanted width) - int _oldSize; // size of data -}; - -/** \endcond docNever */ -#endif // XBYTEARRAY_H From 8b2541fbaf0f16f1cc9cd733efdc6a1393125afb Mon Sep 17 00:00:00 2001 From: Srivats P Date: Mon, 10 Apr 2017 20:59:17 +0530 Subject: [PATCH 16/49] Fix incorrect extension id for STP There was a discrepancy between Protocol::kStpFieldNumber (209) and the one defined in stp.proto (210) which caused protobuf reflection to fail when queried for a descriptor corresponding to 209. Referencing the null descriptor further in the code caused a crash. --- common/stp.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/stp.proto b/common/stp.proto index 82d0e07..89a5223 100644 --- a/common/stp.proto +++ b/common/stp.proto @@ -40,5 +40,5 @@ message Stp { } extend Protocol { - optional Stp stp = 210; + optional Stp stp = 209; } From 33e756656dbb02f18d72eef3fab6d33b6b2511a8 Mon Sep 17 00:00:00 2001 From: Carter Sande Date: Mon, 10 Apr 2017 09:10:16 -0700 Subject: [PATCH 17/49] Update .travis.yml to reflect qt->qt@4 change --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 535c0eb..9ec0896 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ matrix: compiler: gcc before_install: - - "if [ $TRAVIS_OS_NAME = 'osx' ]; then brew update && brew tap cartr/qt4 && brew tap-pin cartr/qt4 && brew install qt && brew install protobuf && ls -lR /usr/local/include; fi" + - "if [ $TRAVIS_OS_NAME = 'osx' ]; then brew update && brew tap cartr/qt4 && brew tap-pin cartr/qt4 && brew install qt@4 && brew install protobuf && ls -lR /usr/local/include; fi" addons: apt: From a8ec2f1bdaeef7509fe93661f902003dcfaf95df Mon Sep 17 00:00:00 2001 From: Srivats P Date: Thu, 6 Jul 2017 20:05:15 +0530 Subject: [PATCH 18/49] Fix IPv4/IPv6 PDML import with Tshark 2.x Fixes #219 --- common/ip4pdml.cpp | 14 ++++++++++++-- common/ip6pdml.cpp | 2 ++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/common/ip4pdml.cpp b/common/ip4pdml.cpp index f355260..eefbd6e 100644 --- a/common/ip4pdml.cpp +++ b/common/ip4pdml.cpp @@ -26,7 +26,6 @@ PdmlIp4Protocol::PdmlIp4Protocol() { ostProtoId_ = OstProto::Protocol::kIp4FieldNumber; - fieldMap_.insert("ip.version", OstProto::Ip4::kVerHdrlenFieldNumber); fieldMap_.insert("ip.dsfield", OstProto::Ip4::kTosFieldNumber); fieldMap_.insert("ip.len", OstProto::Ip4::kTotlenFieldNumber); fieldMap_.insert("ip.id", OstProto::Ip4::kIdFieldNumber); @@ -50,7 +49,18 @@ void PdmlIp4Protocol::unknownFieldHandler(QString name, int /*pos*/, { bool isOk; - if ((name == "ip.options") || + if (name == "ip.version") + { + OstProto::Ip4 *ip4 = pbProto->MutableExtension(OstProto::ip4); + + if (!attributes.value("unmaskedvalue").isEmpty()) + ip4->set_ver_hdrlen(attributes.value("unmaskedvalue") + .toString().toUInt(&isOk, kBaseHex)); + else + ip4->set_ver_hdrlen(attributes.value("value") + .toString().toUInt(&isOk, kBaseHex)); + } + else if ((name == "ip.options") || attributes.value("show").toString().startsWith("Options")) { options_ = QByteArray::fromHex( diff --git a/common/ip6pdml.cpp b/common/ip6pdml.cpp index 2f3a7f8..f0507e5 100644 --- a/common/ip6pdml.cpp +++ b/common/ip6pdml.cpp @@ -26,7 +26,9 @@ PdmlIp6Protocol::PdmlIp6Protocol() ostProtoId_ = OstProto::Protocol::kIp6FieldNumber; fieldMap_.insert("ipv6.version", OstProto::Ip6::kVersionFieldNumber); + // Tshark 1.x uses .class while 2.x uses .tclass - we'll use either fieldMap_.insert("ipv6.class", OstProto::Ip6::kTrafficClassFieldNumber); + fieldMap_.insert("ipv6.tclass", OstProto::Ip6::kTrafficClassFieldNumber); fieldMap_.insert("ipv6.flow", OstProto::Ip6::kFlowLabelFieldNumber); fieldMap_.insert("ipv6.plen", OstProto::Ip6::kPayloadLengthFieldNumber); fieldMap_.insert("ipv6.nxt", OstProto::Ip6::kNextHeaderFieldNumber); From 95f00f267325af97ff6b0c7cbfaa7119be5b029b Mon Sep 17 00:00:00 2001 From: Srivats P Date: Mon, 14 Aug 2017 10:43:11 +0530 Subject: [PATCH 19/49] Add documentation to python binding Also some minor documentation for some .proto that goes into the API guide --- binding/core.py | 41 ++++++++++++++++++++++++++++++++++++----- common/dot2llc.proto | 2 +- common/dot2snap.proto | 1 + common/dot3.proto | 1 + common/gmp.proto | 2 +- common/protocol.proto | 2 +- 6 files changed, 41 insertions(+), 8 deletions(-) diff --git a/binding/core.py b/binding/core.py index c89020b..19b0e3a 100644 --- a/binding/core.py +++ b/binding/core.py @@ -14,6 +14,11 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see +""" +This is the core module for the Ostinato Python API. +All drone configuration is done by creating an instance of the +`DroneProxy` class and calling its various methods subsequently. +""" import os from rpc import OstinatoRpcChannel, OstinatoRpcController, RpcError @@ -22,8 +27,16 @@ import protocols.emulproto_pb2 as emul from __init__ import __version__ class DroneProxy(object): + """ + DroneProxy acts as a proxy to a Drone instance. A method invoked on this + class will be trigerred on the actual Drone instance being proxied + """ def __init__(self, host_name, port_number=7878): + """ + Create a DroneProxy object as a proxy to the Drone instance + running at the specified host and port + """ self.host = host_name self.port = port_number self.channel = OstinatoRpcChannel() @@ -34,14 +47,26 @@ class DroneProxy(object): fn = lambda request=self.void, method_name=method.name: \ self.callRpcMethod(method_name, request) self.__dict__[method.name] = fn + self.__dict__[method.name].__doc__ = 'This is a protobuf API' def hostName(self): + """ + Returns the hostname of the Drone which is being proxied by + this DroneProxy object + """ return self.host def portNumber(self): + """ + Returns the TCP port number of the Drone which is being proxied by + this DroneProxy object + """ return self.port def connect(self): + """ + Connect to the Drone instance + """ self.channel.connect(self.host, self.port) ver = ost_pb.VersionInfo() ver.client_name = 'python-ostinato' @@ -52,6 +77,9 @@ class DroneProxy(object): (ver.version, compat.notes)) def disconnect(self): + """ + Disconnect from the Drone instance + """ self.channel.disconnect() def callRpcMethod(self, method_name, request): @@ -61,9 +89,12 @@ class DroneProxy(object): return controller.response def saveCaptureBuffer(self, buffer, file_name): - f= open(file_name, 'wb') - f.write(buffer) - f.flush() - os.fsync(f.fileno()) - f.close() + """ + Save the capture buffer in a PCAP file + """ + f= open(file_name, 'wb') + f.write(buffer) + f.flush() + os.fsync(f.fileno()) + f.close() diff --git a/common/dot2llc.proto b/common/dot2llc.proto index 8223650..1b4ebf5 100644 --- a/common/dot2llc.proto +++ b/common/dot2llc.proto @@ -1,3 +1,4 @@ +/// (802.2 LLC) /* Copyright (C) 2010 Srivats P. @@ -23,7 +24,6 @@ import "llc.proto"; package OstProto; -// 802.2 LLC message Dot2Llc { // Empty since this is a 'combo' protocol } diff --git a/common/dot2snap.proto b/common/dot2snap.proto index d49059f..a71d5c1 100644 --- a/common/dot2snap.proto +++ b/common/dot2snap.proto @@ -1,3 +1,4 @@ +/// (802.2 SNAP) /* Copyright (C) 2010 Srivats P. diff --git a/common/dot3.proto b/common/dot3.proto index f20f120..907ecdb 100644 --- a/common/dot3.proto +++ b/common/dot3.proto @@ -1,3 +1,4 @@ +/// (802.3) /* Copyright (C) 2010 Srivats P. diff --git a/common/gmp.proto b/common/gmp.proto index f1fbf56..65265e6 100755 --- a/common/gmp.proto +++ b/common/gmp.proto @@ -21,7 +21,7 @@ import "protocol.proto"; package OstProto; -// Group Management Protocol (i.e. IGMP and MLD) +/// Group Management Protocol (i.e. IGMP and MLD) message Gmp { // // Common fields for both ASM and SSM messages diff --git a/common/protocol.proto b/common/protocol.proto index 439d28f..7697a9c 100644 --- a/common/protocol.proto +++ b/common/protocol.proto @@ -52,8 +52,8 @@ message StreamCore { optional bool is_enabled = 2; optional uint32 ordinal = 3; - // Frame Length (includes CRC) optional FrameLengthMode len_mode = 14 [default = e_fl_fixed]; + /// Frame Length (includes CRC) optional uint32 frame_len = 15 [default = 64]; optional uint32 frame_len_min = 16 [default = 64]; optional uint32 frame_len_max = 17 [default = 1518]; From d39fbc2ed4ffc16e72000be319cadcca5e2a990e Mon Sep 17 00:00:00 2001 From: Srivats P Date: Mon, 4 Sep 2017 18:57:13 +0530 Subject: [PATCH 20/49] Add UTM params to docs url --- client/mainwindow.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/mainwindow.cpp b/client/mainwindow.cpp index 240b410..4596edd 100644 --- a/client/mainwindow.cpp +++ b/client/mainwindow.cpp @@ -299,7 +299,8 @@ void MainWindow::on_actionViewRestoreDefaults_triggered() void MainWindow::on_actionHelpOnline_triggered() { - QDesktopServices::openUrl(QUrl("http://ostinato.org/docs")); + QDesktopServices::openUrl( + QUrl("http://ostinato.org/docs/?utm_source=app&utm_medium=menu&utm_campaign=help")); } void MainWindow::on_actionHelpAbout_triggered() From cb52f9ade196d7b52e3692ea8f319d301ea67ea8 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Mon, 4 Sep 2017 22:57:45 +0530 Subject: [PATCH 21/49] Inform user if local drone doesn't start --- client/mainwindow.cpp | 45 +++++++++++++++++++++++++++++++++++++++++++ client/mainwindow.h | 3 +++ 2 files changed, 48 insertions(+) diff --git a/client/mainwindow.cpp b/client/mainwindow.cpp index 4596edd..2db7670 100644 --- a/client/mainwindow.cpp +++ b/client/mainwindow.cpp @@ -43,6 +43,13 @@ along with this program. If not, see #include #include +#ifdef Q_OS_WIN32 +#define WIN32_NO_STATUS +#include +#undef WIN32_NO_STATUS +#include +#endif + extern const char* version; extern const char* revision; @@ -68,6 +75,9 @@ MainWindow::MainWindow(QWidget *parent) qDebug("staring local server - %s", qPrintable(serverApp)); localServer_ = new QProcess(this); + connect(localServer_, SIGNAL(started()), SLOT(onLocalServerStarted())); + connect(localServer_, SIGNAL(error(QProcess::ProcessError)), + SLOT(onLocalServerError(QProcess::ProcessError))); localServer_->setProcessChannelMode(QProcess::ForwardedChannels); localServer_->start(serverApp, QStringList()); } @@ -317,6 +327,41 @@ void MainWindow::on_actionHelpAbout_triggered() delete aboutDialog; } +void MainWindow::onLocalServerStarted() +{ + // We are only interested in startup errors + disconnect(localServer_, SIGNAL(error(QProcess::ProcessError)), + this, SLOT(onLocalServerError(QProcess::ProcessError))); +} + +void MainWindow::onLocalServerError(QProcess::ProcessError error) +{ + QMessageBox msgBox(this); + msgBox.setIcon(QMessageBox::Warning); + msgBox.setTextFormat(Qt::RichText); + msgBox.setStyleSheet("messagebox-text-interaction-flags: 5"); // mouse copy + QString errorStr = tr("

Failed to start the local drone agent - " + "error 0x%1, exit code 0x%2.

") + .arg(error, 0, 16) + .arg(localServer_->exitCode(), 0, 16); +#ifdef Q_OS_WIN32 + if (localServer_->exitCode() == STATUS_DLL_NOT_FOUND) + errorStr.append(tr("

This is most likely because Packet.dll " + "was not found - make sure you have " + "WinPcap " + "installed.

")); +#endif + msgBox.setText(errorStr); + msgBox.setInformativeText(tr("Run drone directly for more information.")); + msgBox.exec(); + + QMessageBox::information(this, QString(), + tr("

If you have remote drone agents running, you can still add " + "and connect to them.

" + "

If you don't want to start the local drone agent at startup, " + "provide the -s option to Ostinato on the command line

")); +} + void MainWindow::onNewVersion(QString newVersion) { statusBar()->showMessage(QString("New Ostinato version %1 available. " diff --git a/client/mainwindow.h b/client/mainwindow.h index 1b37798..eb63079 100644 --- a/client/mainwindow.h +++ b/client/mainwindow.h @@ -22,6 +22,7 @@ along with this program. If not, see #include "ui_mainwindow.h" #include +#include class PortsWindow; class PortStatsWindow; @@ -59,6 +60,8 @@ public slots: void on_actionHelpAbout_triggered(); private slots: + void onLocalServerStarted(); + void onLocalServerError(QProcess::ProcessError error); void onNewVersion(QString version); }; From 40c5e00ec2a10681d2280841c71d508ed4586ea3 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Thu, 7 Sep 2017 20:45:43 +0530 Subject: [PATCH 22/49] (UX) Add textual hints for to the ports window Improves first run experience --- client/portswindow.cpp | 6 +- client/portswindow.ui | 332 ++++++++++++++++++++++++----------------- 2 files changed, 197 insertions(+), 141 deletions(-) diff --git a/client/portswindow.cpp b/client/portswindow.cpp index 76f7ee7..68d9e24 100644 --- a/client/portswindow.cpp +++ b/client/portswindow.cpp @@ -349,8 +349,8 @@ void PortsWindow::when_portView_currentChanged(const QModelIndex& currentIndex, if (!current.isValid()) { - qDebug("setting stacked widget to blank page"); - swDetail->setCurrentIndex(2); // blank page + qDebug("setting stacked widget to welcome page"); + swDetail->setCurrentIndex(0); // welcome page } else { @@ -360,7 +360,7 @@ void PortsWindow::when_portView_currentChanged(const QModelIndex& currentIndex, } else if (plm->isPort(current)) { - swDetail->setCurrentIndex(0); // port detail page + swDetail->setCurrentIndex(2); // port detail page updatePortRates(); connect(&(plm->port(current)), SIGNAL(portRateChanged(int, int)), SLOT(updatePortRates())); diff --git a/client/portswindow.ui b/client/portswindow.ui index cbab508..ca3331f 100644 --- a/client/portswindow.ui +++ b/client/portswindow.ui @@ -1,7 +1,8 @@ - + + PortsWindow - - + + 0 0 @@ -9,83 +10,138 @@ 352 - + Form - - - - + + + + Qt::Horizontal - + false - - - + + + 1 0 - + Qt::ActionsContextMenu - + QAbstractItemView::SingleSelection - - - + + + 2 0 - - 0 + + 2 - - - - 0 - - - 0 - - - 0 - - + + + + + + <p><b>How to use Ostinato</b></p> +<p>The port list on the left contains all the ports on which you can transmit packets.</p> +<p>Ports belong to a port group. Make sure the Port Group has a <img src=":/icons/bullet_green.png"/> next to it, then double click the port group to show or hide the ports in the port group.</p> +<p>To generate packets, you need to create and configure packet streams. A stream is a sequence of one or more same or similar packets.</p> +<p>To create a stream, select the port on which you want to send packets.</p> +<hr/> +<p>Don't see the port that you want (or any ports at all) inside the port group? <a href="http://ostinato.org/docs/faq">Get Help!</a></p> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + <p>You have selected a port group in the port list on the left.</p> +<p>You can transmit packets on any of the ports within the port group.</p> +<p>Make sure the port group has a <img src=":/icons/bullet_green.png"/> next to it and then double click the port group to show or hide the ports in the port group.</p> +<p>To generate packets, you need to create and configure packet streams. A stream is a sequence of one or more same or similar packets.</p> +<p>To create a stream, select the port on which you want to send packets. </p> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + Qt::Vertical + + + + 20 + 177 + + + + + + + + + 0 - - + + QFrame::Panel - + QFrame::Raised - - - 3 - - - 3 - - - 3 - - + + 3 - + Qt::Horizontal - + 40 20 @@ -94,8 +150,15 @@ - - + + + Right click in the blank area below to configure streams. Click Apply on the right to activate the changes + + + + + + Apply @@ -104,50 +167,50 @@ - - + + 0 - - + + Streams - + - + - - + + Avg pps - + true - + - - + + Avg bps - - + + false - + Qt::Horizontal - + 40 20 @@ -158,39 +221,39 @@ - - - + + + 0 1 - + Qt::ActionsContextMenu - + QFrame::StyledPanel - + 1 - + QAbstractItemView::ExtendedSelection - + QAbstractItemView::SelectRows - - + + Devices - + - + @@ -198,109 +261,102 @@ - - - - - - Select a port to configure streams - - - Qt::AlignCenter - - - - - - - - - :/icons/portgroup_add.png + + + + :/icons/portgroup_add.png:/icons/portgroup_add.png - + New Port Group - - - :/icons/portgroup_delete.png + + + + :/icons/portgroup_delete.png:/icons/portgroup_delete.png - + Delete Port Group - - - :/icons/portgroup_connect.png + + + + :/icons/portgroup_connect.png:/icons/portgroup_connect.png - + Connect Port Group - - - :/icons/portgroup_disconnect.png + + + + :/icons/portgroup_disconnect.png:/icons/portgroup_disconnect.png - + Disconnect Port Group - - - :/icons/stream_add.png + + + + :/icons/stream_add.png:/icons/stream_add.png - + New Stream - - - :/icons/stream_delete.png + + + + :/icons/stream_delete.png:/icons/stream_delete.png - + Delete Stream - - - :/icons/stream_edit.png + + + + :/icons/stream_edit.png:/icons/stream_edit.png - + Edit Stream - - + + true - + Exclusive Port Control (EXPERIMENTAL) - - + + Open Streams ... - - + + Save Streams ... - - + + Port Configuration ... - - - :/icons/stream_duplicate.png + + + + :/icons/stream_duplicate.png:/icons/stream_duplicate.png - + Duplicate Stream @@ -314,7 +370,7 @@ - + @@ -323,11 +379,11 @@ averagePacketsPerSec setEnabled(bool) - + 326 80 - + 454 79 @@ -339,11 +395,11 @@ averageBitsPerSec setEnabled(bool) - + 523 80 - + 651 88 From 88cea753b659dd627157da9940190abe36360520 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Thu, 7 Sep 2017 20:48:46 +0530 Subject: [PATCH 23/49] UX: Add category labels to Port Stats Window toolbar --- client/portstatswindow.ui | 309 ++++++++++++++++++++++---------------- 1 file changed, 181 insertions(+), 128 deletions(-) diff --git a/client/portstatswindow.ui b/client/portstatswindow.ui index 870633e..1406a4b 100644 --- a/client/portstatswindow.ui +++ b/client/portstatswindow.ui @@ -1,7 +1,8 @@ - + + PortStatsWindow - - + + 0 0 @@ -9,183 +10,234 @@ 415 - + Form - + - - + + QFrame::Panel - + QFrame::Raised - + - - + + + Transmit + + + + + + Start Tx - + Starts transmit on selected port(s) - - Start Transmit + + Start - - :/icons/control_play.png + + + :/icons/control_play.png:/icons/control_play.png - - + + Stop Tx - + Stops transmit on selected port(s) - - Stop Trasmit + + Stop - - :/icons/control_stop.png + + + :/icons/control_stop.png:/icons/control_stop.png - - - Clear Selected Port Stats - - - Clears statistics of the selected port(s) - - - Clear - - - :/icons/portstats_clear.png - - - - - - - Clear All Ports Stats - - - Clears statistics of all ports - - - Clear All - - - :/icons/portstats_clear_all.png - - - - - - - Start Capture - - - Captures packets on the selected port(s) - - - Start Capture - - - :/icons/sound_none.png - - - - - - - Stop Capture - - - End capture on selecteed port(s) - - - Stop Capture - - - :/icons/sound_mute.png - - - - - - - View Capture Buffer - - - View captured packets on selected port(s) - - - View Capture - - - :/icons/magnifier.png - - - - - - + + Qt::Vertical - - + + + Stats + + + + + + + Clear Selected Port Stats + + + Clears statistics of the selected port(s) + + + Clear + + + + :/icons/portstats_clear.png:/icons/portstats_clear.png + + + + + + + Clear All Ports Stats + + + Clears statistics of all ports + + + Clear All + + + + :/icons/portstats_clear_all.png:/icons/portstats_clear_all.png + + + + + + + Qt::Vertical + + + + + + + Capture + + + + + + + Start Capture + + + Captures packets on the selected port(s) + + + Start + + + + :/icons/sound_none.png:/icons/sound_none.png + + + + + + + Stop Capture + + + End capture on selecteed port(s) + + + Stop + + + + :/icons/sound_mute.png:/icons/sound_mute.png + + + + + + + View Capture Buffer + + + View captured packets on selected port(s) + + + View + + + + :/icons/magnifier.png:/icons/magnifier.png + + + + + + + Qt::Vertical + + + + + + + ARP/ND + + + + + + Resolve Neighbors - + Resolve Device Neighbors on selected port(s) - + Resolve Neighbors - - :/icons/neighbor_resolve.png + + + :/icons/neighbor_resolve.png:/icons/neighbor_resolve.png - - + + Clear Neighbors - + Clear Device Neighbors on selected port(s) - + Clear Neighbors - - :/icons/neighbor_clear.png + + + :/icons/neighbor_clear.png:/icons/neighbor_clear.png - - + + Qt::Vertical - + Qt::Horizontal - + 40 20 @@ -194,15 +246,16 @@ - - + + Select which ports to view - + Filter - - :/icons/portstats_filter.png + + + :/icons/portstats_filter.png:/icons/portstats_filter.png @@ -210,12 +263,12 @@ - + - + From d348229028f5e31374b9abfc5cb1bd6c261b9561 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Fri, 8 Sep 2017 21:13:53 +0530 Subject: [PATCH 24/49] UX: Improve PortStatsWindow select, toolbar buttons - Automatic full column selection - Enable/Disable toolbar buttons based on selection - Code refactoring to avoid unncessary selectedColumns computation --- client/portstatswindow.cpp | 70 ++++++++++++++++++++++++-------------- client/portstatswindow.h | 6 ++-- client/portstatswindow.ui | 8 +++-- 3 files changed, 55 insertions(+), 29 deletions(-) diff --git a/client/portstatswindow.cpp b/client/portstatswindow.cpp index 0e7b73f..c06177b 100644 --- a/client/portstatswindow.cpp +++ b/client/portstatswindow.cpp @@ -47,6 +47,12 @@ PortStatsWindow::PortStatsWindow(PortGroupList *pgl, QWidget *parent) tvPortStats->verticalHeader()->setDefaultSectionSize( tvPortStats->verticalHeader()->minimumSectionSize()); + connect(tvPortStats->selectionModel(), + SIGNAL(selectionChanged( + const QItemSelection&, const QItemSelection&)), + SLOT(when_tvPortStats_selectionChanged( + const QItemSelection&, const QItemSelection&))); + when_tvPortStats_selectionChanged(QItemSelection(), QItemSelection()); } PortStatsWindow::~PortStatsWindow() @@ -70,12 +76,43 @@ void PortStatsWindow::showMyReservedPortsOnly(bool enabled) } /* ------------- SLOTS (private) -------------- */ + +void PortStatsWindow::when_tvPortStats_selectionChanged( + const QItemSelection& /*selected*/, + const QItemSelection& /*deselected*/) +{ + QModelIndexList indexList = + tvPortStats->selectionModel()->selectedColumns(); + + if (proxyStatsModel) { + selectedColumns.clear(); + foreach(QModelIndex index, indexList) + selectedColumns.append(proxyStatsModel->mapToSource(index)); + } + else + selectedColumns = indexList; + + bool isEmpty = selectedColumns.isEmpty(); + + tbStartTransmit->setDisabled(isEmpty); + tbStopTransmit->setDisabled(isEmpty); + + tbStartCapture->setDisabled(isEmpty); + tbStopCapture->setDisabled(isEmpty); + tbViewCapture->setDisabled(isEmpty); + + tbClear->setDisabled(isEmpty); + + tbResolveNeighbors->setDisabled(isEmpty); + tbClearNeighbors->setDisabled(isEmpty); +} + void PortStatsWindow::on_tbStartTransmit_clicked() { QList pgpl; // Get selected ports - model->portListFromIndex(selectedColumns(), pgpl); + model->portListFromIndex(selectedColumns, pgpl); // Clear selected ports, portgroup by portgroup for (int i = 0; i < pgpl.size(); i++) @@ -90,7 +127,7 @@ void PortStatsWindow::on_tbStopTransmit_clicked() QList pgpl; // Get selected ports - model->portListFromIndex(selectedColumns(), pgpl); + model->portListFromIndex(selectedColumns, pgpl); // Clear selected ports, portgroup by portgroup for (int i = 0; i < pgpl.size(); i++) @@ -106,7 +143,7 @@ void PortStatsWindow::on_tbStartCapture_clicked() QList pgpl; // Get selected ports - model->portListFromIndex(selectedColumns(), pgpl); + model->portListFromIndex(selectedColumns, pgpl); // Clear selected ports, portgroup by portgroup for (int i = 0; i < pgpl.size(); i++) @@ -122,7 +159,7 @@ void PortStatsWindow::on_tbStopCapture_clicked() QList pgpl; // Get selected ports - model->portListFromIndex(selectedColumns(), pgpl); + model->portListFromIndex(selectedColumns, pgpl); // Clear selected ports, portgroup by portgroup for (int i = 0; i < pgpl.size(); i++) @@ -138,7 +175,7 @@ void PortStatsWindow::on_tbViewCapture_clicked() QList pgpl; // Get selected ports - model->portListFromIndex(selectedColumns(), pgpl); + model->portListFromIndex(selectedColumns, pgpl); // Clear selected ports, portgroup by portgroup for (int i = 0; i < pgpl.size(); i++) @@ -153,7 +190,7 @@ void PortStatsWindow::on_tbResolveNeighbors_clicked() QList portList; // Get selected ports - model->portListFromIndex(selectedColumns(), portList); + model->portListFromIndex(selectedColumns, portList); // Resolve ARP/ND for selected ports, portgroup by portgroup for (int i = 0; i < portList.size(); i++) @@ -168,7 +205,7 @@ void PortStatsWindow::on_tbClearNeighbors_clicked() QList portList; // Get selected ports - model->portListFromIndex(selectedColumns(), portList); + model->portListFromIndex(selectedColumns, portList); // Clear ARP/ND for ports, portgroup by portgroup for (int i = 0; i < portList.size(); i++) @@ -183,7 +220,7 @@ void PortStatsWindow::on_tbClear_clicked() QList portList; // Get selected ports - model->portListFromIndex(selectedColumns(), portList); + model->portListFromIndex(selectedColumns, portList); // Clear selected ports, portgroup by portgroup for (int i = 0; i < portList.size(); i++) @@ -252,20 +289,3 @@ void PortStatsWindow::on_tbFilter_clicked() hv->moveSection(hv->visualIndex(newColumns.at(vi)), vi); } } - -/* ------------ Private Methods -------------- */ - -QModelIndexList PortStatsWindow::selectedColumns() -{ - QModelIndexList indexList = - tvPortStats->selectionModel()->selectedColumns(); - QModelIndexList sourceIndexList; - - if (!proxyStatsModel) - return indexList; - - foreach(QModelIndex index, indexList) - sourceIndexList.append(proxyStatsModel->mapToSource(index)); - - return sourceIndexList; -} diff --git a/client/portstatswindow.h b/client/portstatswindow.h index dfb5fbf..e5d993a 100644 --- a/client/portstatswindow.h +++ b/client/portstatswindow.h @@ -40,6 +40,9 @@ public slots: void showMyReservedPortsOnly(bool enabled); private slots: + void when_tvPortStats_selectionChanged(const QItemSelection &selected, + const QItemSelection &deselected); + void on_tbStartTransmit_clicked(); void on_tbStopTransmit_clicked(); @@ -56,11 +59,10 @@ private slots: void on_tbFilter_clicked(); private: - QModelIndexList selectedColumns(); - PortGroupList *pgl; PortStatsModel *model; QSortFilterProxyModel *proxyStatsModel; + QModelIndexList selectedColumns; }; diff --git a/client/portstatswindow.ui b/client/portstatswindow.ui index 1406a4b..f285b37 100644 --- a/client/portstatswindow.ui +++ b/client/portstatswindow.ui @@ -237,7 +237,7 @@ Qt::Horizontal - + 40 20 @@ -263,7 +263,11 @@ - + + + QAbstractItemView::SelectColumns + + From abb48a1c12cacbb0132d9d1cc25ff95216ddb459 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sat, 9 Sep 2017 13:13:52 +0530 Subject: [PATCH 25/49] UX: Auto expand port group and allow deselect in PortsWindow --- client/portswindow.cpp | 17 +++++++++++++- client/portswindow.ui | 7 +++++- client/xtreeview.h | 50 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 client/xtreeview.h diff --git a/client/portswindow.cpp b/client/portswindow.cpp index 68d9e24..b3a31d5 100644 --- a/client/portswindow.cpp +++ b/client/portswindow.cpp @@ -373,7 +373,22 @@ void PortsWindow::when_portView_currentChanged(const QModelIndex& currentIndex, void PortsWindow::when_portModel_dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { - qDebug("In %s", __FUNCTION__); + qDebug("In %s %d:(%d, %d) - %d:(%d, %d)", __FUNCTION__, + topLeft.parent().isValid(), topLeft.row(), topLeft.column(), + bottomRight.parent().isValid(), bottomRight.row(), bottomRight.column()); + + if (!topLeft.isValid() || !bottomRight.isValid()) + return; + + if (topLeft.parent() != bottomRight.parent()) + return; + + // If a port has changed, expand the port group + if (topLeft.parent().isValid()) + tvPortList->expand(proxyPortModel ? + proxyPortModel->mapFromSource(topLeft.parent()) : + topLeft.parent()); + #if 0 // not sure why the >= <= operators are not overloaded in QModelIndex if ((tvPortList->currentIndex() >= topLeft) && (tvPortList->currentIndex() <= bottomRight)) diff --git a/client/portswindow.ui b/client/portswindow.ui index ca3331f..546e468 100644 --- a/client/portswindow.ui +++ b/client/portswindow.ui @@ -22,7 +22,7 @@ false - + 1 @@ -368,6 +368,11 @@
deviceswidget.h
1 + + XTreeView + QTreeView +
xtreeview.h
+
diff --git a/client/xtreeview.h b/client/xtreeview.h new file mode 100644 index 0000000..b69eebb --- /dev/null +++ b/client/xtreeview.h @@ -0,0 +1,50 @@ +/* +Copyright (C) 2017 Srivats P. + +This file is part of "Ostinato" + +This is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see +*/ + +#ifndef _X_TREE_VIEW_H +#define _X_TREE_VIEW_H + +#include + +#include + +#if QT_VERSION >= 0x050000 +#error "Do we even need this anymore?" +#endif + +class XTreeView : public QTreeView +{ +public: + XTreeView(QWidget *parent) : QTreeView(parent) {} + virtual ~XTreeView() {} + +private: + virtual void mousePressEvent(QMouseEvent *event) + { + QModelIndex item = indexAt(event->pos()); + + if (!item.isValid()) + setCurrentIndex(QModelIndex()); + + QTreeView::mousePressEvent(event); + } +}; + +#endif + From 4d13ecf15d149ca3a3e84ce49805e49347e20893 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sat, 9 Sep 2017 18:53:58 +0530 Subject: [PATCH 26/49] UX: Add text hint about stream list Remove text about how to create streams from Apply text hint. Make hint text copy consistent across all hints --- client/portswindow.ui | 20 ++++++++++++++---- client/xtableview.h | 48 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 client/xtableview.h diff --git a/client/portswindow.ui b/client/portswindow.ui index 546e468..c15737e 100644 --- a/client/portswindow.ui +++ b/client/portswindow.ui @@ -54,7 +54,7 @@ <p><b>How to use Ostinato</b></p> <p>The port list on the left contains all the ports on which you can transmit packets.</p> <p>Ports belong to a port group. Make sure the Port Group has a <img src=":/icons/bullet_green.png"/> next to it, then double click the port group to show or hide the ports in the port group.</p> -<p>To generate packets, you need to create and configure packet streams. A stream is a sequence of one or more same or similar packets.</p> +<p>To generate packets, you need to create and configure packet streams. A stream is a sequence of one or more packets.</p> <p>To create a stream, select the port on which you want to send packets.</p> <hr/> <p>Don't see the port that you want (or any ports at all) inside the port group? <a href="http://ostinato.org/docs/faq">Get Help!</a></p> @@ -93,7 +93,7 @@ <p>You have selected a port group in the port list on the left.</p> <p>You can transmit packets on any of the ports within the port group.</p> <p>Make sure the port group has a <img src=":/icons/bullet_green.png"/> next to it and then double click the port group to show or hide the ports in the port group.</p> -<p>To generate packets, you need to create and configure packet streams. A stream is a sequence of one or more same or similar packets.</p> +<p>To generate packets, you need to create and configure packet streams. A stream is a sequence of one or more packets.</p> <p>To create a stream, select the port on which you want to send packets. </p>
@@ -152,7 +152,7 @@ - Right click in the blank area below to configure streams. Click Apply on the right to activate the changes + Configuration Changed? Click Apply on the right to activate the changes @@ -221,7 +221,7 @@
- + 0 @@ -231,6 +231,13 @@ Qt::ActionsContextMenu + + This is the stream list for the selected port + +A stream is a sequence of one or more packets + +Right-click to create a stream + QFrame::StyledPanel @@ -373,6 +380,11 @@ QTreeView
xtreeview.h
+ + XTableView + QTableView +
xtableview.h
+
diff --git a/client/xtableview.h b/client/xtableview.h new file mode 100644 index 0000000..296ec54 --- /dev/null +++ b/client/xtableview.h @@ -0,0 +1,48 @@ +/* +Copyright (C) 2017 Srivats P. + +This file is part of "Ostinato" + +This is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see +*/ + +#ifndef _X_TABLE_VIEW_H +#define _X_TABLE_VIEW_H + +#include + +#include + +class XTableView : public QTableView +{ +public: + XTableView(QWidget *parent) : QTableView(parent) {} + virtual ~XTableView() {} + +protected: + virtual void paintEvent(QPaintEvent *event) + { + if (!model()->hasChildren()) { + QPainter painter(viewport()); + style()->drawItemText(&painter, viewport()->rect(), + layoutDirection() | Qt::AlignCenter, palette(), + true, whatsThis(), QPalette::WindowText); + } + else + QTableView::paintEvent(event); + } +}; + +#endif + From cca2e94bb3cec2f4f9a052efc34e92f0647af60a Mon Sep 17 00:00:00 2001 From: Srivats P Date: Thu, 14 Sep 2017 20:38:13 +0530 Subject: [PATCH 27/49] Detect when port config has changed and needs to APPLY'd For now we prompt user to click Apply, in future we can use this to do a "Auto Apply" --- client/port.cpp | 30 ++++++++++++++++++++++++++++-- client/port.h | 29 ++++++++++++++++++++++++----- client/portswindow.cpp | 21 +++++++++++++++++++++ client/portswindow.h | 1 + client/portswindow.ui | 17 +++++++++++++++-- client/streamconfigdialog.cpp | 2 +- client/streammodel.cpp | 8 +++++--- common/streambase.cpp | 2 +- common/streambase.h | 2 +- 9 files changed, 97 insertions(+), 15 deletions(-) diff --git a/client/port.cpp b/client/port.cpp index 50f1716..bf245c7 100644 --- a/client/port.cpp +++ b/client/port.cpp @@ -57,6 +57,7 @@ Port::Port(quint32 id, quint32 portGroupId) stats.mutable_port_id()->set_id(id); mPortGroupId = portGroupId; capFile_ = NULL; + dirty_ = false; } Port::~Port() @@ -100,6 +101,15 @@ void Port::reorderStreamsByOrdinals() qSort(mStreams.begin(), mStreams.end(), StreamBase::StreamLessThan); } +void Port::setDirty(bool dirty) +{ + if (dirty == dirty_) + return; + + dirty_ = dirty; + emit localConfigChanged(dirty_); +} + void Port::recalculateAverageRates() { double pps = 0; @@ -209,6 +219,7 @@ void Port::setAveragePacketRate(double packetsPerSec) Q_ASSERT(false); // Unreachable!! } numActiveStreams_ = n; + setDirty(true); } else avgPacketsPerSec_ = avgBitsPerSec_ = numActiveStreams_ = 0; @@ -282,6 +293,7 @@ void Port::setAverageBitRate(double bitsPerSec) Q_ASSERT(false); // Unreachable!! } numActiveStreams_ = n; + setDirty(true); } else avgPacketsPerSec_ = avgBitsPerSec_ = numActiveStreams_ = 0; @@ -305,6 +317,7 @@ bool Port::newStreamAt(int index, OstProto::Stream const *stream) mStreams.insert(index, s); updateStreamOrdinalsFromIndex(); recalculateAverageRates(); + setDirty(true); return true; } @@ -317,6 +330,7 @@ bool Port::deleteStreamAt(int index) delete mStreams.takeAt(index); updateStreamOrdinalsFromIndex(); recalculateAverageRates(); + setDirty(true); return true; } @@ -506,6 +520,8 @@ void Port::when_syncComplete() deviceGroups_.at(i)->device_group_id().id()); } modifiedDeviceGroupList_.clear(); + + setDirty(false); } void Port::updateStats(OstProto::PortStats *portStats) @@ -543,6 +559,7 @@ void Port::duplicateStreams(const QList &list, int count) insertAt++; } } + setDirty(true); emit streamListChanged(mPortGroupId, mPortId); } @@ -625,6 +642,7 @@ bool Port::openStreams(QString fileName, bool append, QString &error) if (i % 32 == 0) qApp->processEvents(); } + setDirty(true); _user_cancel: emit streamListChanged(mPortGroupId, mPortId); @@ -743,11 +761,12 @@ OstProto::DeviceGroup* Port::mutableDeviceGroupByIndex(int index) // Caller can modify DeviceGroup - assume she will modifiedDeviceGroupList_.insert(devGrp->device_group_id().id()); + setDirty(true); return devGrp; } -OstProto::DeviceGroup* Port::deviceGroupById(uint deviceGroupId) +const OstProto::DeviceGroup* Port::deviceGroupById(uint deviceGroupId) const { for (int i = 0; i < deviceGroups_.size(); i++) { OstProto::DeviceGroup *devGrp = deviceGroups_.at(i); @@ -776,6 +795,7 @@ bool Port::newDeviceGroupAt(int index, const OstProto::DeviceGroup *deviceGroup) devGrp->mutable_device_group_id()->set_id(newDeviceGroupId()); deviceGroups_.insert(index, devGrp); modifiedDeviceGroupList_.insert(devGrp->device_group_id().id()); + setDirty(true); return true; } @@ -788,6 +808,7 @@ bool Port::deleteDeviceGroupAt(int index) OstProto::DeviceGroup *devGrp = deviceGroups_.takeAt(index); modifiedDeviceGroupList_.remove(devGrp->device_group_id().id()); delete devGrp; + setDirty(true); return true; } @@ -818,7 +839,12 @@ bool Port::updateDeviceGroup( uint deviceGroupId, OstProto::DeviceGroup *deviceGroup) { - OstProto::DeviceGroup *devGrp = deviceGroupById(deviceGroupId); + using OstProto::DeviceGroup; + + // XXX: We should not call mutableDeviceGroupById() because that will + // implicitly set the port as dirty, so we use a const_cast hack instead + DeviceGroup *devGrp = const_cast( + deviceGroupById(deviceGroupId)); if (!devGrp) { qDebug("%s: deviceGroup id %u does not exist", __FUNCTION__, diff --git a/client/port.h b/client/port.h index 281a61a..c1141f5 100644 --- a/client/port.h +++ b/client/port.h @@ -50,6 +50,7 @@ class Port : public QObject { quint32 mPortId; quint32 mPortGroupId; QString mUserAlias; // user defined + bool dirty_; double avgPacketsPerSec_; double avgBitsPerSec_; @@ -70,6 +71,7 @@ class Port : public QObject { void updateStreamOrdinalsFromIndex(); void reorderStreamsByOrdinals(); + void setDirty(bool dirty); public: enum AdminStatus { AdminDisable, AdminEnable }; @@ -108,11 +110,17 @@ public: //void setExclusive(bool flag); int numStreams() { return mStreams.size(); } - Stream* streamByIndex(int index) + const Stream* streamByIndex(int index) const { Q_ASSERT(index < mStreams.size()); return mStreams[index]; } + Stream* mutableStreamByIndex(int index) + { + Q_ASSERT(index < mStreams.size()); + setDirty(true); // assume - that's the best we can do atm + return mStreams[index]; + } OstProto::LinkState linkState() { return stats.state().link_state(); } @@ -129,6 +137,7 @@ public: void protoDataCopyInto(OstProto::Port *data); + //! Used when config received from server // FIXME(MED): naming inconsistency - PortConfig/Stream; also retVal void updatePortConfig(OstProto::Port *port); @@ -144,6 +153,7 @@ public: bool updateStream(uint streamId, OstProto::Stream *stream); //@} + bool isDirty() { return dirty_; } void getDeletedStreamsSinceLastSync(OstProto::StreamIdList &streamIdList); void getNewStreamsSinceLastSync(OstProto::StreamIdList &streamIdList); void getModifiedStreamsSinceLastSync( @@ -178,7 +188,7 @@ public: int numDeviceGroups() const; const OstProto::DeviceGroup* deviceGroupByIndex(int index) const; OstProto::DeviceGroup* mutableDeviceGroupByIndex(int index); - OstProto::DeviceGroup* deviceGroupById(uint deviceGroupId); + const OstProto::DeviceGroup* deviceGroupById(uint deviceGroupId) const; //! Used by StreamModel //@{ @@ -216,11 +226,20 @@ public: void deviceInfoRefreshed(); signals: + //! Used when local config changed and when config received from server void portRateChanged(int portGroupId, int portId); - void portDataChanged(int portGroupId, int portId); - void streamListChanged(int portGroupId, int portId); - void deviceInfoChanged(); + //! Used by MyService::Stub to update from config received from server + //@{ + void portDataChanged(int portGroupId, int portId); + void deviceInfoChanged(); + //@} + + //! Used when local config changed + //@{ + void streamListChanged(int portGroupId, int portId); + void localConfigChanged(bool changed); + //@} }; #endif diff --git a/client/portswindow.cpp b/client/portswindow.cpp index b3a31d5..51e4152 100644 --- a/client/portswindow.cpp +++ b/client/portswindow.cpp @@ -345,6 +345,8 @@ void PortsWindow::when_portView_currentChanged(const QModelIndex& currentIndex, { disconnect(&(plm->port(previous)), SIGNAL(portRateChanged(int, int)), this, SLOT(updatePortRates())); + disconnect(&(plm->port(previous)), SIGNAL(localConfigChanged(bool)), + this, SLOT(updateApplyHint(bool))); } if (!current.isValid()) @@ -364,6 +366,15 @@ void PortsWindow::when_portView_currentChanged(const QModelIndex& currentIndex, updatePortRates(); connect(&(plm->port(current)), SIGNAL(portRateChanged(int, int)), SLOT(updatePortRates())); + connect(&(plm->port(current)), SIGNAL(localConfigChanged(bool)), + SLOT(updateApplyHint(bool))); + if (plm->port(current).isDirty()) + updateApplyHint(true); + else if (plm->port(current).numStreams()) + applyHint->setText("Use the Statistics window to transmit " + "packets"); + else + applyHint->setText(""); } } @@ -511,6 +522,16 @@ void PortsWindow::updateStreamViewActions() actionSave_Streams->setEnabled(tvStreamList->model()->rowCount() > 0); } +void PortsWindow::updateApplyHint(bool configChanged) +{ + if (configChanged) + applyHint->setText("Configuration has changed - click Apply " + "to activate the changes"); + else + applyHint->setText("Configuration activated. Use the Statistics " + "window to transmit packets"); +} + void PortsWindow::updatePortViewActions(const QModelIndex& currentIndex) { QModelIndex current = currentIndex; diff --git a/client/portswindow.h b/client/portswindow.h index b407270..e67bcea 100644 --- a/client/portswindow.h +++ b/client/portswindow.h @@ -66,6 +66,7 @@ public slots: void showMyReservedPortsOnly(bool enabled); private slots: + void updateApplyHint(bool configChanged); void updatePortViewActions(const QModelIndex& currentIndex); void updateStreamViewActions(); diff --git a/client/portswindow.ui b/client/portswindow.ui index c15737e..f4f1c58 100644 --- a/client/portswindow.ui +++ b/client/portswindow.ui @@ -150,12 +150,25 @@
- + - Configuration Changed? Click Apply on the right to activate the changes + Apply Hint + + + + Qt::Horizontal + + + + 40 + 20 + + + + diff --git a/client/streamconfigdialog.cpp b/client/streamconfigdialog.cpp index 8db5b08..0ef56de 100644 --- a/client/streamconfigdialog.cpp +++ b/client/streamconfigdialog.cpp @@ -1222,7 +1222,7 @@ void StreamConfigDialog::on_pbOk_clicked() // Copy the data from the "local working copy of stream" to "actual stream" mpStream->protoDataCopyInto(s); - mPort.streamByIndex(mCurrentStreamIndex)->protoDataCopyFrom(s); + mPort.mutableStreamByIndex(mCurrentStreamIndex)->protoDataCopyFrom(s); qDebug("stream stored"); diff --git a/client/streammodel.cpp b/client/streammodel.cpp index c66f02c..99c12ef 100644 --- a/client/streammodel.cpp +++ b/client/streammodel.cpp @@ -155,19 +155,21 @@ bool StreamModel::setData(const QModelIndex &index, const QVariant &value, int r { // Edit Supported Fields case StreamName: - mCurrentPort->streamByIndex(index.row())->setName(value.toString()); + mCurrentPort->mutableStreamByIndex(index.row()) + ->setName(value.toString()); emit(dataChanged(index, index)); return true; case StreamStatus: - mCurrentPort->streamByIndex(index.row())->setEnabled(value.toBool()); + mCurrentPort->mutableStreamByIndex(index.row()) + ->setEnabled(value.toBool()); emit(dataChanged(index, index)); return true; case StreamNextWhat: if (role == Qt::EditRole) { - mCurrentPort->streamByIndex(index.row())->setNextWhat( + mCurrentPort->mutableStreamByIndex(index.row())->setNextWhat( (Stream::NextWhat)value.toInt()); emit(dataChanged(index, index)); return true; diff --git a/common/streambase.cpp b/common/streambase.cpp index 622e3d5..e3ac38d 100644 --- a/common/streambase.cpp +++ b/common/streambase.cpp @@ -145,7 +145,7 @@ ProtocolListIterator* StreamBase::createProtocolListIterator() const return new ProtocolListIterator(*currentFrameProtocols); } -quint32 StreamBase::id() +quint32 StreamBase::id() const { return mStreamId->id(); } diff --git a/common/streambase.h b/common/streambase.h index ca6e6be..6fe156b 100644 --- a/common/streambase.h +++ b/common/streambase.h @@ -68,7 +68,7 @@ public: e_nw_goto_id }; - quint32 id(); + quint32 id() const; bool setId(quint32 id); #if 0 // FIXME(HI): needed? From 3aa44343f03b8a8a56f20406b434b3bcc56cacb9 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Thu, 14 Sep 2017 21:01:56 +0530 Subject: [PATCH 28/49] Add new entry at end if nothing selected in stream/devGrp list --- client/deviceswidget.cpp | 8 ++++---- client/portswindow.cpp | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/deviceswidget.cpp b/client/deviceswidget.cpp index 7f84a2c..64bbbfe 100644 --- a/client/deviceswidget.cpp +++ b/client/deviceswidget.cpp @@ -170,13 +170,13 @@ void DevicesWidget::on_deviceInfo_toggled(bool checked) void DevicesWidget::on_actionNewDeviceGroup_triggered() { - // In case nothing is selected, insert 1 row at the top - int row = 0, count = 1; - QItemSelection selection = deviceGroupList->selectionModel()->selection(); - if (!portGroups_) return; + // In case nothing is selected, insert 1 row at the end + int row = portGroups_->getDeviceGroupModel()->rowCount(), count = 1; + QItemSelection selection = deviceGroupList->selectionModel()->selection(); + // In case we have a single range selected; insert as many rows as // in the singe selected range before the top of the selected range if (selection.size() == 1) { diff --git a/client/portswindow.cpp b/client/portswindow.cpp index 51e4152..551e438 100644 --- a/client/portswindow.cpp +++ b/client/portswindow.cpp @@ -769,8 +769,8 @@ void PortsWindow::on_actionNew_Stream_triggered() { qDebug("New Stream Action"); - // In case nothing is selected, insert 1 row at the top - int row = 0, count = 1; + // In case nothing is selected, insert 1 row at the end + int row = plm->getStreamModel()->rowCount(), count = 1; // In case we have a single range selected; insert as many rows as // in the singe selected range before the top of the selected range From db77563466adc732a447794216613a1b76eee112 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sat, 16 Sep 2017 12:12:43 +0530 Subject: [PATCH 29/49] UX: Change port name color if local config is changed Although the applyHint is also changed in this case, the applyHint is visible only when the port is selected. Having the port name in a different color is a visual hint to the user that Apply is pending --- client/port.cpp | 2 +- client/port.h | 2 +- client/portgroup.cpp | 2 ++ client/portmodel.cpp | 4 ++++ client/portswindow.cpp | 17 +++++++++++------ client/portswindow.h | 2 +- 6 files changed, 20 insertions(+), 9 deletions(-) diff --git a/client/port.cpp b/client/port.cpp index bf245c7..e9da599 100644 --- a/client/port.cpp +++ b/client/port.cpp @@ -107,7 +107,7 @@ void Port::setDirty(bool dirty) return; dirty_ = dirty; - emit localConfigChanged(dirty_); + emit localConfigChanged(mPortGroupId, mPortId, dirty_); } void Port::recalculateAverageRates() diff --git a/client/port.h b/client/port.h index c1141f5..c98b7ec 100644 --- a/client/port.h +++ b/client/port.h @@ -238,7 +238,7 @@ signals: //! Used when local config changed //@{ void streamListChanged(int portGroupId, int portId); - void localConfigChanged(bool changed); + void localConfigChanged(int portGroupId, int portId, bool changed); //@} }; diff --git a/client/portgroup.cpp b/client/portgroup.cpp index 8ebe1f5..b0af226 100644 --- a/client/portgroup.cpp +++ b/client/portgroup.cpp @@ -347,6 +347,8 @@ void PortGroup::processPortIdList(PbRpcController *controller) p = new Port(portIdList->port_id(i).id(), mPortGroupId); connect(p, SIGNAL(portDataChanged(int, int)), this, SIGNAL(portGroupDataChanged(int, int))); + connect(p, SIGNAL(localConfigChanged(int, int, bool)), + this, SIGNAL(portGroupDataChanged(int, int))); qDebug("before port append\n"); mPorts.append(p); atConnectPortConfig_.append(NULL); // will be filled later diff --git a/client/portmodel.cpp b/client/portmodel.cpp index cd1c9d7..e1e0f06 100644 --- a/client/portmodel.cpp +++ b/client/portmodel.cpp @@ -187,6 +187,10 @@ QVariant PortModel::data(const QModelIndex &index, int role) const { return portIconFactory[port->linkState()][port->hasExclusiveControl()]; } + else if ((role == Qt::ForegroundRole)) + { + return port->isDirty() ? QBrush(Qt::red) : QVariant(); + } else { DBG0("Exit PortModel data 6\n"); diff --git a/client/portswindow.cpp b/client/portswindow.cpp index 551e438..430352f 100644 --- a/client/portswindow.cpp +++ b/client/portswindow.cpp @@ -345,8 +345,10 @@ void PortsWindow::when_portView_currentChanged(const QModelIndex& currentIndex, { disconnect(&(plm->port(previous)), SIGNAL(portRateChanged(int, int)), this, SLOT(updatePortRates())); - disconnect(&(plm->port(previous)), SIGNAL(localConfigChanged(bool)), - this, SLOT(updateApplyHint(bool))); + disconnect(&(plm->port(previous)), + SIGNAL(localConfigChanged(int, int, bool)), + this, + SLOT(updateApplyHint(int, int, bool))); } if (!current.isValid()) @@ -366,10 +368,12 @@ void PortsWindow::when_portView_currentChanged(const QModelIndex& currentIndex, updatePortRates(); connect(&(plm->port(current)), SIGNAL(portRateChanged(int, int)), SLOT(updatePortRates())); - connect(&(plm->port(current)), SIGNAL(localConfigChanged(bool)), - SLOT(updateApplyHint(bool))); + connect(&(plm->port(current)), + SIGNAL(localConfigChanged(int, int, bool)), + SLOT(updateApplyHint(int, int, bool))); if (plm->port(current).isDirty()) - updateApplyHint(true); + updateApplyHint(plm->port(current).portGroupId(), + plm->port(current).id(), true); else if (plm->port(current).numStreams()) applyHint->setText("Use the Statistics window to transmit " "packets"); @@ -522,7 +526,8 @@ void PortsWindow::updateStreamViewActions() actionSave_Streams->setEnabled(tvStreamList->model()->rowCount() > 0); } -void PortsWindow::updateApplyHint(bool configChanged) +void PortsWindow::updateApplyHint(int /*portGroupId*/, int /*portId*/, + bool configChanged) { if (configChanged) applyHint->setText("Configuration has changed - click Apply " diff --git a/client/portswindow.h b/client/portswindow.h index e67bcea..f072cc7 100644 --- a/client/portswindow.h +++ b/client/portswindow.h @@ -66,7 +66,7 @@ public slots: void showMyReservedPortsOnly(bool enabled); private slots: - void updateApplyHint(bool configChanged); + void updateApplyHint(int portGroupId, int portId, bool configChanged); void updatePortViewActions(const QModelIndex& currentIndex); void updateStreamViewActions(); From 509e9d5398de1f230c5ec6b6611cd86dc7e2fadc Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sat, 16 Sep 2017 12:30:00 +0530 Subject: [PATCH 30/49] Change Stream Config Dialog inputs This is the first of many commits that lays the foundation for - * Editing multiple streams without exiting the dialog * Triggering the dialog when a new stream is added instead of add+edit --- client/port.h | 10 +++++----- client/portswindow.cpp | 5 ++++- client/streamconfigdialog.cpp | 17 ++++++++++++----- client/streamconfigdialog.h | 6 ++++-- 4 files changed, 25 insertions(+), 13 deletions(-) diff --git a/client/port.h b/client/port.h index c98b7ec..04c0feb 100644 --- a/client/port.h +++ b/client/port.h @@ -94,15 +94,15 @@ public: { return QString().fromStdString(d.notes()); } const QString userName() const { return QString().fromStdString(d.user_name()); } - AdminStatus adminStatus() + AdminStatus adminStatus() const { return (d.is_enabled()?AdminEnable:AdminDisable); } - bool hasExclusiveControl() + bool hasExclusiveControl() const { return d.is_exclusive_control(); } - OstProto::TransmitMode transmitMode() + OstProto::TransmitMode transmitMode() const { return d.transmit_mode(); } - double averagePacketRate() + double averagePacketRate() const { return avgPacketsPerSec_; } - double averageBitRate() + double averageBitRate() const { return avgBitsPerSec_; } //void setAdminEnable(AdminStatus status) { mAdminStatus = status; } diff --git a/client/portswindow.cpp b/client/portswindow.cpp index 430352f..3a1c911 100644 --- a/client/portswindow.cpp +++ b/client/portswindow.cpp @@ -314,7 +314,10 @@ void PortsWindow::on_tvStreamList_activated(const QModelIndex & index) qDebug("%s: invalid index", __FUNCTION__); return; } - scd = new StreamConfigDialog(plm->port(currentPort), index.row(), this); + + QList streams; + streams.append(plm->port(currentPort).mutableStreamByIndex(index.row())); + scd = new StreamConfigDialog(streams, plm->port(currentPort), this); qDebug("stream list activated\n"); ret = scd->exec(); diff --git a/client/streamconfigdialog.cpp b/client/streamconfigdialog.cpp index 0ef56de..8daa491 100644 --- a/client/streamconfigdialog.cpp +++ b/client/streamconfigdialog.cpp @@ -39,13 +39,20 @@ int StreamConfigDialog::lastProtocolDataIndex = 0; static const uint kEthFrameOverHead = 20; -StreamConfigDialog::StreamConfigDialog(Port &port, uint streamIndex, - QWidget *parent) : QDialog (parent), mPort(port) +StreamConfigDialog::StreamConfigDialog( + QList &streamList, + const Port &port, + QWidget *parent) + : QDialog (parent), streamList_(streamList), mPort(port) { OstProto::Stream s; - mCurrentStreamIndex = streamIndex; + mCurrentStreamIndex = 0; + + // FIXME: temporary till we support a list + Q_ASSERT(streamList_.size() == 1); + mpStream = new Stream; - mPort.streamByIndex(mCurrentStreamIndex)->protoDataCopyInto(s); + streamList_.at(mCurrentStreamIndex)->protoDataCopyInto(s); mpStream->protoDataCopyFrom(s); _iter = mpStream->createProtocolListIterator(); isUpdateInProgress = false; @@ -1222,7 +1229,7 @@ void StreamConfigDialog::on_pbOk_clicked() // Copy the data from the "local working copy of stream" to "actual stream" mpStream->protoDataCopyInto(s); - mPort.mutableStreamByIndex(mCurrentStreamIndex)->protoDataCopyFrom(s); + streamList_[mCurrentStreamIndex]->protoDataCopyFrom(s); qDebug("stream stored"); diff --git a/client/streamconfigdialog.h b/client/streamconfigdialog.h index 5330414..11f0b29 100644 --- a/client/streamconfigdialog.h +++ b/client/streamconfigdialog.h @@ -43,7 +43,8 @@ class StreamConfigDialog : public QDialog, public Ui::StreamConfigDialog { Q_OBJECT public: - StreamConfigDialog(Port &port, uint streamIndex, QWidget *parent = 0); + StreamConfigDialog(QList &streamList, const Port &port, + QWidget *parent = 0); ~StreamConfigDialog(); private: @@ -72,7 +73,8 @@ private: QStringListModel *mpAvailableProtocolsModel; QStringListModel *mpSelectedProtocolsModel; - Port& mPort; + QList streamList_; + const Port& mPort; uint mCurrentStreamIndex; Stream *mpStream; From 360fa13c977e9921e977fb03b8ed61cfc9360b44 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sun, 17 Sep 2017 13:18:44 +0530 Subject: [PATCH 31/49] UX: Edit multiple streams in the StreamConfigDialog The dialog now accepts a list of streams as input and has prev/next buttons to tranverse through the list making changes in one or more of the streams --- client/port.h | 9 ++- client/portswindow.cpp | 57 +++++++-------- client/streamconfigdialog.cpp | 129 +++++++++++++++++++++------------- client/streamconfigdialog.h | 4 +- 4 files changed, 121 insertions(+), 78 deletions(-) diff --git a/client/port.h b/client/port.h index 04c0feb..a515325 100644 --- a/client/port.h +++ b/client/port.h @@ -115,12 +115,17 @@ public: Q_ASSERT(index < mStreams.size()); return mStreams[index]; } - Stream* mutableStreamByIndex(int index) + Stream* mutableStreamByIndex(int index, bool assumeChange = true) { Q_ASSERT(index < mStreams.size()); - setDirty(true); // assume - that's the best we can do atm + if (assumeChange) + setDirty(true); return mStreams[index]; } + void setLocalConfigChanged(bool changed) + { + setDirty(changed); + } OstProto::LinkState linkState() { return stats.state().link_state(); } diff --git a/client/portswindow.cpp b/client/portswindow.cpp index 3a1c911..addec00 100644 --- a/client/portswindow.cpp +++ b/client/portswindow.cpp @@ -302,29 +302,26 @@ void PortsWindow::showMyReservedPortsOnly(bool enabled) void PortsWindow::on_tvStreamList_activated(const QModelIndex & index) { - QModelIndex currentPort = tvPortList->currentIndex(); - StreamConfigDialog *scd; - int ret; - - if (proxyPortModel) - currentPort = proxyPortModel->mapToSource(currentPort); - if (!index.isValid()) { qDebug("%s: invalid index", __FUNCTION__); return; } - QList streams; - streams.append(plm->port(currentPort).mutableStreamByIndex(index.row())); - scd = new StreamConfigDialog(streams, plm->port(currentPort), this); qDebug("stream list activated\n"); - ret = scd->exec(); - if (ret == QDialog::Accepted) - plm->port(currentPort).recalculateAverageRates(); + Port &curPort = plm->port(proxyPortModel ? + proxyPortModel->mapToSource(tvPortList->currentIndex()) : + tvPortList->currentIndex()); - delete scd; + QList streams; + streams.append(curPort.mutableStreamByIndex(index.row(), false)); + + StreamConfigDialog scd(streams, curPort, this); + if (scd.exec() == QDialog::Accepted) { + curPort.recalculateAverageRates(); + curPort.setLocalConfigChanged(true); + } } void PortsWindow::when_portView_currentChanged(const QModelIndex& currentIndex, @@ -493,21 +490,15 @@ void PortsWindow::updateStreamViewActions() tvStreamList->selectionModel()->selection().size()); // If more than one non-contiguous ranges selected, - // disable "New" and "Edit" + // disable "New" if (tvStreamList->selectionModel()->selection().size() > 1) { actionNew_Stream->setDisabled(true); - actionEdit_Stream->setDisabled(true); } else { actionNew_Stream->setEnabled(true); - - // Enable "Edit" only if the single range has a single row - if (tvStreamList->selectionModel()->selection().at(0).height() > 1) - actionEdit_Stream->setDisabled(true); - else - actionEdit_Stream->setEnabled(true); + actionEdit_Stream->setEnabled(true); } // Duplicate/Delete are always enabled as long as we have a selection @@ -795,12 +786,22 @@ void PortsWindow::on_actionEdit_Stream_triggered() { qDebug("Edit Stream Action"); - // Ensure we have only one range selected which contains only one row - if ((tvStreamList->selectionModel()->selection().size() == 1) && - (tvStreamList->selectionModel()->selection().at(0).height() == 1)) - { - on_tvStreamList_activated(tvStreamList->selectionModel()-> - selection().at(0).topLeft()); + QItemSelectionModel* streamModel = tvStreamList->selectionModel(); + if (!streamModel->hasSelection()) + return; + + Port &curPort = plm->port(proxyPortModel ? + proxyPortModel->mapToSource(tvPortList->currentIndex()) : + tvPortList->currentIndex()); + + QList streams; + foreach(QModelIndex index, streamModel->selectedRows()) + streams.append(curPort.mutableStreamByIndex(index.row(), false)); + + StreamConfigDialog scd(streams, curPort, this); + if (scd.exec() == QDialog::Accepted) { + curPort.recalculateAverageRates(); + curPort.setLocalConfigChanged(true); } } diff --git a/client/streamconfigdialog.cpp b/client/streamconfigdialog.cpp index 8daa491..e8db52e 100644 --- a/client/streamconfigdialog.cpp +++ b/client/streamconfigdialog.cpp @@ -43,17 +43,24 @@ StreamConfigDialog::StreamConfigDialog( QList &streamList, const Port &port, QWidget *parent) - : QDialog (parent), streamList_(streamList), mPort(port) + : QDialog (parent), _userStreamList(streamList), mPort(port) { - OstProto::Stream s; mCurrentStreamIndex = 0; - // FIXME: temporary till we support a list - Q_ASSERT(streamList_.size() == 1); + Q_ASSERT(_userStreamList.size() > 0); - mpStream = new Stream; - streamList_.at(mCurrentStreamIndex)->protoDataCopyInto(s); - mpStream->protoDataCopyFrom(s); + // Create a copy of the user provided stream list + // We need a copy because user may edit multiple streams and then + // discard the edit - in this case the user provided stream list + // should not be modified on return + foreach(Stream* stm, _userStreamList) { + OstProto::Stream s; + stm->protoDataCopyInto(s); + _streamList.append(new Stream()); + _streamList.last()->protoDataCopyFrom(s); + } + + mpStream = _streamList.at(mCurrentStreamIndex); _iter = mpStream->createProtocolListIterator(); isUpdateInProgress = false; @@ -158,8 +165,6 @@ StreamConfigDialog::StreamConfigDialog( this, SLOT(when_lvSelectedProtocols_currentChanged(const QModelIndex&, const QModelIndex&))); - variableFieldsWidget->setStream(mpStream); - LoadCurrentStream(); mpPacketModel = new PacketModel(this); tvPacketTree->setModel(mpPacketModel); @@ -172,10 +177,9 @@ StreamConfigDialog::StreamConfigDialog( vwPacketDump->setModel(mpPacketModel); vwPacketDump->setSelectionModel(tvPacketTree->selectionModel()); - // TODO(MED): - //! \todo Enable navigation of streams - pbPrev->setHidden(true); - pbNext->setHidden(true); + pbPrev->setDisabled(mCurrentStreamIndex == 0); + pbNext->setDisabled(int(mCurrentStreamIndex) == (_streamList.size()-1)); + //! \todo Support Goto Stream Id leStreamId->setHidden(true); disconnect(rbActionGotoStream, SIGNAL(toggled(bool)), leStreamId, SLOT(setEnabled(bool))); @@ -325,7 +329,8 @@ StreamConfigDialog::~StreamConfigDialog() } delete _iter; - delete mpStream; + while (!_streamList.isEmpty()) + delete _streamList.takeFirst(); } void StreamConfigDialog::loadProtocolWidgets() @@ -406,30 +411,6 @@ void StreamConfigDialog::on_cmbPktLenMode_currentIndexChanged(QString mode) } } -void StreamConfigDialog::on_pbPrev_clicked() -{ -#if 0 - StoreCurrentStream(currStreamIdx); - currStreamIdx--; - LoadCurrentStream(currStreamIdx); - - pbPrev->setDisabled((currStreamIdx == 0)); - pbNext->setDisabled((currStreamIdx == 2)); -#endif -} - -void StreamConfigDialog::on_pbNext_clicked() -{ -#if 0 - StoreCurrentStream(currStreamIdx); - currStreamIdx++; - LoadCurrentStream(currStreamIdx); - - pbPrev->setDisabled((currStreamIdx == 0)); - pbNext->setDisabled((currStreamIdx == 2)); -#endif -} - void StreamConfigDialog::on_tbSelectProtocols_currentChanged(int index) { qDebug("%s, index = %d", __FUNCTION__, index); @@ -967,6 +948,7 @@ void StreamConfigDialog::LoadCurrentStream() QString str; qDebug("loading mpStream %p", mpStream); + variableFieldsWidget->setStream(mpStream); // Meta Data { @@ -986,6 +968,7 @@ void StreamConfigDialog::LoadCurrentStream() // Variable Fields { + variableFieldsWidget->clear(); variableFieldsWidget->load(); } @@ -1202,13 +1185,9 @@ void StreamConfigDialog::on_leBitsPerSec_textEdited(const QString &text) } } -void StreamConfigDialog::on_pbOk_clicked() +bool StreamConfigDialog::isCurrentStreamValid() { QString log; - OstProto::Stream s; - - // Store dialog contents into stream - StoreCurrentStream(); if ((mPort.transmitMode() == OstProto::kInterleavedTransmit) && (mpStream->isFrameVariable())) @@ -1224,12 +1203,68 @@ void StreamConfigDialog::on_pbOk_clicked() if (QMessageBox::warning(this, "Preflight Check", log + "\nContinue?", QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::No) - return; + return false; } - // Copy the data from the "local working copy of stream" to "actual stream" - mpStream->protoDataCopyInto(s); - streamList_[mCurrentStreamIndex]->protoDataCopyFrom(s); + return true; +} + +void StreamConfigDialog::on_pbPrev_clicked() +{ + Q_ASSERT(mCurrentStreamIndex > 0); + + StoreCurrentStream(); + + if (!isCurrentStreamValid()) + return; + + delete _iter; + mpStream = _streamList.at(--mCurrentStreamIndex); + _iter = mpStream->createProtocolListIterator(); + + LoadCurrentStream(); + on_twTopLevel_currentChanged(twTopLevel->currentIndex()); + + pbPrev->setDisabled(mCurrentStreamIndex == 0); + pbNext->setDisabled(int(mCurrentStreamIndex) == (_streamList.size()-1)); +} + +void StreamConfigDialog::on_pbNext_clicked() +{ + Q_ASSERT(int(mCurrentStreamIndex) < (_streamList.size()-1)); + + StoreCurrentStream(); + + if (!isCurrentStreamValid()) + return; + + delete _iter; + mpStream = _streamList.at(++mCurrentStreamIndex); + _iter = mpStream->createProtocolListIterator(); + + LoadCurrentStream(); + on_twTopLevel_currentChanged(twTopLevel->currentIndex()); + + pbPrev->setDisabled(mCurrentStreamIndex == 0); + pbNext->setDisabled(int(mCurrentStreamIndex) == (_streamList.size()-1)); + +} + +void StreamConfigDialog::on_pbOk_clicked() +{ + // Store dialog contents into current stream + StoreCurrentStream(); + + if (!isCurrentStreamValid()) + return; + + // Copy the working copy of streams to user provided streams + Q_ASSERT(_userStreamList.size() == _streamList.size()); + for (int i = 0; i < _streamList.size(); i++) { + OstProto::Stream s; + _streamList.at(i)->protoDataCopyInto(s); + _userStreamList[i]->protoDataCopyFrom(s); + } qDebug("stream stored"); diff --git a/client/streamconfigdialog.h b/client/streamconfigdialog.h index 11f0b29..cecb9cc 100644 --- a/client/streamconfigdialog.h +++ b/client/streamconfigdialog.h @@ -73,7 +73,8 @@ private: QStringListModel *mpAvailableProtocolsModel; QStringListModel *mpSelectedProtocolsModel; - QList streamList_; + QList _userStreamList; + QList _streamList; const Port& mPort; uint mCurrentStreamIndex; @@ -94,6 +95,7 @@ private: static int lastProtocolDataIndex; void setupUiExtra(); + bool isCurrentStreamValid(); void LoadCurrentStream(); void StoreCurrentStream(); void loadProtocolWidgets(); From fd243f6847473a8965209f014053a8f4a59af634 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Wed, 20 Sep 2017 22:00:47 +0530 Subject: [PATCH 32/49] UX: Open StreamConfigDialog on add stream Add+Edit is now reduced to single step. For new users, it is easier to comprehend this single step and no text hint is needed for edit --- client/portswindow.cpp | 29 ++++++++++++++++++++++++----- client/streammodel.cpp | 24 ++++++++++++++++++++++++ client/streammodel.h | 2 ++ 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/client/portswindow.cpp b/client/portswindow.cpp index addec00..216f04c 100644 --- a/client/portswindow.cpp +++ b/client/portswindow.cpp @@ -768,18 +768,37 @@ void PortsWindow::on_actionNew_Stream_triggered() { qDebug("New Stream Action"); + QItemSelectionModel* selectionModel = tvStreamList->selectionModel(); + if (selectionModel->selection().size() > 1) { + qDebug("%s: Unexpected selection size %d, can't add", + selectionModel->selection().size()); + return; + } + // In case nothing is selected, insert 1 row at the end - int row = plm->getStreamModel()->rowCount(), count = 1; + StreamModel *streamModel = plm->getStreamModel(); + int row = streamModel->rowCount(), count = 1; // In case we have a single range selected; insert as many rows as // in the singe selected range before the top of the selected range - if (tvStreamList->selectionModel()->selection().size() == 1) + if (selectionModel->selection().size() == 1) { - row = tvStreamList->selectionModel()->selection().at(0).top(); - count = tvStreamList->selectionModel()->selection().at(0).height(); + row = selectionModel->selection().at(0).top(); + count = selectionModel->selection().at(0).height(); } - plm->getStreamModel()->insertRows(row, count); + Port &curPort = plm->port(proxyPortModel ? + proxyPortModel->mapToSource(tvPortList->currentIndex()) : + tvPortList->currentIndex()); + + QList streams; + for (int i = 0; i < count; i++) + streams.append(new Stream); + + StreamConfigDialog scd(streams, curPort, this); + scd.setWindowTitle(tr("Add Stream(s)")); + if (scd.exec() == QDialog::Accepted) + streamModel->insert(row, streams); } void PortsWindow::on_actionEdit_Stream_triggered() diff --git a/client/streammodel.cpp b/client/streammodel.cpp index 99c12ef..a97acde 100644 --- a/client/streammodel.cpp +++ b/client/streammodel.cpp @@ -223,6 +223,30 @@ QVariant StreamModel::headerData(int section, Qt::Orientation orientation, int r return QVariant(); } +/*! + * Inserts streams before the given row + * + * StreamModel takes ownership of the passed streams; caller should + * not try to access them after calling this function + */ +bool StreamModel::insert(int row, QList &streams) +{ + int count = streams.size(); + qDebug("insert row = %d", row); + qDebug("insert count = %d", count); + beginInsertRows(QModelIndex(), row, row+count-1); + for (int i = 0; i < count; i++) { + OstProto::Stream s; + streams.at(i)->protoDataCopyInto(s); + mCurrentPort->newStreamAt(row+i, &s); + delete streams.at(i); + } + streams.clear(); + endInsertRows(); + + return true; +} + bool StreamModel::insertRows(int row, int count, const QModelIndex &/*parent*/) { qDebug("insertRows() row = %d", row); diff --git a/client/streammodel.h b/client/streammodel.h index d559618..57cdb97 100644 --- a/client/streammodel.h +++ b/client/streammodel.h @@ -25,6 +25,7 @@ along with this program. If not, see #include "port.h" class PortGroupList; +class Stream; class StreamModel : public QAbstractTableModel { @@ -44,6 +45,7 @@ class StreamModel : public QAbstractTableModel int role = Qt::EditRole); QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + bool insert(int row, QList &streams); bool insertRows (int row, int count, const QModelIndex & parent = QModelIndex()); bool removeRows (int row, int count, From f62a3be54a03666a3462e64b7183324596ec5633 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Wed, 20 Sep 2017 22:05:13 +0530 Subject: [PATCH 33/49] UX: Don't allow edit of non-contiguous streams Allowing this has potential of confusion for user when using Prev/Next --- client/portswindow.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/portswindow.cpp b/client/portswindow.cpp index 216f04c..3ee7f41 100644 --- a/client/portswindow.cpp +++ b/client/portswindow.cpp @@ -490,10 +490,11 @@ void PortsWindow::updateStreamViewActions() tvStreamList->selectionModel()->selection().size()); // If more than one non-contiguous ranges selected, - // disable "New" + // disable "New" and "Edit" if (tvStreamList->selectionModel()->selection().size() > 1) { actionNew_Stream->setDisabled(true); + actionEdit_Stream->setDisabled(true); } else { From fd8db1cf151aabbe1710a203535234c7345d713b Mon Sep 17 00:00:00 2001 From: Srivats P Date: Thu, 21 Sep 2017 20:43:54 +0530 Subject: [PATCH 34/49] UX: Add text hints to Devices Widget --- client/deviceswidget.ui | 126 +++++++++++++++++++++++----------------- 1 file changed, 72 insertions(+), 54 deletions(-) diff --git a/client/deviceswidget.ui b/client/deviceswidget.ui index 14d8d7e..2e09917 100644 --- a/client/deviceswidget.ui +++ b/client/deviceswidget.ui @@ -1,7 +1,8 @@ - + + DevicesWidget - - + + 0 0 @@ -9,47 +10,38 @@ 328 - + Form - - - 0 - - - 0 - - - 0 - - + + 0 - + - - + + Configuration - + true - - + + Information - + Qt::Horizontal - + 131 23 @@ -58,90 +50,116 @@ - - + + Refresh information - + Refresh device and neighbor information - + - - :/icons/refresh.png + + + :/icons/refresh.png:/icons/refresh.png - - + + Qt::ActionsContextMenu - + + This is the device group list for the selected port + +A device group is a set of one or more devices/hosts which will be emulated by Ostinato + +Right-click to create/edit a device group + + QFrame::StyledPanel - + 1 - + QAbstractItemView::ExtendedSelection - + QAbstractItemView::SelectRows - - - + + + 0 1 - + + No devices being emulated + +To emulate a device, click on Configuration and create a device group + + QAbstractItemView::SelectRows - - + + + IP neighbor cache is empty + + QAbstractItemView::SingleSelection - - - :/icons/devicegroup_add.png + + + + :/icons/devicegroup_add.png:/icons/devicegroup_add.png - + New Device Group - - - :/icons/devicegroup_delete.png + + + + :/icons/devicegroup_delete.png:/icons/devicegroup_delete.png - + Delete Device Group - - - :/icons/devicegroup_edit.png + + + + :/icons/devicegroup_edit.png:/icons/devicegroup_edit.png - + Edit Device Group + + + XTableView + QTableView +
xtableview.h
+
+
- +
From 1047f0b1d8e2c317d312956f61b5fb5cf862b197 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Thu, 21 Sep 2017 20:50:24 +0530 Subject: [PATCH 35/49] Fix warning --- client/portswindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/portswindow.cpp b/client/portswindow.cpp index 3ee7f41..2fa93bc 100644 --- a/client/portswindow.cpp +++ b/client/portswindow.cpp @@ -771,7 +771,7 @@ void PortsWindow::on_actionNew_Stream_triggered() QItemSelectionModel* selectionModel = tvStreamList->selectionModel(); if (selectionModel->selection().size() > 1) { - qDebug("%s: Unexpected selection size %d, can't add", + qDebug("%s: Unexpected selection size %d, can't add", __FUNCTION__, selectionModel->selection().size()); return; } From 7d4f285d8d7545c4291cebb630f50ab42f9c2286 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Thu, 21 Sep 2017 21:11:34 +0530 Subject: [PATCH 36/49] UX: Tweak welcome message --- client/portswindow.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/portswindow.ui b/client/portswindow.ui index f4f1c58..f364eac 100644 --- a/client/portswindow.ui +++ b/client/portswindow.ui @@ -51,7 +51,7 @@ - <p><b>How to use Ostinato</b></p> + <p><b>Welcome to Ostinato</b></p> <p>The port list on the left contains all the ports on which you can transmit packets.</p> <p>Ports belong to a port group. Make sure the Port Group has a <img src=":/icons/bullet_green.png"/> next to it, then double click the port group to show or hide the ports in the port group.</p> <p>To generate packets, you need to create and configure packet streams. A stream is a sequence of one or more packets.</p> From 6bd687e2bc2d9997903d978ac8ebcc6aed9624be Mon Sep 17 00:00:00 2001 From: Srivats P Date: Fri, 22 Sep 2017 20:45:42 +0530 Subject: [PATCH 37/49] UX: Enhance StreamConfigDialog * Add stream name/enabled fields to dialog * Change Dialog title to include current stream name --- client/portswindow.cpp | 2 +- client/streamconfigdialog.cpp | 16 + client/streamconfigdialog.h | 3 + client/streamconfigdialog.ui | 922 +++++++++++++++++----------------- 4 files changed, 492 insertions(+), 451 deletions(-) diff --git a/client/portswindow.cpp b/client/portswindow.cpp index 2fa93bc..d47d2d7 100644 --- a/client/portswindow.cpp +++ b/client/portswindow.cpp @@ -797,7 +797,7 @@ void PortsWindow::on_actionNew_Stream_triggered() streams.append(new Stream); StreamConfigDialog scd(streams, curPort, this); - scd.setWindowTitle(tr("Add Stream(s)")); + scd.setWindowTitle(tr("Add Stream")); if (scd.exec() == QDialog::Accepted) streamModel->insert(row, streams); } diff --git a/client/streamconfigdialog.cpp b/client/streamconfigdialog.cpp index e8db52e..7c4bcd8 100644 --- a/client/streamconfigdialog.cpp +++ b/client/streamconfigdialog.cpp @@ -67,6 +67,8 @@ StreamConfigDialog::StreamConfigDialog( setupUi(this); setupUiExtra(); + _windowTitle = windowTitle(); + for (int i = ProtoMin; i < ProtoMax; i++) { bgProto[i]->setProperty("ProtocolLevel", i); @@ -333,6 +335,12 @@ StreamConfigDialog::~StreamConfigDialog() delete _streamList.takeFirst(); } +void StreamConfigDialog::setWindowTitle(const QString &title) +{ + _windowTitle = title; + QDialog::setWindowTitle(title); +} + void StreamConfigDialog::loadProtocolWidgets() { ProtocolListIterator *iter; @@ -950,8 +958,14 @@ void StreamConfigDialog::LoadCurrentStream() qDebug("loading mpStream %p", mpStream); variableFieldsWidget->setStream(mpStream); + QDialog::setWindowTitle(QString("%1 [%2]").arg(_windowTitle) + .arg(mpStream->name().isEmpty() ? + tr("") : mpStream->name())); + // Meta Data { + name->setText(mpStream->name()); + enabled->setChecked(mpStream->isEnabled()); cmbPktLenMode->setCurrentIndex(mpStream->lenMode()); lePktLen->setText(str.setNum(mpStream->frameLen())); lePktLenMin->setText(str.setNum(mpStream->frameLenMin())); @@ -1037,6 +1051,8 @@ void StreamConfigDialog::StoreCurrentStream() qDebug("storing pStream %p", pStream); // Meta Data + pStream->setName(name->text()); + pStream->setEnabled(enabled->isChecked()); pStream->setLenMode((Stream::FrameLengthMode) cmbPktLenMode->currentIndex()); pStream->setFrameLen(lePktLen->text().toULong(&isOk)); pStream->setFrameLenMin(lePktLenMin->text().toULong(&isOk)); diff --git a/client/streamconfigdialog.h b/client/streamconfigdialog.h index cecb9cc..62c2fdd 100644 --- a/client/streamconfigdialog.h +++ b/client/streamconfigdialog.h @@ -47,6 +47,8 @@ public: QWidget *parent = 0); ~StreamConfigDialog(); + void setWindowTitle(const QString &title); + private: enum ButtonId @@ -76,6 +78,7 @@ private: QList _userStreamList; QList _streamList; const Port& mPort; + QString _windowTitle; uint mCurrentStreamIndex; Stream *mpStream; diff --git a/client/streamconfigdialog.ui b/client/streamconfigdialog.ui index eacf894..2c02119 100644 --- a/client/streamconfigdialog.ui +++ b/client/streamconfigdialog.ui @@ -1,10 +1,11 @@ - + + StreamConfigDialog - - + + Qt::ApplicationModal - + 0 0 @@ -12,123 +13,140 @@ 507 - - + + 0 0 - + Edit Stream - - :/icons/stream_edit.png + + + :/icons/stream_edit.png:/icons/stream_edit.png - - QLineEdit:enabled[inputMask = "HH; "], -QLineEdit:enabled[inputMask = "HH HH; "], -QLineEdit:enabled[inputMask = "HH HH HH; "], -QLineEdit:enabled[inputMask = "HH HH HH HH; "], -QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff } + + QLineEdit:enabled[inputMask = "HH; "], +QLineEdit:enabled[inputMask = "HH HH; "], +QLineEdit:enabled[inputMask = "HH HH HH; "], +QLineEdit:enabled[inputMask = "HH HH HH HH; "], +QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff } - + true - - - - + + + + - + 0 - - + + Protocol Selection - - - - - Qt::Horizontal + + + + + Basics - - - 241 - 20 - - - + + + + + Name + + + name + + + + + + + + + + Enabled + + + + + - - - + + + Frame Length (including FCS) - - - + + + - + Fixed - + Increment - + Decrement - + Random - - - + + + Min - - - + + + false - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - + + + Max - - - + + + false - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -136,13 +154,13 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - - - + + + 0 - - + + 0 0 @@ -150,42 +168,42 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff 269 - + Simple - - - - + + + + L1 - + - - + + None - + true - - + + Mac - + false - - + + false - + Other @@ -193,65 +211,65 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - - - + + + true - + L2 - + - - + + None - + true - - + + Ethernet II - + false - - + + 802.3 Raw - - + + 802.3 LLC - + false - - + + 802.3 LLC SNAP - - + + false - + Other @@ -259,116 +277,116 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - - - + + + true - + L3 - - - - + + + + None - + true - - - + + + false - + ARP - - - + + + false - + IPv4 - + false - - - + + + false - + IPv6 - - - + + + false - + IP 6over4 - + false - - - + + + false - + IP 4over6 - + false - - - + + + false - + IP 4over4 - + false - - - + + + false - + IP 6over6 - + false - - - + + + false - + Other @@ -376,41 +394,41 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - - - + + + true - + L5 - + - - + + None - + true - - + + false - + Text - - + + false - + Other @@ -418,41 +436,41 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - - - + + + true - + VLAN - + false - + false - + - - + + Untagged - + true - - + + Tagged - - + + Stacked @@ -460,81 +478,81 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - - - + + + true - + L4 - - - - + + + + None - + true - - - + + + false - + ICMP - - - + + + false - + IGMP - - - + + + false - + TCP - - - + + + false - + UDP - - - + + + false - + Other - - - + + + false - + MLD @@ -542,48 +560,48 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - - - + + + true - + Payload - + - - + + None - + true - - + + Pattern - + false - - + + Hex Dump - - + + false - + Other @@ -593,8 +611,8 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - - + + 0 0 @@ -602,28 +620,28 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff 135 - + Advanced - + - + - - + + Available Protocols - - + + true - + QAbstractItemView::ExtendedSelection - + QAbstractItemView::SelectRows @@ -631,13 +649,13 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - + - + Qt::Vertical - + 20 40 @@ -646,24 +664,25 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - - + + false - - > + + > - - :/icons/arrow_right.png + + + :/icons/arrow_right.png:/icons/arrow_right.png - + Qt::Vertical - + 20 40 @@ -674,61 +693,64 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - + - - + + Selected Protocols - + - - + + false - + ^ - - :/icons/arrow_up.png + + + :/icons/arrow_up.png:/icons/arrow_up.png - - + + false - + v - - :/icons/arrow_down.png + + + :/icons/arrow_down.png:/icons/arrow_down.png - - + + false - + - - - :/icons/delete.png + + + :/icons/delete.png:/icons/delete.png - + Qt::Horizontal - + 40 20 @@ -739,8 +761,8 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - - + + QAbstractItemView::SelectRows @@ -753,54 +775,54 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - - + + Protocol Data - + - - + + -1 - - + + Variable Fields - + - + - - + + Stream Control - - - - + + + + Send - + - - + + Packets - + true - - + + Bursts @@ -808,71 +830,71 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - - - + + + Numbers - + - - + + Number of Packets - + leNumPackets - - + + - + - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + Number of Bursts - + leNumBursts - - + + false - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + Packets per Burst - + lePacketsPerBurst - - + + false - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -880,68 +902,68 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - - - + + + Rate - + false - + false - + - - + + Packets/Sec - + true - - + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + false - + Bursts/Sec - - + + false - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + Bits/Sec - - + + false - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -949,42 +971,42 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - - - + + + After this stream - + - - + + Stop - - + + Goto Next Stream - + true - - + + Goto First - - + + false - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -992,12 +1014,12 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - + - + Qt::Horizontal - + 20 41 @@ -1005,25 +1027,25 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - - - + + + Mode - + - - + + Fixed - + true - - + + Continuous @@ -1031,87 +1053,87 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - - - + + + true - + Gaps (in seconds) - + false - + false - - - - + + + + - - :/icons/gaps.png + + :/icons/gaps.png - - - + + + ISG - + leGapIsg - - - + + + false - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - + + + IBG - + leGapIbg - - - + + + false - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - + + + IPG - + leGapIpg - - - + + + false - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -1119,12 +1141,12 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - + - + Qt::Vertical - + 153 21 @@ -1134,56 +1156,56 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - - + + Packet View - + - - + + Qt::Vertical - - + + QAbstractItemView::SelectItems - + QAbstractItemView::ScrollPerPixel - + true - + - - + + - - + + Prev - - + + Next - + Qt::Horizontal - + 191 20 @@ -1192,18 +1214,18 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - - + + OK - + true - - + + Cancel @@ -1298,7 +1320,7 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff pbCancel - + @@ -1307,11 +1329,11 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff StreamConfigDialog reject() - + 623 496 - + 533 466 @@ -1323,11 +1345,11 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff leStreamId setEnabled(bool) - + 463 143 - + 463 177 @@ -1339,11 +1361,11 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff rbPacketsPerSec setEnabled(bool) - + 30 68 - + 299 82 @@ -1355,11 +1377,11 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff rbBurstsPerSec setEnabled(bool) - + 30 95 - + 299 132 @@ -1371,11 +1393,11 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff lePacketsPerBurst setEnabled(bool) - + 30 95 - + 134 189 @@ -1387,11 +1409,11 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff lePacketsPerSec setEnabled(bool) - + 299 82 - + 299 108 @@ -1403,11 +1425,11 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff leBurstsPerSec setEnabled(bool) - + 299 132 - + 299 158 @@ -1419,11 +1441,11 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff leBitsPerSec setEnabled(bool) - + 299 182 - + 299 208 @@ -1435,11 +1457,11 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff rbPacketsPerSec setChecked(bool) - + 95 70 - + 299 82 @@ -1451,11 +1473,11 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff rbBurstsPerSec setChecked(bool) - + 96 98 - + 299 132 @@ -1467,11 +1489,11 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff leNumPackets setDisabled(bool) - + 73 196 - + 164 108 @@ -1483,11 +1505,11 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff leNumBursts setDisabled(bool) - + 96 199 - + 226 155 From b2291eb1c091b7698968b310518213bc0d53524b Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sat, 23 Sep 2017 16:47:02 +0530 Subject: [PATCH 38/49] Tweak text for Port Group has no ports --- client/portgroup.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/client/portgroup.cpp b/client/portgroup.cpp index b0af226..c609d3f 100644 --- a/client/portgroup.cpp +++ b/client/portgroup.cpp @@ -305,20 +305,20 @@ void PortGroup::when_portListChanged(quint32 /*portGroupId*/) QString faq("http://ostinato.org/docs/faq#q-port-group-has-no-interfaces"); if (state() == QAbstractSocket::ConnectedState && numPorts() <= 0) { - if (QMessageBox::warning(NULL, tr("No ports in portgroup"), - QString("The portgroup %1:%2 does not contain any ports!\n\n" - "Packet Transmit/Capture requires elevated privileges. " - "Please ensure that you are running 'drone' - the server " - "component of Ostinato with admin/root OR setuid privilege.\n\n" - "For help see the Ostinato FAQ (%3)") + QMessageBox msgBox; + msgBox.setIcon(QMessageBox::Warning); + msgBox.setTextFormat(Qt::RichText); + msgBox.setStyleSheet("messagebox-text-interaction-flags: 5"); + QString msg = tr("

The portgroup %1:%2 does not contain any ports!

" + "

Packet Transmit/Capture requires special privileges. " + "Please ensure that you are running 'drone' - the agent " + "component of Ostinato with required privileges.

") .arg(serverName()) - .arg(int(serverPort())) - .arg(faq.remove(QRegExp("#.*$"))), - QMessageBox::Ok | QMessageBox::Help, - QMessageBox::Ok) == QMessageBox::Help) - { - QDesktopServices::openUrl(QUrl(faq)); - } + .arg(int(serverPort())); + msgBox.setText(msg); + msgBox.setInformativeText(tr("See the Ostinato FAQ " + "for instructions to fix this problem").arg(faq)); + msgBox.exec(); } } From d32253b699910e6aec639c33dedda82e90d742ca Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sat, 23 Sep 2017 17:28:35 +0530 Subject: [PATCH 39/49] Make URL in new Version status message clickable Additionally this message is now permanently visible won't be replaced or obscured by other (temporary) messages --- client/mainwindow.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/client/mainwindow.cpp b/client/mainwindow.cpp index 2db7670..1a54dbe 100644 --- a/client/mainwindow.cpp +++ b/client/mainwindow.cpp @@ -364,8 +364,11 @@ void MainWindow::onLocalServerError(QProcess::ProcessError error) void MainWindow::onNewVersion(QString newVersion) { - statusBar()->showMessage(QString("New Ostinato version %1 available. " - "Visit http://ostinato.org to download").arg(newVersion)); + QLabel *msg = new QLabel(tr("New Ostinato version %1 available. Visit " + "ostinato.org to download") + .arg(newVersion)); + msg->setOpenExternalLinks(true); + statusBar()->addPermanentWidget(msg); } //! Returns true on success (or user cancel) and false on failure From ce7f9d808ff64d560ea4968e78aed108e2c55fe3 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sat, 23 Sep 2017 18:53:12 +0530 Subject: [PATCH 40/49] Fix missing MsgBox on packet.dll missing started is emitted before the missing dll is detected, so user a timer to disconnect the signal-slot connection --- client/mainwindow.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/mainwindow.cpp b/client/mainwindow.cpp index 1a54dbe..1afc8d8 100644 --- a/client/mainwindow.cpp +++ b/client/mainwindow.cpp @@ -41,6 +41,7 @@ along with this program. If not, see #include #include #include +#include #include #ifdef Q_OS_WIN32 @@ -75,11 +76,11 @@ MainWindow::MainWindow(QWidget *parent) qDebug("staring local server - %s", qPrintable(serverApp)); localServer_ = new QProcess(this); - connect(localServer_, SIGNAL(started()), SLOT(onLocalServerStarted())); connect(localServer_, SIGNAL(error(QProcess::ProcessError)), SLOT(onLocalServerError(QProcess::ProcessError))); localServer_->setProcessChannelMode(QProcess::ForwardedChannels); localServer_->start(serverApp, QStringList()); + QTimer::singleShot(5000, this, SLOT(onLocalServerStarted())); } else localServer_ = NULL; From de04acec984c3f4b4ce5a4dd36b3be1b254d155e Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sat, 23 Sep 2017 18:54:42 +0530 Subject: [PATCH 41/49] Tweak error message when local drone start fails --- client/mainwindow.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/client/mainwindow.cpp b/client/mainwindow.cpp index 1afc8d8..a3a9aa7 100644 --- a/client/mainwindow.cpp +++ b/client/mainwindow.cpp @@ -345,6 +345,11 @@ void MainWindow::onLocalServerError(QProcess::ProcessError error) "error 0x%1, exit code 0x%2.

") .arg(error, 0, 16) .arg(localServer_->exitCode(), 0, 16); + if (error == QProcess::FailedToStart) + errorStr.append(tr("

The drone program does not exist at %1 or you " + "don't have sufficient permissions to execute it." + "

") + .arg(QCoreApplication::applicationDirPath())); #ifdef Q_OS_WIN32 if (localServer_->exitCode() == STATUS_DLL_NOT_FOUND) errorStr.append(tr("

This is most likely because Packet.dll " @@ -353,14 +358,17 @@ void MainWindow::onLocalServerError(QProcess::ProcessError error) "installed.

")); #endif msgBox.setText(errorStr); - msgBox.setInformativeText(tr("Run drone directly for more information.")); + msgBox.setInformativeText(tr("Try running drone directly.")); msgBox.exec(); + QString archUrl("https://userguide.ostinato.org/Architecture.html"); QMessageBox::information(this, QString(), tr("

If you have remote drone agents running, you can still add " "and connect to them.

" "

If you don't want to start the local drone agent at startup, " - "provide the -s option to Ostinato on the command line

")); + "provide the -s option to Ostinato on the command line.

" + "

Learn about Ostinato's Controller-Agent " + "architecture

").arg(archUrl)); } void MainWindow::onNewVersion(QString newVersion) From f01bdd257fa6617ef403562331ca9342988b49c1 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Mon, 25 Sep 2017 18:19:45 +0530 Subject: [PATCH 42/49] Inform user that Ostinato TCP is stateless --- common/tcp.cpp | 2 +- common/tcp.ui | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/common/tcp.cpp b/common/tcp.cpp index 3714a5d..58ebfac 100644 --- a/common/tcp.cpp +++ b/common/tcp.cpp @@ -55,7 +55,7 @@ void TcpProtocol::protoDataCopyFrom(const OstProto::Protocol &protocol) QString TcpProtocol::name() const { - return QString("Transmission Control Protocol"); + return QString("Transmission Control Protocol (state less)"); } QString TcpProtocol::shortName() const diff --git a/common/tcp.ui b/common/tcp.ui index 6f3eee8..97673f7 100644 --- a/common/tcp.ui +++ b/common/tcp.ui @@ -196,6 +196,29 @@
+ + + + <i>Note: Ostinato is stateless- it cannot establish a TCP connection and generate seq/ack numbers accordingly</i> + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + +
From a2b349e5b78bc7d76c8d1a67da8bb7aa5c54ac1e Mon Sep 17 00:00:00 2001 From: Srivats P Date: Mon, 25 Sep 2017 21:26:57 +0530 Subject: [PATCH 43/49] Inform user about Drone version incompatibility --- client/portgroup.cpp | 13 +++++++++++++ server/myservice.cpp | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/client/portgroup.cpp b/client/portgroup.cpp index c609d3f..404e942 100644 --- a/client/portgroup.cpp +++ b/client/portgroup.cpp @@ -200,6 +200,19 @@ void PortGroup::processVersionCompatibility(PbRpcController *controller) qPrintable(QString::fromStdString(verCompat->notes()))); compat = kIncompatible; emit portGroupDataChanged(mPortGroupId); + + QMessageBox msgBox; + msgBox.setIcon(QMessageBox::Warning); + msgBox.setTextFormat(Qt::RichText); + msgBox.setStyleSheet("messagebox-text-interaction-flags: 5"); + msgBox.setText(tr("The Drone agent at %1:%2 is incompatible with this " + "Ostinato version - %3") + .arg(serverName()) + .arg(int(serverPort())) + .arg(version)); + msgBox.setInformativeText(QString::fromStdString(verCompat->notes())); + msgBox.exec(); + goto _error_exit; } diff --git a/server/myservice.cpp b/server/myservice.cpp index 6ba4f15..0784830 100644 --- a/server/myservice.cpp +++ b/server/myservice.cpp @@ -590,7 +590,7 @@ void MyService::checkVersion(::google::protobuf::RpcController* controller, } else { response->set_result(OstProto::VersionCompatibility::kIncompatible); - response->set_notes(QString("Drone needs client version %1.%2.x") + response->set_notes(QString("Drone needs controller version %1.%2.x") .arg(my[0], my[1]).toStdString()); static_cast(controller)->TriggerDisconnect(); } From 57e8fe7236ce44420f578e87db2702e4bcec10c5 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Tue, 26 Sep 2017 19:59:49 +0530 Subject: [PATCH 44/49] UX: Report more drone startup errors * Changed drone exit code from -1 to 1 'coz typically exit codes are between 0 and 255 * Detect and report drone TCP port bind failure * In all the following drone errors are reported (including previous commits) - * Drone already running (TCP port bind failure) * Drone executable not found * Packet.dll not found (Win32 only) * The following conditions should NOT be reported * Start/Stop Ostinato * Stop before 5sec * Stop after 5sec --- client/mainwindow.cpp | 34 ++++++++++++++++++++++++++++------ client/mainwindow.h | 4 +++- server/drone_main.cpp | 2 +- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/client/mainwindow.cpp b/client/mainwindow.cpp index a3a9aa7..c437d28 100644 --- a/client/mainwindow.cpp +++ b/client/mainwindow.cpp @@ -76,11 +76,13 @@ MainWindow::MainWindow(QWidget *parent) qDebug("staring local server - %s", qPrintable(serverApp)); localServer_ = new QProcess(this); + connect(localServer_, SIGNAL(finished(int, QProcess::ExitStatus)), + SLOT(onLocalServerFinished(int, QProcess::ExitStatus))); connect(localServer_, SIGNAL(error(QProcess::ProcessError)), SLOT(onLocalServerError(QProcess::ProcessError))); localServer_->setProcessChannelMode(QProcess::ForwardedChannels); localServer_->start(serverApp, QStringList()); - QTimer::singleShot(5000, this, SLOT(onLocalServerStarted())); + QTimer::singleShot(5000, this, SLOT(stopLocalServerMonitor())); } else localServer_ = NULL; @@ -151,6 +153,7 @@ MainWindow::MainWindow(QWidget *parent) MainWindow::~MainWindow() { + stopLocalServerMonitor(); if (localServer_) { #ifdef Q_OS_WIN32 //! \todo - find a way to terminate cleanly @@ -328,28 +331,47 @@ void MainWindow::on_actionHelpAbout_triggered() delete aboutDialog; } -void MainWindow::onLocalServerStarted() +void MainWindow::stopLocalServerMonitor() { // We are only interested in startup errors disconnect(localServer_, SIGNAL(error(QProcess::ProcessError)), this, SLOT(onLocalServerError(QProcess::ProcessError))); + disconnect(localServer_, SIGNAL(finished(int, QProcess::ExitStatus)), + this, SLOT(onLocalServerFinished(int, QProcess::ExitStatus))); } -void MainWindow::onLocalServerError(QProcess::ProcessError error) +void MainWindow::onLocalServerFinished(int exitCode, + QProcess::ExitStatus /*exitStatus*/) +{ + if (exitCode) + reportLocalServerError(); +} + +void MainWindow::onLocalServerError(QProcess::ProcessError /*error*/) +{ + reportLocalServerError(); +} + +void MainWindow::reportLocalServerError() { QMessageBox msgBox(this); msgBox.setIcon(QMessageBox::Warning); msgBox.setTextFormat(Qt::RichText); msgBox.setStyleSheet("messagebox-text-interaction-flags: 5"); // mouse copy QString errorStr = tr("

Failed to start the local drone agent - " - "error 0x%1, exit code 0x%2.

") - .arg(error, 0, 16) + "error 0x%1, exit status 0x%2 exit code 0x%3.

") + .arg(localServer_->error(), 0, 16) + .arg(localServer_->exitStatus(), 0, 16) .arg(localServer_->exitCode(), 0, 16); - if (error == QProcess::FailedToStart) + if (localServer_->error() == QProcess::FailedToStart) errorStr.append(tr("

The drone program does not exist at %1 or you " "don't have sufficient permissions to execute it." "

") .arg(QCoreApplication::applicationDirPath())); + if (localServer_->exitCode() == 1) + errorStr.append(tr("

The drone program was not able to bind to " + "TCP port 7878 - maybe a drone process is already " + "running?

")); #ifdef Q_OS_WIN32 if (localServer_->exitCode() == STATUS_DLL_NOT_FOUND) errorStr.append(tr("

This is most likely because Packet.dll " diff --git a/client/mainwindow.h b/client/mainwindow.h index eb63079..8f1f22b 100644 --- a/client/mainwindow.h +++ b/client/mainwindow.h @@ -60,8 +60,10 @@ public slots: void on_actionHelpAbout_triggered(); private slots: - void onLocalServerStarted(); + void stopLocalServerMonitor(); + void onLocalServerFinished(int exitCode, QProcess::ExitStatus exitStatus); void onLocalServerError(QProcess::ProcessError error); + void reportLocalServerError(); void onNewVersion(QString version); }; diff --git a/server/drone_main.cpp b/server/drone_main.cpp index f80e62d..4ce6c7b 100644 --- a/server/drone_main.cpp +++ b/server/drone_main.cpp @@ -77,7 +77,7 @@ int main(int argc, char *argv[]) if (!drone->init()) { - exitCode = -1; + exitCode = 1; goto _exit; } From a757b9e35394fbd94f55170c872d7c3cd4891914 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sat, 30 Sep 2017 19:32:27 +0530 Subject: [PATCH 45/49] UX: Warn for very short duration stream transmits Changed the default num_packets from 1 to 10, because otherwise the default stream values will trigger this warning, which is not a good experience for the user --- common/protocol.proto | 2 +- common/streambase.cpp | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/common/protocol.proto b/common/protocol.proto index 7697a9c..e7ad676 100644 --- a/common/protocol.proto +++ b/common/protocol.proto @@ -78,7 +78,7 @@ message StreamControl { optional SendUnit unit = 1 [default = e_su_packets]; optional SendMode mode = 2 [default = e_sm_fixed]; - optional uint32 num_packets = 3 [default = 1]; + optional uint32 num_packets = 3 [default = 10]; optional uint32 num_bursts = 4 [default = 1]; optional uint32 packets_per_burst = 5 [default = 10]; optional NextWhat next = 6 [default = e_nw_goto_next]; diff --git a/common/streambase.cpp b/common/streambase.cpp index e3ac38d..7327ea5 100644 --- a/common/streambase.cpp +++ b/common/streambase.cpp @@ -609,6 +609,20 @@ bool StreamBase::preflightCheck(QString &result) const } } + if (frameCount() <= averagePacketRate() && nextWhat() != e_nw_goto_id) + { + result += QObject::tr("Only %L1 frames at the rate of " + "%L2 frames/sec are configured to be transmitted - " + "transmission will last for only %L3 second " + "(if you wish to transmit for a longer duration, " + "increase the number of bursts/packets and/or set the " + "'After this stream' action as 'Goto First').\n") + .arg(frameCount()) + .arg(averagePacketRate(), 0, 'f', 2) + .arg(frameCount()/averagePacketRate(), 0, 'f'); + pass = false; + } + return pass; } From c53a1866b8b029d2258f743c465265107d90aeae Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sat, 30 Sep 2017 22:17:06 +0530 Subject: [PATCH 46/49] UX: Show stream check results as a bulleted list --- client/streamconfigdialog.cpp | 16 +++++++++++----- common/streambase.cpp | 22 +++++++++++----------- common/streambase.h | 2 +- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/client/streamconfigdialog.cpp b/client/streamconfigdialog.cpp index 7c4bcd8..92efe06 100644 --- a/client/streamconfigdialog.cpp +++ b/client/streamconfigdialog.cpp @@ -1203,20 +1203,26 @@ void StreamConfigDialog::on_leBitsPerSec_textEdited(const QString &text) bool StreamConfigDialog::isCurrentStreamValid() { - QString log; + QStringList log; if ((mPort.transmitMode() == OstProto::kInterleavedTransmit) && (mpStream->isFrameVariable())) { - log += "In 'Interleaved Streams' transmit mode, the count for " - "varying fields at transmit time may not be same as configured\n"; + log << tr("In 'Interleaved Streams' transmit mode, the count for " + "varying fields at transmit time may not be same as configured"); } mpStream->preflightCheck(log); - if (log.length()) + if (log.size()) { - if (QMessageBox::warning(this, "Preflight Check", log + "\nContinue?", + if (QMessageBox::warning(this, "Preflight Check", + tr("

We found possible problems with this stream -

") + + "
    " + + log.replaceInStrings(QRegExp("(.*)"), "
  • \\1
  • ") + .join("\n") + + "
" + + tr("

Ignore?

"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::No) return false; diff --git a/common/streambase.cpp b/common/streambase.cpp index 7327ea5..345b7e7 100644 --- a/common/streambase.cpp +++ b/common/streambase.cpp @@ -581,7 +581,7 @@ quint64 StreamBase::neighborMacAddress(int frameIndex) const return getNeighborMacAddress(portId_, int(mStreamId->id()), frameIndex); } -bool StreamBase::preflightCheck(QString &result) const +bool StreamBase::preflightCheck(QStringList &result) const { bool pass = true; int count = isFrameSizeVariable() ? frameCount() : 1; @@ -590,8 +590,8 @@ bool StreamBase::preflightCheck(QString &result) const { if (frameLen(i) < (frameProtocolLength(i) + kFcsSize)) { - result += QString("One or more frames may be truncated - " - "frame length should be at least %1.\n") + result << QObject::tr("One or more frames may be truncated - " + "frame length should be at least %1") .arg(frameProtocolLength(i) + kFcsSize); pass = false; break; @@ -602,8 +602,8 @@ bool StreamBase::preflightCheck(QString &result) const { if (frameLen(i) > 1522) { - result += QString("Jumbo frames may be truncated or dropped " - "if not supported by the hardware\n"); + result << QObject::tr("Jumbo frames may be truncated or dropped " + "if not supported by the hardware"); pass = false; break; } @@ -611,12 +611,12 @@ bool StreamBase::preflightCheck(QString &result) const if (frameCount() <= averagePacketRate() && nextWhat() != e_nw_goto_id) { - result += QObject::tr("Only %L1 frames at the rate of " - "%L2 frames/sec are configured to be transmitted - " - "transmission will last for only %L3 second " - "(if you wish to transmit for a longer duration, " - "increase the number of bursts/packets and/or set the " - "'After this stream' action as 'Goto First').\n") + result << QObject::tr("Only %L1 frames at the rate of " + "%L2 frames/sec are configured to be transmitted. " + "Transmission will last for only %L3 second - " + "to transmit for a longer duration, " + "increase the number of packets (bursts) and/or " + "set the 'After this stream' action as 'Goto First'") .arg(frameCount()) .arg(averagePacketRate(), 0, 'f', 2) .arg(frameCount()/averagePacketRate(), 0, 'f'); diff --git a/common/streambase.h b/common/streambase.h index 6fe156b..5e8ed7c 100644 --- a/common/streambase.h +++ b/common/streambase.h @@ -140,7 +140,7 @@ public: quint64 deviceMacAddress(int frameIndex) const; quint64 neighborMacAddress(int frameIndex) const; - bool preflightCheck(QString &result) const; + bool preflightCheck(QStringList &result) const; static bool StreamLessThan(StreamBase* stream1, StreamBase* stream2); From f9b83763bdf703994b7d742c6f820d08f8da147f Mon Sep 17 00:00:00 2001 From: Srivats P Date: Thu, 5 Oct 2017 20:04:23 +0530 Subject: [PATCH 47/49] UX: Change apply hint color to red Changing the color to be same as the port name color shows that these are related --- client/portswindow.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/portswindow.cpp b/client/portswindow.cpp index d47d2d7..e46ce02 100644 --- a/client/portswindow.cpp +++ b/client/portswindow.cpp @@ -525,7 +525,8 @@ void PortsWindow::updateApplyHint(int /*portGroupId*/, int /*portId*/, bool configChanged) { if (configChanged) - applyHint->setText("Configuration has changed - click Apply " + applyHint->setText("Configuration has changed - " + "click Apply " "to activate the changes"); else applyHint->setText("Configuration activated. Use the Statistics " From b3fb053ccaeff9e93755fa9ead8b4907d1b87efa Mon Sep 17 00:00:00 2001 From: Srivats P Date: Tue, 10 Oct 2017 21:36:06 +0530 Subject: [PATCH 48/49] Change all URLs to jump URLs --- client/jumpurl.h | 38 ++++++++++++++++++++++++++++++++++++++ client/mainwindow.cpp | 17 +++++++++-------- client/portgroup.cpp | 4 ++-- client/portswindow.ui | 2 +- 4 files changed, 50 insertions(+), 11 deletions(-) create mode 100644 client/jumpurl.h diff --git a/client/jumpurl.h b/client/jumpurl.h new file mode 100644 index 0000000..d91a3be --- /dev/null +++ b/client/jumpurl.h @@ -0,0 +1,38 @@ +/* +Copyright (C) 2017 Srivats P. + +This file is part of "Ostinato" + +This is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see +*/ + +#ifndef _JUMP_URL_H +#define _JUMP_URL_H + +#include + +inline QString jumpUrl( + QString keyword, + QString source="app", + QString medium="hint", + QString name="help") +{ + return QString("http://jump.ostinato.org/" + keyword + "?" + + "utm_source=" + source + "&" + + "utm_medium=" + medium + "&" + + "utm_campaign=" + name); +} + +#endif + diff --git a/client/mainwindow.cpp b/client/mainwindow.cpp index c437d28..5d8c93d 100644 --- a/client/mainwindow.cpp +++ b/client/mainwindow.cpp @@ -23,6 +23,7 @@ along with this program. If not, see #include "dbgthread.h" #endif +#include "jumpurl.h" #include "params.h" #include "portgrouplist.h" #include "portstatswindow.h" @@ -313,8 +314,7 @@ void MainWindow::on_actionViewRestoreDefaults_triggered() void MainWindow::on_actionHelpOnline_triggered() { - QDesktopServices::openUrl( - QUrl("http://ostinato.org/docs/?utm_source=app&utm_medium=menu&utm_campaign=help")); + QDesktopServices::openUrl(QUrl(jumpUrl("help", "app", "menu"))); } void MainWindow::on_actionHelpAbout_triggered() @@ -376,28 +376,29 @@ void MainWindow::reportLocalServerError() if (localServer_->exitCode() == STATUS_DLL_NOT_FOUND) errorStr.append(tr("

This is most likely because Packet.dll " "was not found - make sure you have " - "WinPcap " - "installed.

")); + "WinPcap" + " installed.

") + .arg(jumpUrl("winpcap"))); #endif msgBox.setText(errorStr); msgBox.setInformativeText(tr("Try running drone directly.")); msgBox.exec(); - QString archUrl("https://userguide.ostinato.org/Architecture.html"); QMessageBox::information(this, QString(), tr("

If you have remote drone agents running, you can still add " "and connect to them.

" "

If you don't want to start the local drone agent at startup, " "provide the -s option to Ostinato on the command line.

" "

Learn about Ostinato's Controller-Agent " - "architecture

").arg(archUrl)); + "architecture

").arg(jumpUrl("arch"))); } void MainWindow::onNewVersion(QString newVersion) { QLabel *msg = new QLabel(tr("New Ostinato version %1 available. Visit " - "ostinato.org to download") - .arg(newVersion)); + "ostinato.org to download") + .arg(newVersion) + .arg(jumpUrl("download", "app", "status", "update"))); msg->setOpenExternalLinks(true); statusBar()->addPermanentWidget(msg); } diff --git a/client/portgroup.cpp b/client/portgroup.cpp index 404e942..42620ae 100644 --- a/client/portgroup.cpp +++ b/client/portgroup.cpp @@ -19,6 +19,7 @@ along with this program. If not, see #include "portgroup.h" +#include "jumpurl.h" #include "settings.h" #include "emulproto.pb.h" @@ -315,7 +316,6 @@ void PortGroup::on_rpcChannel_notification(int notifType, void PortGroup::when_portListChanged(quint32 /*portGroupId*/) { - QString faq("http://ostinato.org/docs/faq#q-port-group-has-no-interfaces"); if (state() == QAbstractSocket::ConnectedState && numPorts() <= 0) { QMessageBox msgBox; @@ -330,7 +330,7 @@ void PortGroup::when_portListChanged(quint32 /*portGroupId*/) .arg(int(serverPort())); msgBox.setText(msg); msgBox.setInformativeText(tr("See the Ostinato FAQ " - "for instructions to fix this problem").arg(faq)); + "for instructions to fix this problem").arg(jumpUrl("noports"))); msgBox.exec(); } } diff --git a/client/portswindow.ui b/client/portswindow.ui index f364eac..d6de594 100644 --- a/client/portswindow.ui +++ b/client/portswindow.ui @@ -57,7 +57,7 @@ <p>To generate packets, you need to create and configure packet streams. A stream is a sequence of one or more packets.</p> <p>To create a stream, select the port on which you want to send packets.</p> <hr/> -<p>Don't see the port that you want (or any ports at all) inside the port group? <a href="http://ostinato.org/docs/faq">Get Help!</a></p>
+<p>Don't see the port that you want (or any ports at all) inside the port group? <a href="http://jump.ostinato.org/noports">Get Help!</a></p>
Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop From e05fa5a6902ecbc365e750f4f01964a150cb85d8 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Tue, 24 Oct 2017 18:21:56 +0530 Subject: [PATCH 49/49] UI improvements/fixes for Mac protocol * Change count/step to IntEdit with suitable minimums * Change mac address to MacEdit Fixes #224 fixes #188 --- common/mac.cpp | 10 ++- common/mac.ui | 165 +++++++++++++++++++------------------------ common/macconfig.cpp | 51 +++++++------ 3 files changed, 100 insertions(+), 126 deletions(-) diff --git a/common/mac.cpp b/common/mac.cpp index c1daf43..7d540b7 100644 --- a/common/mac.cpp +++ b/common/mac.cpp @@ -278,16 +278,14 @@ bool MacProtocol::setFieldData(int index, const QVariant &value, { case mac_dstAddr: { - quint64 mac = value.toString().toULongLong(&isOk, BASE_HEX); - if (isOk) - data.set_dst_mac(mac); + quint64 mac = value.toULongLong(); + data.set_dst_mac(mac); break; } case mac_srcAddr: { - quint64 mac = value.toString().toULongLong(&isOk, BASE_HEX); - if (isOk) - data.set_src_mac(mac); + quint64 mac = value.toULongLong(); + data.set_src_mac(mac); break; } diff --git a/common/mac.ui b/common/mac.ui index f3604bb..dfa8247 100644 --- a/common/mac.ui +++ b/common/mac.ui @@ -1,7 +1,8 @@ - + + mac - - + + 0 0 @@ -9,191 +10,157 @@ 200 - + Form - - - - + + + + Address - - - + + + Mode - - - + + + Count - - - + + + Step - - - + + + Destination - - - + + + 120 0 - - >HH HH HH HH HH HH; - - - - - - + + - + Fixed - + Increment - + Decrement - + Resolve - - - + + + false - - - - - 0 - - - - + + + false - - - - - 0 - - - - + + + Source - - - - >HH HH HH HH HH HH; - - - - - + + - - + + - + Fixed - + Increment - + Decrement - + Resolve - - - + + + false - - - - - - + + + false - - - - - 0 - - - - + + + Please ensure that a corresponding device is configured on the port to enable source/destination mac address resolution. A corresponding device is one which has VLANs and source/gateway IP corresponding to this stream. - + true - + - + Qt::Vertical - + 20 40 @@ -203,6 +170,18 @@ + + + IntEdit + QSpinBox +
intedit.h
+
+ + MacEdit + QLineEdit +
macedit.h
+
+
diff --git a/common/macconfig.cpp b/common/macconfig.cpp index 1e50854..1b01284 100644 --- a/common/macconfig.cpp +++ b/common/macconfig.cpp @@ -20,12 +20,9 @@ along with this program. If not, see #include "macconfig.h" #include "mac.h" -#define MAX_MAC_ITER_COUNT 256 - MacConfigForm::MacConfigForm(QWidget *parent) : AbstractProtocolConfigForm(parent) { - QRegExp reMac("([0-9,a-f,A-F]{2,2}[:-]){5,5}[0-9,a-f,A-F]{2,2}"); setupUi(this); resolveInfo->hide(); @@ -34,10 +31,10 @@ MacConfigForm::MacConfigForm(QWidget *parent) resolveInfo->setPixmap(resolveInfo->style()->standardIcon( QStyle::SP_MessageBoxInformation).pixmap(128)); #endif - leDstMac->setValidator(new QRegExpValidator(reMac, this)); - leSrcMac->setValidator(new QRegExpValidator(reMac, this)); - leDstMacCount->setValidator(new QIntValidator(1, MAX_MAC_ITER_COUNT, this)); - leSrcMacCount->setValidator(new QIntValidator(1, MAX_MAC_ITER_COUNT, this)); + leDstMacCount->setMinimum(1); + leSrcMacCount->setMinimum(1); + leDstMacStep->setMinimum(0); + leSrcMacStep->setMinimum(0); } MacConfigForm::~MacConfigForm() @@ -100,75 +97,75 @@ void MacConfigForm::on_cmbSrcMacMode_currentIndexChanged(int index) void MacConfigForm::loadWidget(AbstractProtocol *proto) { - leDstMac->setText( + leDstMac->setValue( proto->fieldData( MacProtocol::mac_dstAddr, - AbstractProtocol::FieldTextValue - ).toString()); + AbstractProtocol::FieldValue + ).toULongLong()); cmbDstMacMode->setCurrentIndex( proto->fieldData( MacProtocol::mac_dstMacMode, AbstractProtocol::FieldValue ).toUInt()); - leDstMacCount->setText( + leDstMacCount->setValue( proto->fieldData( MacProtocol::mac_dstMacCount, AbstractProtocol::FieldValue - ).toString()); - leDstMacStep->setText( + ).toUInt()); + leDstMacStep->setValue( proto->fieldData( MacProtocol::mac_dstMacStep, AbstractProtocol::FieldValue - ).toString()); + ).toUInt()); - leSrcMac->setText( + leSrcMac->setValue( proto->fieldData( MacProtocol::mac_srcAddr, - AbstractProtocol::FieldTextValue - ).toString()); + AbstractProtocol::FieldValue + ).toULongLong()); cmbSrcMacMode->setCurrentIndex( proto->fieldData( MacProtocol::mac_srcMacMode, AbstractProtocol::FieldValue ).toUInt()); - leSrcMacCount->setText( + leSrcMacCount->setValue( proto->fieldData( MacProtocol::mac_srcMacCount, AbstractProtocol::FieldValue - ).toString()); - leSrcMacStep->setText( + ).toUInt()); + leSrcMacStep->setValue( proto->fieldData( MacProtocol::mac_srcMacStep, AbstractProtocol::FieldValue - ).toString()); + ).toUInt()); } void MacConfigForm::storeWidget(AbstractProtocol *proto) { proto->setFieldData( MacProtocol::mac_dstAddr, - leDstMac->text().remove(QChar(' '))); + leDstMac->value()); proto->setFieldData( MacProtocol::mac_dstMacMode, cmbDstMacMode->currentIndex()); proto->setFieldData( MacProtocol::mac_dstMacCount, - leDstMacCount->text()); + leDstMacCount->value()); proto->setFieldData( MacProtocol::mac_dstMacStep, - leDstMacStep->text()); + leDstMacStep->value()); proto->setFieldData( MacProtocol::mac_srcAddr, - leSrcMac->text().remove(QChar(' '))); + leSrcMac->value()); proto->setFieldData( MacProtocol::mac_srcMacMode, cmbSrcMacMode->currentIndex()); proto->setFieldData( MacProtocol::mac_srcMacCount, - leSrcMacCount->text()); + leSrcMacCount->value()); proto->setFieldData( MacProtocol::mac_srcMacStep, - leSrcMacStep->text()); + leSrcMacStep->value()); }