Image *
readPNG(const char *filename);
+const char *
+readPNMHeader(const char *buffer, size_t size, unsigned *channels, unsigned *width, unsigned *height);
} /* namespace image */
#include <assert.h>
#include <string.h>
#include <stdint.h>
+#include <stdio.h>
#include "image.hpp"
}
}
+const char *
+readPNMHeader(const char *buffer, size_t bufferSize, unsigned *channels, unsigned *width, unsigned *height)
+{
+ *channels = 0;
+ *width = 0;
+ *height = 0;
+
+ const char *currentBuffer = buffer;
+ const char *nextBuffer;
+
+ // parse number of channels
+ int scannedChannels = sscanf(currentBuffer, "P%d\n", channels);
+ if (scannedChannels != 1) { // validate scanning of channels
+ // invalid channel line
+ return buffer;
+ }
+ // convert channel token to number of channels
+ *channels = (*channels == 5) ? 1 : 3;
+
+ // advance past channel line
+ nextBuffer = (const char *) memchr((const void *) currentBuffer, '\n', bufferSize) + 1;
+ bufferSize -= nextBuffer - currentBuffer;
+ currentBuffer = nextBuffer;
+
+ // skip over optional comment
+ if (*currentBuffer == '#') {
+ // advance past comment line
+ nextBuffer = (const char *) memchr((const void *) currentBuffer, '\n', bufferSize) + 1;
+ bufferSize -= nextBuffer - currentBuffer;
+ currentBuffer = nextBuffer;
+ }
+
+ // parse dimensions of image
+ int scannedDimensions = sscanf(currentBuffer, "%d %d\n", width, height);
+ if (scannedDimensions != 2) { // validate scanning of dimensions
+ // invalid dimension line
+ return buffer;
+ }
+
+ // advance past dimension line
+ nextBuffer = (const char *) memchr((const void *) currentBuffer, '\n', bufferSize) + 1;
+ bufferSize -= nextBuffer - currentBuffer;
+ currentBuffer = nextBuffer;
+
+ // skip over "255\n" at end of header
+ nextBuffer = (const char *) memchr((const void *) currentBuffer, '\n', bufferSize) + 1;
+
+ // return start of image data
+ return nextBuffer;
+}
} /* namespace image */
Q_ASSERT(index.column() == 0);
if (event) {
- QPoint offset;
+ QPoint offset = QPoint(0, 0);
QStaticText text = event->staticText();
//text.setTextWidth(option.rect.width());
//QStyledItemDelegate::paint(painter, option, index);
QStyle *style = QApplication::style();
style->drawControl(QStyle::CE_ItemViewItem, &option, painter, 0);
+
+ // draw thumbnail of frame
+ if(event->type() == ApiTraceEvent::Frame) {
+ ApiTraceFrame *frame = static_cast<ApiTraceFrame*>(event);
+ const QImage & thumbnail = frame->thumbnail();
+ if (!thumbnail.isNull()) {
+ painter->drawImage(option.rect.topLeft() + offset, thumbnail);
+ offset += QPoint(option.rect.height() + 16, 0);
+ }
+ }
+
if (event->hasState()) {
QPixmap px = m_stateEmblem.pixmap(option.rect.height(),
option.rect.height());
return m_frameMarker;
}
-QList<ApiTraceFrame*> ApiTrace::frames() const
+const QList<ApiTraceFrame*> & ApiTrace::frames() const
{
return m_frames;
}
void ApiTrace::finishedParsing()
{
- ApiTraceFrame *firstFrame = m_frames[0];
- if (firstFrame && !firstFrame->isLoaded()) {
- loadFrame(firstFrame);
+ if (!m_frames.isEmpty()) {
+ ApiTraceFrame *firstFrame = m_frames[0];
+ if (firstFrame && !firstFrame->isLoaded()) {
+ loadFrame(firstFrame);
+ }
}
}
{
unsigned numCalls = 0;
- for (int frameIdx = 0; frameIdx <= m_frames.size(); ++frameIdx) {
+ for (int frameIdx = 0; frameIdx < m_frames.size(); ++frameIdx) {
const ApiTraceFrame *frame = m_frames[frameIdx];
unsigned numCallsInFrame = frame->isLoaded()
? frame->numChildren()
return m_loadingFrames.contains(frame);
}
+void ApiTrace::bindThumbnailsToFrames(const QList<QImage> &thumbnails)
+{
+ QList<ApiTraceFrame *> frames = m_frames;
+
+ QList<QImage>::const_iterator thumbnail = thumbnails.begin();
+
+ foreach (ApiTraceFrame *frame, frames) {
+ if (thumbnail != thumbnails.end()) {
+ frame->setThumbnail(*thumbnail);
+
+ ++thumbnail;
+
+ emit changed(frame);
+ }
+ }
+}
+
#include "apitrace.moc"
ApiTraceCall *callWithIndex(int idx) const;
- QList<ApiTraceFrame*> frames() const;
+ const QList<ApiTraceFrame*> & frames() const;
ApiTraceFrame *frameAt(int idx) const;
int numFrames() const;
int numCallsInFrame(int idx) const;
void findCallIndex(int index);
void setCallError(const ApiTraceError &error);
+ void bindThumbnailsToFrames(const QList<QImage> &thumbnails);
signals:
void loadTrace(const QString &name);
void finishedLoadingTrace();
void invalidated();
void framesInvalidated();
- void changed(ApiTraceCall *call);
+ void changed(ApiTraceEvent *event);
void startedSaving();
void saved();
void findResult(const ApiTrace::SearchRequest &request,
return m_lastCallIndex;
}
}
+
+void ApiTraceFrame::setThumbnail(const QImage & thumbnail)
+{
+ m_thumbnail = thumbnail;
+}
+
+const QImage & ApiTraceFrame::thumbnail() const
+{
+ return m_thumbnail;
+}
void setLastCallIndex(unsigned index);
unsigned lastCallIndex() const;
+
+ void setThumbnail(const QImage & thumbnail);
+ const QImage & thumbnail() const;
+
private:
ApiTrace *m_parentTrace;
quint64 m_binaryDataSize;
bool m_loaded;
unsigned m_callsToLoad;
unsigned m_lastCallIndex;
+ QImage m_thumbnail;
};
Q_DECLARE_METATYPE(ApiTraceFrame*);
this, SLOT(beginAddingFrames(int, int)));
connect(m_trace, SIGNAL(endAddingFrames()),
this, SLOT(endAddingFrames()));
- connect(m_trace, SIGNAL(changed(ApiTraceCall*)),
- this, SLOT(callChanged(ApiTraceCall*)));
+ connect(m_trace, SIGNAL(changed(ApiTraceEvent*)),
+ this, SLOT(changed(ApiTraceEvent*)));
connect(m_trace, SIGNAL(beginLoadingFrame(ApiTraceFrame*,int)),
this, SLOT(beginLoadingFrame(ApiTraceFrame*,int)));
connect(m_trace, SIGNAL(endLoadingFrame(ApiTraceFrame*)),
emit dataChanged(index, index);
} else if (event->type() == ApiTraceEvent::Frame) {
ApiTraceFrame *frame = static_cast<ApiTraceFrame*>(event);
- const QList<ApiTraceFrame*> frames = m_trace->frames();
+ const QList<ApiTraceFrame*> & frames = m_trace->frames();
int row = frames.indexOf(frame);
QModelIndex index = createIndex(row, 0, frame);
emit dataChanged(index, index);
return createIndex(row, 0, call);
}
+void ApiTraceModel::changed(ApiTraceEvent *event)
+{
+ if (event->type() == ApiTraceEvent::Call) {
+ callChanged(static_cast<ApiTraceCall*>(event));
+ } else if (event->type() == ApiTraceEvent::Frame) {
+ frameChanged(static_cast<ApiTraceFrame*>(event));
+ }
+}
+
void ApiTraceModel::callChanged(ApiTraceCall *call)
{
ApiTrace *trace = call->parentFrame()->parentTrace();
emit dataChanged(index, index);
}
+void ApiTraceModel::frameChanged(ApiTraceFrame *frame)
+{
+ const QList<ApiTraceFrame*> & frames = m_trace->frames();
+ int row = frames.indexOf(frame);
+ QModelIndex index = createIndex(row, 0, frame);
+ emit dataChanged(index, index);
+}
+
void ApiTraceModel::endAddingFrames()
{
endInsertRows();
void invalidateFrames();
void beginAddingFrames(int oldCount, int numAdded);
void endAddingFrames();
+ void changed(ApiTraceEvent *event);
void callChanged(ApiTraceCall *call);
+ void frameChanged(ApiTraceFrame *frame);
void beginLoadingFrame(ApiTraceFrame *frame, int numAdded);
void endLoadingFrame(ApiTraceFrame *frame);
#include <QApplication>
#include <QMetaType>
#include <QVariant>
+#include <QImage>
Q_DECLARE_METATYPE(QList<ApiTraceFrame*>);
Q_DECLARE_METATYPE(QVector<ApiTraceCall*>);
Q_DECLARE_METATYPE(Qt::CaseSensitivity);
Q_DECLARE_METATYPE(ApiTrace::SearchResult);
Q_DECLARE_METATYPE(ApiTrace::SearchRequest);
+Q_DECLARE_METATYPE(QList<QImage>);
static void usage(void)
{
qRegisterMetaType<Qt::CaseSensitivity>();
qRegisterMetaType<ApiTrace::SearchResult>();
qRegisterMetaType<ApiTrace::SearchRequest>();
+ qRegisterMetaType<QList<QImage> >();
QStringList args = app.arguments();
int i = 1;
dlgUi.doubleBufferingCB->isChecked());
m_retracer->setBenchmarking(
!dlgUi.errorCheckCB->isChecked());
- replayTrace(false);
+ replayTrace(false, true);
}
}
m_ui.actionStop->setEnabled(false);
m_ui.actionReplay->setEnabled(true);
m_ui.actionLookupState->setEnabled(true);
+ m_ui.actionShowThumbnails->setEnabled(true);
}
void MainWindow::newTraceFile(const QString &fileName)
if (fileName.isEmpty()) {
m_ui.actionReplay->setEnabled(false);
m_ui.actionLookupState->setEnabled(false);
+ m_ui.actionShowThumbnails->setEnabled(false);
setWindowTitle(tr("QApiTrace"));
} else {
QFileInfo info(fileName);
m_ui.actionReplay->setEnabled(true);
m_ui.actionLookupState->setEnabled(true);
+ m_ui.actionShowThumbnails->setEnabled(true);
setWindowTitle(
tr("QApiTrace - %1").arg(info.fileName()));
}
m_ui.actionStop->setEnabled(false);
m_ui.actionReplay->setEnabled(true);
m_ui.actionLookupState->setEnabled(true);
+ m_ui.actionShowThumbnails->setEnabled(true);
m_progressBar->hide();
if (output.length() < 80) {
m_ui.actionStop->setEnabled(false);
m_ui.actionReplay->setEnabled(true);
m_ui.actionLookupState->setEnabled(true);
+ m_ui.actionShowThumbnails->setEnabled(true);
m_stateEvent = 0;
m_nonDefaultsLookupEvent = 0;
}
}
-void MainWindow::replayTrace(bool dumpState)
+void MainWindow::replayTrace(bool dumpState, bool dumpThumbnails)
{
if (m_trace->fileName().isEmpty()) {
return;
m_retracer->setFileName(m_trace->fileName());
m_retracer->setAPI(m_api);
m_retracer->setCaptureState(dumpState);
+ m_retracer->setCaptureThumbnails(dumpThumbnails);
if (m_retracer->captureState() && m_selectedEvent) {
int index = 0;
if (m_selectedEvent->type() == ApiTraceEvent::Call) {
m_ui.actionStop->setEnabled(true);
m_progressBar->show();
- if (dumpState) {
- statusBar()->showMessage(
- tr("Looking up the state..."));
+ if (dumpState || dumpThumbnails) {
+ if (dumpState && dumpThumbnails) {
+ statusBar()->showMessage(
+ tr("Looking up the state and capturing thumbnails..."));
+ } else if (dumpState) {
+ statusBar()->showMessage(
+ tr("Looking up the state..."));
+ } else if (dumpThumbnails) {
+ statusBar()->showMessage(
+ tr("Capturing thumbnails..."));
+ }
} else {
statusBar()->showMessage(
tr("Replaying the trace file..."));
return;
}
m_stateEvent = m_selectedEvent;
- replayTrace(true);
+ replayTrace(true, false);
+}
+
+void MainWindow::showThumbnails()
+{
+ replayTrace(false, true);
}
MainWindow::~MainWindow()
this, SLOT(slotStartedSaving()));
connect(m_trace, SIGNAL(saved()),
this, SLOT(slotSaved()));
- connect(m_trace, SIGNAL(changed(ApiTraceCall*)),
- this, SLOT(slotTraceChanged(ApiTraceCall*)));
+ connect(m_trace, SIGNAL(changed(ApiTraceEvent*)),
+ this, SLOT(slotTraceChanged(ApiTraceEvent*)));
connect(m_trace, SIGNAL(findResult(ApiTrace::SearchRequest,ApiTrace::SearchResult,ApiTraceCall*)),
this, SLOT(slotSearchResult(ApiTrace::SearchRequest,ApiTrace::SearchResult,ApiTraceCall*)));
connect(m_trace, SIGNAL(foundFrameStart(ApiTraceFrame*)),
this, SLOT(replayError(const QString&)));
connect(m_retracer, SIGNAL(foundState(ApiTraceState*)),
this, SLOT(replayStateFound(ApiTraceState*)));
+ connect(m_retracer, SIGNAL(foundThumbnails(const QList<QImage>&)),
+ this, SLOT(replayThumbnailsFound(const QList<QImage>&)));
connect(m_retracer, SIGNAL(retraceErrors(const QList<ApiTraceError>&)),
this, SLOT(slotRetraceErrors(const QList<ApiTraceError>&)));
this, SLOT(replayStop()));
connect(m_ui.actionLookupState, SIGNAL(triggered()),
this, SLOT(lookupState()));
+ connect(m_ui.actionShowThumbnails, SIGNAL(triggered()),
+ this, SLOT(showThumbnails()));
connect(m_ui.actionOptions, SIGNAL(triggered()),
this, SLOT(showSettings()));
m_nonDefaultsLookupEvent = 0;
}
+void MainWindow::replayThumbnailsFound(const QList<QImage> &thumbnails)
+{
+ m_trace->bindThumbnailsToFrames(thumbnails);
+}
+
void MainWindow::slotGoTo()
{
m_searchWidget->hide();
return NULL;
}
-void MainWindow::slotTraceChanged(ApiTraceCall *call)
+void MainWindow::slotTraceChanged(ApiTraceEvent *event)
{
- Q_ASSERT(call);
- if (call == m_selectedEvent) {
- m_ui.detailsWebView->setHtml(call->toHtml());
+ Q_ASSERT(event);
+ if (event == m_selectedEvent) {
+ if (event->type() == ApiTraceEvent::Call) {
+ ApiTraceCall *call = static_cast<ApiTraceCall*>(event);
+ m_ui.detailsWebView->setHtml(call->toHtml());
+ }
}
}
#include <QMainWindow>
#include <QProcess>
+#include <QList>
+#include <QImage>
class ApiTrace;
class ApiTraceCall;
void replayStop();
void replayFinished(const QString &output);
void replayStateFound(ApiTraceState *state);
+ void replayThumbnailsFound(const QList<QImage> &thumbnails);
void replayError(const QString &msg);
void startedLoadingTrace();
void loadProgess(int percent);
void finishedLoadingTrace();
void lookupState();
+ void showThumbnails();
void showSettings();
void openHelp(const QUrl &url);
void showSurfacesMenu(const QPoint &pos);
void slotSaved();
void slotGoFrameStart();
void slotGoFrameEnd();
- void slotTraceChanged(ApiTraceCall *call);
+ void slotTraceChanged(ApiTraceEvent *event);
void slotRetraceErrors(const QList<ApiTraceError> &errors);
void slotErrorSelected(QTreeWidgetItem *current);
void slotSearchResult(const ApiTrace::SearchRequest &request,
void initObjects();
void initConnections();
void newTraceFile(const QString &fileName);
- void replayTrace(bool dumpState);
+ void replayTrace(bool dumpState, bool dumpThumbnails);
void fillStateForFrame();
/* there's a difference between selected frame/call and
#include "apitracecall.h"
+#include "image.hpp"
+
#include <QDebug>
#include <QVariant>
+#include <QList>
+#include <QImage>
#include <qjson/parser.h>
m_captureState = enable;
}
+bool Retracer::captureThumbnails() const
+{
+ return m_captureThumbnails;
+}
+
+void Retracer::setCaptureThumbnails(bool enable)
+{
+ m_captureThumbnails = enable;
+}
+
void Retracer::run()
{
retrace->setBenchmarking(m_benchmarking);
retrace->setDoubleBuffered(m_doubleBuffered);
retrace->setCaptureState(m_captureState);
+ retrace->setCaptureThumbnails(m_captureThumbnails);
retrace->setCaptureAtCallNumber(m_captureCall);
connect(retrace, SIGNAL(finished(const QString&)),
this, SIGNAL(error(const QString&)));
connect(retrace, SIGNAL(foundState(ApiTraceState*)),
this, SIGNAL(foundState(ApiTraceState*)));
+ connect(retrace, SIGNAL(foundThumbnails(const QList<QImage>&)),
+ this, SIGNAL(foundThumbnails(const QList<QImage>&)));
connect(retrace, SIGNAL(retraceErrors(const QList<ApiTraceError>&)),
this, SIGNAL(retraceErrors(const QList<ApiTraceError>&)));
arguments << QLatin1String("-sb");
}
- if (m_captureState) {
- arguments << QLatin1String("-D");
- arguments << QString::number(m_captureCall);
+ if (m_captureState || m_captureThumbnails) {
+ if (m_captureState) {
+ arguments << QLatin1String("-D");
+ arguments << QString::number(m_captureCall);
+ }
+ if (m_captureThumbnails) {
+ arguments << QLatin1String("-s"); // emit snapshots
+ arguments << QLatin1String("-"); // emit to stdout
+ }
} else {
if (m_benchmarking) {
arguments << QLatin1String("-b");
void RetraceProcess::replayFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
- QByteArray output = m_process->readAllStandardOutput();
+ QByteArray output;
QString msg;
- QString errStr = m_process->readAllStandardError();
-
-#if 0
- qDebug()<<"Process finished = ";
- qDebug()<<"\terr = "<<errStr;
- qDebug()<<"\tout = "<<output;
-#endif
if (exitStatus != QProcess::NormalExit) {
msg = QLatin1String("Process crashed");
} else if (exitCode != 0) {
msg = QLatin1String("Process exited with non zero exit code");
} else {
- if (m_captureState) {
- bool ok = false;
- QVariantMap parsedJson = m_jsonParser->parse(output, &ok).toMap();
- ApiTraceState *state = new ApiTraceState(parsedJson);
- emit foundState(state);
- msg = tr("State fetched.");
+ if (m_captureState || m_captureThumbnails) {
+ if (m_captureState) {
+ bool ok = false;
+ output = m_process->readAllStandardOutput();
+ QVariantMap parsedJson = m_jsonParser->parse(output, &ok).toMap();
+ ApiTraceState *state = new ApiTraceState(parsedJson);
+ emit foundState(state);
+ msg = tr("State fetched.");
+ }
+ if (m_captureThumbnails) {
+ m_process->setReadChannel(QProcess::StandardOutput);
+
+ QList<QImage> thumbnails;
+
+ while (!m_process->atEnd()) {
+ unsigned channels = 0;
+ unsigned width = 0;
+ unsigned height = 0;
+
+ char header[512];
+ qint64 headerSize = 0;
+ int headerLines = 3; // assume no optional comment line
+
+ for (int headerLine = 0; headerLine < headerLines; ++headerLine) {
+ qint64 headerRead = m_process->readLine(&header[headerSize], sizeof(header) - headerSize);
+
+ // if header actually contains optional comment line, ...
+ if (headerLine == 1 && header[headerSize] == '#') {
+ ++headerLines;
+ }
+
+ headerSize += headerRead;
+ }
+
+ const char *headerEnd = image::readPNMHeader(header, headerSize, &channels, &width, &height);
+
+ // if invalid PNM header was encountered, ...
+ if (header == headerEnd) {
+ qDebug() << "error: invalid snapshot stream encountered\n";
+ break;
+ }
+
+ //qDebug() << "channels: " << channels << ", width: " << width << ", height: " << height << "\n";
+
+ QImage snapshot = QImage(width, height, channels == 1 ? QImage::Format_Mono : QImage::Format_RGB888);
+
+ int rowBytes = channels * width;
+ for (int y = 0; y < height; ++y) {
+ unsigned char *scanLine = snapshot.scanLine(y);
+ m_process->read((char *) scanLine, rowBytes);
+ }
+
+ QImage thumbnail = snapshot.scaled(16, 16, Qt::KeepAspectRatio, Qt::FastTransformation);
+ thumbnails.append(thumbnail);
+ }
+
+ emit foundThumbnails(thumbnails);
+ msg = tr("Thumbnails fetched.");
+ }
} else {
+ output = m_process->readAllStandardOutput();
msg = QString::fromUtf8(output);
}
}
+ QString errStr = m_process->readAllStandardError();
QStringList errorLines = errStr.split('\n');
QList<ApiTraceError> errors;
QRegExp regexp("(^\\d+): +(\\b\\w+\\b): (.+$)");
m_captureState = enable;
}
+bool RetraceProcess::captureThumbnails() const
+{
+ return m_captureThumbnails;
+}
+
+void RetraceProcess::setCaptureThumbnails(bool enable)
+{
+ m_captureThumbnails = enable;
+}
+
void RetraceProcess::terminate()
{
if (m_process) {
bool captureState() const;
void setCaptureState(bool enable);
+ bool captureThumbnails() const;
+ void setCaptureThumbnails(bool enable);
+
public slots:
void start();
void terminate();
void finished(const QString &output);
void error(const QString &msg);
void foundState(ApiTraceState *state);
+ void foundThumbnails(const QList<QImage> &thumbnails);
void retraceErrors(const QList<ApiTraceError> &errors);
private slots:
bool m_benchmarking;
bool m_doubleBuffered;
bool m_captureState;
+ bool m_captureThumbnails;
qlonglong m_captureCall;
QProcess *m_process;
bool captureState() const;
void setCaptureState(bool enable);
+ bool captureThumbnails() const;
+ void setCaptureThumbnails(bool enable);
+
signals:
void finished(const QString &output);
void foundState(ApiTraceState *state);
+ void foundThumbnails(const QList<QImage> &thumbnails);
void error(const QString &msg);
void retraceErrors(const QList<ApiTraceError> &errors);
bool m_benchmarking;
bool m_doubleBuffered;
bool m_captureState;
+ bool m_captureThumbnails;
qlonglong m_captureCall;
QProcessEnvironment m_processEnvironment;
int TraceLoader::numberOfCallsInFrame(int frameIdx) const
{
- if (frameIdx > m_frameBookmarks.size()) {
+ if (frameIdx >= m_frameBookmarks.size()) {
return 0;
}
FrameBookmarks::const_iterator itr =
{
unsigned numCalls = 0;
- for (int frameIdx = 0; frameIdx <= m_frameBookmarks.size(); ++frameIdx) {
+ for (int frameIdx = 0; frameIdx < m_frameBookmarks.size(); ++frameIdx) {
const FrameBookmark &frameBookmark = m_frameBookmarks[frameIdx];
unsigned firstCall = numCalls;
unsigned endCall = numCalls + frameBookmark.numberOfCalls;
<addaction name="actionReplay"/>
<addaction name="actionStop"/>
<addaction name="actionLookupState"/>
+ <addaction name="actionShowThumbnails"/>
<addaction name="separator"/>
<addaction name="actionOptions"/>
</widget>
<string>Ctrl+L</string>
</property>
</action>
+ <action name="actionShowThumbnails">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Show &Thumbnails</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+T</string>
+ </property>
+ </action>
<action name="actionOptions">
<property name="text">
<string>Options</string>