Commit 3658bdac authored by hjk's avatar hjk
Browse files

Debugger: Use primitive internal widget instead of matplotview



This practically removes any functionality beyond plain plot display,
but does that at least reliably, cross-platform, without dependency
on 3rd party python packages.

Change-Id: Iaff2f78595394522f32264c642df20dd48b83f8b
Reviewed-by: default avatarChristian Stenger <christian.stenger@theqtcompany.com>
parent 3e82dcad
......@@ -36,13 +36,6 @@ import re
import time
import importlib
try:
import subprocess
hasSubprocess = True
except:
hasSubprocess = False
hasPlot = False
if sys.version_info[0] >= 3:
xrange = range
toInteger = int
......@@ -105,81 +98,53 @@ WatchpointAtExpression, \
BreakpointOnQmlSignalEmit, \
BreakpointAtJavaScriptThrow, \
= range(0, 14)
#
# matplot based display for array-like structures.
#
try:
import matplotlib
hasPlot = True
except:
hasPlot = False
if hasSubprocess and hasPlot:
matplotFigure = {}
matplotCount = 0
matplotProc = None
devNull = None
def matplotInit():
global matplotProc
global devNull
if matplotProc is None:
devNull = open(os.devnull)
# FIXME: That might not be the one we want.
pythonExecutable = sys.executable
matplotProc = subprocess.Popen(args=[pythonExecutable, "-i"],
bufsize=0, stdin=subprocess.PIPE, stdout=devNull, stderr=devNull)
matplotProc.stdin.write(b"import sys\n")
matplotProc.stdin.write(b"sys.ps1=''\n")
matplotProc.stdin.write(b"from matplotlib import pyplot\n")
matplotProc.stdin.write(b"import time\n")
matplotProc.stdin.write(b"pyplot.ion()\n")
matplotProc.stdin.flush()
def matplotSend(iname, show, data):
global matplotFigure
global matplotCount
matplotInit()
def s(line):
matplotProc.stdin.write(line.encode("latin1"))
matplotProc.stdin.write(b"\n")
sys.stdout.flush()
matplotProc.stdin.flush()
if show:
s("pyplot.ion()")
if not iname in matplotFigure:
matplotCount += 1
matplotFigure[iname] = matplotCount
s("pyplot.figure(%s)" % matplotFigure[iname])
s("pyplot.suptitle('%s')" % iname)
s("data = %s" % data)
s("pyplot.plot([i for i in range(len(data))], data, 'b.-')")
time.sleep(0.2)
s("pyplot.draw()")
matplotProc.stdin.flush()
else:
if iname in matplotFigure:
s("pyplot.figure(%s)" % matplotFigure[iname])
s("pyplot.close()")
del matplotFigure[iname]
matplotProc.stdin.flush()
# Encodings. Keep that synchronized with DebuggerEncoding in debuggerprotocol.h
Unencoded8Bit, \
Base64Encoded8BitWithQuotes, \
Base64Encoded16BitWithQuotes, \
Base64Encoded32BitWithQuotes, \
Base64Encoded16Bit, \
Base64Encoded8Bit, \
Hex2EncodedLatin1, \
Hex4EncodedLittleEndian, \
Hex8EncodedLittleEndian, \
Hex2EncodedUtf8, \
Hex8EncodedBigEndian, \
Hex4EncodedBigEndian, \
Hex4EncodedLittleEndianWithoutQuotes, \
Hex2EncodedLocal8Bit, \
JulianDate, \
MillisecondsSinceMidnight, \
JulianDateAndMillisecondsSinceMidnight, \
Hex2EncodedInt1, \
Hex2EncodedInt2, \
Hex2EncodedInt4, \
Hex2EncodedInt8, \
Hex2EncodedUInt1, \
Hex2EncodedUInt2, \
Hex2EncodedUInt4, \
Hex2EncodedUInt8, \
Hex2EncodedFloat4, \
Hex2EncodedFloat8, \
IPv6AddressAndHexScopeId, \
Hex2EncodedUtf8WithoutQuotes, \
DateTimeInternal \
= range(30)
# Display modes. Keep that synchronized with DebuggerDisplay in watchutils.h
StopDisplay, \
DisplayImageData, \
DisplayUtf16String, \
DisplayImageFile, \
DisplayLatin1String, \
DisplayUtf8String, \
DisplayPlotData \
= range(7)
def matplotQuit():
global matplotProc
if not matplotProc is None:
matplotProc.stdin.write(b"exit")
matplotProc.kill()
devNull.close()
def arrayForms():
global hasPlot
return [ArrayPlotFormat] if hasPlot else []
return [ArrayPlotFormat]
def mapForms():
return [CompactMapFormat]
......@@ -612,10 +577,7 @@ class DumperBase:
self.putNumChild(0)
self.putValue(mem, encodingType, elided=elided)
if displayFormat == Latin1StringFormat \
or displayFormat == Utf8StringFormat:
self.putDisplay(StopDisplay)
elif displayFormat == SeparateLatin1StringFormat \
if displayFormat == SeparateLatin1StringFormat \
or displayFormat == SeparateUtf8StringFormat:
self.putField("editformat", displayType)
elided, shown = self.computeLimit(bytelen, 100000)
......@@ -916,14 +878,14 @@ class DumperBase:
def putCStyleArray(self, value):
arrayType = value.type.unqualified()
innerType = value[0].type
innerTypeName = str(innerType.unqualified())
ts = innerType.sizeof
#self.putAddress(value.address)
try:
self.putValue("@0x%x" % self.addressOf(value), priority = -1)
except:
self.putEmptyValue()
self.putType(arrayType)
self.putNumChild(1)
try:
p = self.addressOf(value)
......@@ -933,20 +895,20 @@ class DumperBase:
displayFormat = self.currentItemFormat()
n = int(arrayType.sizeof / ts)
if p and self.tryPutSimpleFormattedPointer(p, str(arrayType), displayFormat, arrayType.sizeof):
self.putNumChild(n)
pass
elif displayFormat is None:
innerTypeName = str(innerType.unqualified())
blob = self.readMemory(self.addressOf(value), arrayType.sizeof)
if displayFormat != RawFormat:
if innerTypeName == "char":
# Use Latin1 as default for char [].
blob = self.readMemory(self.addressOf(value), arrayType.sizeof)
self.putValue(blob, Hex2EncodedLatin1)
elif innerTypeName == "wchar_t":
blob = self.readMemory(self.addressOf(value), arrayType.sizeof)
if innerType.sizeof == 2:
self.putValue(blob, Hex4EncodedLittleEndian)
else:
self.putValue(blob, Hex8EncodedLittleEndian)
elif p:
self.tryPutSimpleFormattedPointer(p, arrayType, innerTypeName, displayFormat, arrayType.sizeof)
self.putNumChild(n)
if self.isExpanded():
try:
......@@ -958,17 +920,7 @@ class DumperBase:
with Children(self, childType=innerType):
self.putFields(value)
if hasPlot and self.isSimpleType(innerType):
show = displayFormat == ArrayPlotFormat
iname = self.currentIName
data = []
if show:
base = self.createPointerValue(p, innerType)
data = [str(base[i]) for i in range(0, n)]
matplotSend(iname, show, data)
else:
#self.putValue(self.currentValue.value + " (not plottable)")
self.putField("plottable", "0")
self.putPlotDataHelper(p, n, innerType)
def cleanAddress(self, addr):
if addr is None:
......@@ -1038,29 +990,23 @@ class DumperBase:
data = self.readMemory(base, shown)
self.putValue(data, Hex2EncodedLatin1, elided=elided)
def putDisplay(self, editFormat, value = None, cmd = None):
def putDisplay(self, editFormat, value):
self.put('editformat="%s",' % editFormat)
if cmd is None:
if not value is None:
self.put('editvalue="%s",' % value)
else:
self.put('editvalue="%s|%s",' % (cmd, value))
self.put('editvalue="%s",' % value)
# This is shared by pointer and array formatting.
def tryPutSimpleFormattedPointer(self, value, typeName, displayFormat, limit):
if displayFormat == AutomaticFormat and typeName == "char":
def tryPutSimpleFormattedPointer(self, value, typeName, innerTypeName, displayFormat, limit):
if displayFormat == AutomaticFormat and innerTypeName == "char":
# Use Latin1 as default for char *.
self.putType(typeName)
(elided, data) = self.encodeCArray(value, 1, limit)
self.putValue(data, Hex2EncodedLatin1, elided=elided)
self.putDisplay(StopDisplay)
return True
if displayFormat == Latin1StringFormat:
self.putType(typeName)
(elided, data) = self.encodeCArray(value, 1, limit)
self.putValue(data, Hex2EncodedLatin1, elided=elided)
self.putDisplay(StopDisplay)
return True
if displayFormat == SeparateLatin1StringFormat:
......@@ -1074,7 +1020,6 @@ class DumperBase:
self.putType(typeName)
(elided, data) = self.encodeCArray(value, 1, limit)
self.putValue(data, Hex2EncodedUtf8, elided=elided)
self.putDisplay(StopDisplay)
return True
if displayFormat == SeparateUtf8StringFormat:
......@@ -1088,21 +1033,18 @@ class DumperBase:
self.putType(typeName)
(elided, data) = self.encodeCArray(value, 1, limit)
self.putValue(data, Hex2EncodedLocal8Bit, elided=elided)
self.putDisplay(StopDisplay)
return True
if displayFormat == Utf16StringFormat:
self.putType(typeName)
(elided, data) = self.encodeCArray(value, 2, limit)
self.putValue(data, Hex4EncodedLittleEndian, elided=elided)
self.putDisplay(StopDisplay)
return True
if displayFormat == Ucs4StringFormat:
self.putType(typeName)
(elided, data) = self.encodeCArray(value, 4, limit)
self.putValue(data, Hex8EncodedLittleEndian, elided=elided)
self.putDisplay(StopDisplay)
return True
return False
......@@ -1155,7 +1097,7 @@ class DumperBase:
if displayFormat == SeparateLatin1StringFormat \
or displayFormat == SeparateUtf8StringFormat:
limit = 1000000
if self.tryPutSimpleFormattedPointer(value, typeName, displayFormat, limit):
if self.tryPutSimpleFormattedPointer(value, typeName, innerTypeName, displayFormat, limit):
self.putNumChild(0)
return
......@@ -1555,22 +1497,18 @@ class DumperBase:
self.putArrayData(addr, n, self.lookupType(typeName))
self.putAddress(addr)
def putPlotData(self, base, n, typeobj):
def putPlotDataHelper(self, base, n, innerType):
if self.currentItemFormat() == ArrayPlotFormat and self.isSimpleType(innerType):
enc = self.simpleEncoding(innerType)
if enc:
self.putField("editencoding", enc)
self.putField("editvalue", self.readMemory(base, n * innerType.sizeof))
self.putField("editformat", DisplayPlotData)
def putPlotData(self, base, n, innerType):
self.putPlotDataHelper(base, n, innerType)
if self.isExpanded():
self.putArrayData(base, n, typeobj)
if hasPlot:
if self.isSimpleType(typeobj):
show = self.currentItemFormat() == ArrayPlotFormat
iname = self.currentIName
data = []
if show:
base = self.createPointerValue(base, typeobj)
data = [str(base[i]) for i in range(0, toInteger(n))]
matplotSend(iname, show, data)
else:
#self.putValue(self.currentValue.value + " (not plottable)")
self.putValue(self.currentValue.value)
self.putField("plottable", "0")
self.putArrayData(base, n, innerType)
def putSpecialArgv(self, value):
"""
......@@ -1757,10 +1695,6 @@ class DumperBase:
self.qqEditable = {}
self.typeCache = {}
if hasPlot: # Hack for generic array type. [] is used as "type" name.
self.qqDumpers['[]'] = ""
self.qqFormats['[]'] = arrayForms()
for mod in self.dumpermodules:
m = importlib.import_module(mod)
dic = m.__dict__
......@@ -1981,48 +1915,3 @@ class DumperBase:
return items
# Some "Enums"
# Encodings. Keep that synchronized with DebuggerEncoding in debuggerprotocol.h
Unencoded8Bit, \
Base64Encoded8BitWithQuotes, \
Base64Encoded16BitWithQuotes, \
Base64Encoded32BitWithQuotes, \
Base64Encoded16Bit, \
Base64Encoded8Bit, \
Hex2EncodedLatin1, \
Hex4EncodedLittleEndian, \
Hex8EncodedLittleEndian, \
Hex2EncodedUtf8, \
Hex8EncodedBigEndian, \
Hex4EncodedBigEndian, \
Hex4EncodedLittleEndianWithoutQuotes, \
Hex2EncodedLocal8Bit, \
JulianDate, \
MillisecondsSinceMidnight, \
JulianDateAndMillisecondsSinceMidnight, \
Hex2EncodedInt1, \
Hex2EncodedInt2, \
Hex2EncodedInt4, \
Hex2EncodedInt8, \
Hex2EncodedUInt1, \
Hex2EncodedUInt2, \
Hex2EncodedUInt4, \
Hex2EncodedUInt8, \
Hex2EncodedFloat4, \
Hex2EncodedFloat8, \
IPv6AddressAndHexScopeId, \
Hex2EncodedUtf8WithoutQuotes, \
DateTimeInternal \
= range(30)
# Display modes. Keep that synchronized with DebuggerDisplay in watchutils.h
StopDisplay, \
DisplayImageData, \
DisplayUtf16String, \
DisplayImageFile, \
DisplayLatin1String, \
DisplayUtf8String \
= range(6)
......@@ -1615,8 +1615,6 @@ class Dumper(DumperBase):
self.qmlBreakpoints.append(Resolver(self, args))
def exitGdb(self, _):
if hasPlot:
matplotQuit()
gdb.execute("quit")
def loadDumpers(self, args):
......
......@@ -62,14 +62,12 @@ def qdump__QByteArray(d, value):
elided, p = d.encodeByteArrayHelper(d.extractPointer(value), d.displayStringLimit)
displayFormat = d.currentItemFormat()
if displayFormat == AutomaticFormat or displayFormat == Latin1StringFormat:
d.putDisplay(StopDisplay)
d.putValue(p, Hex2EncodedLatin1, elided=elided)
elif displayFormat == SeparateLatin1StringFormat:
d.putValue(p, Hex2EncodedLatin1, elided=elided)
d.putField("editformat", DisplayLatin1String)
d.putField("editvalue", d.encodeByteArray(value, limit=100000))
elif displayFormat == Utf8StringFormat:
d.putDisplay(StopDisplay)
d.putValue(p, Hex2EncodedUtf8, elided=elided)
elif displayFormat == SeparateUtf8StringFormat:
d.putValue(p, Hex2EncodedUtf8, elided=elided)
......@@ -546,8 +544,7 @@ def qdump__QFiniteStack(d, value):
size = int(value["_size"])
d.check(0 <= size and size <= alloc and alloc <= 1000 * 1000 * 1000)
d.putItemCount(size)
if d.isExpanded():
d.putPlotData(value["_array"], size, d.templateArgument(value.type, 0))
d.putPlotData(value["_array"], size, d.templateArgument(value.type, 0))
def qdump__QFlags(d, value):
......@@ -847,9 +844,7 @@ def qdump__QImage(d, value):
d.putType("void *")
displayFormat = d.currentItemFormat()
if displayFormat == SimpleFormat:
d.putDisplay(StopDisplay)
elif displayFormat == SeparateFormat:
if displayFormat == SeparateFormat:
# This is critical for performance. Writing to an external
# file using the following is faster when using GDB.
# file = tempfile.mkstemp(prefix="gdbpy_")
......@@ -1743,9 +1738,7 @@ def qdump__QString(d, value):
data, size, alloc = d.stringData(value)
d.putNumChild(size)
displayFormat = d.currentItemFormat()
if displayFormat == SimpleFormat:
d.putDisplay(StopDisplay)
elif displayFormat == SeparateFormat:
if displayFormat == SeparateFormat:
d.putField("editformat", DisplayUtf16String)
d.putField("editvalue", d.encodeString(value, limit=100000))
if d.isExpanded():
......@@ -1897,9 +1890,7 @@ def qdump__QUrl(d, value):
d.putValue(url, Hex4EncodedLittleEndian)
displayFormat = d.currentItemFormat()
if displayFormat == SimpleFormat:
d.putDisplay(StopDisplay)
elif displayFormat == SeparateFormat:
if displayFormat == SeparateFormat:
d.putField("editformat", DisplayUtf16String)
d.putField("editvalue", url)
......
......@@ -711,15 +711,15 @@ def qdump__std__vector(d, value):
d.checkPointer(alloc)
d.putItemCount(size)
if d.isExpanded():
if isBool:
if isBool:
if d.isExpanded():
with Children(d, size, maxNumChild=10000, childType=type):
base = d.pointerValue(start)
for i in d.childRange():
q = base + int(i / 8)
d.putBoolItem(str(i), (int(d.extractPointer(q)) >> (i % 8)) & 1)
else:
d.putPlotData(start, size, type)
else:
d.putPlotData(start, size, type)
def qdump__std__vector__QNX(d, value):
innerType = d.templateArgument(value.type, 0)
......
......@@ -246,7 +246,8 @@ enum DebuggerDisplay {
DisplayUtf16String = 2,
DisplayImageFile = 3,
DisplayLatin1String = 4,
DisplayUtf8String = 5
DisplayUtf8String = 5,
DisplayPlotData = 6
};
// Decode string data as returned by the dumper helpers.
QString decodeData(const QByteArray &baIn, int encoding);
......
......@@ -114,11 +114,15 @@ ImageViewer::ImageViewer(QWidget *parent)
connect(m_imageWidget, &ImageWidget::clicked, this, &ImageViewer::clicked);
}
void ImageViewer::setImage(const QImage &i)
void ImageViewer::setImage(const QImage &image)
{
m_imageWidget->setImage(i);
m_info = tr("Size: %1x%2, %3 byte, format: %4, depth: %5")
.arg(i.width()).arg(i.height()).arg(i.byteCount()).arg(i.format()).arg(i.depth());
m_imageWidget->setImage(image);
clicked(QString());
}
void ImageViewer::setInfo(const QString &info)
{
m_info = info;
clicked(QString());
}
......@@ -165,4 +169,77 @@ void ImageViewer::contextMenuEvent(QContextMenuEvent *ev)
openImageViewer(image);
}
//
//
//
PlotViewer::PlotViewer(QWidget *parent)
: QWidget(parent)
{
}
void PlotViewer::setData(const PlotViewer::Data &data)
{
m_data = data;
update();
}
void PlotViewer::setInfo(const QString &description)
{
m_info = description;
update();
}
void PlotViewer::paintEvent(QPaintEvent *)
{
QPainter pain(this);
const int n = int(m_data.size());
const int w = width();
const int h = height();
const int b = 10; // Border width.
pain.fillRect(rect(), Qt::white);
double ymin = 0;
double ymax = 0;
for (int i = 0; i < n; ++i) {
const double v = m_data.at(i);
if (v < ymin)
ymin = v;
else if (v > ymax)
ymax = v;
}
const double d = ymin == ymax ? (h / 2 - b) : (ymax - ymin);
const int k = 1; // Length of cross marker arms.
for (int i = 0; i + 1 < n; ++i) {
// Lines between points.
const int x1 = b + i * (w - 2 * b) / (n - 1);
const int x2 = b + (i + 1) * (w - 2 * b) / (n - 1);
const int y1 = h - (b + int((m_data[i] - ymin) * (h - 2 * b) / d));
const int y2 = h - (b + int((m_data[i + 1] - ymin) * (h - 2 * b) / d));
pain.drawLine(x1, y1, x2, y2);
if (i == 0) {
// Little cross marker on first point
pain.drawLine(x1 - k, y1 - k, x1 + k, y1 + k);
pain.drawLine(x1 + k, y1 - k, x1 - k, y1 + k);
}
// ... and all subsequent points.
pain.drawLine(x2 - k, y2 - k, x2 + k, y2 + k);
pain.drawLine(x2 + k, y2 - k, x2 - k, y2 + k);
}
if (n) {
pain.drawText(10, 10,
QString::fromLatin1("%5 items. X: %1..%2, Y: %3...%4").arg(0).arg(n).arg(ymin).arg(ymax).arg(n));
} else {
pain.drawText(10, 10,
QString::fromLatin1("Container is empty"));
}
}
#include "imageviewer.moc"
......@@ -33,6 +33,8 @@
#include <QWidget>
#include <vector>
QT_BEGIN_NAMESPACE
class QScrollArea;
class QLabel;
......@@ -49,19 +51,36 @@ class ImageViewer : public QWidget
public:
explicit ImageViewer(QWidget *parent = 0);
void setImage(const QImage &);
void setImage(const QImage &image);
void setInfo(const QString &description);
protected:
void contextMenuEvent(QContextMenuEvent *);
private slots:
private:
void clicked(const QString &);
private:
QScrollArea *m_scrollArea;
ImageWidget *m_imageWidget;
QLa