Files
renderdoc/qrenderdoc/Windows/EventBrowser.cpp
T
baldurk 38acc56084 Unregister shortcuts when closing windows that registered shortcuts
* This prevents leaking for cases where new widgets are created (and
  the small chance a widget pointer could be re-used and cause serious
  problems), and multiple-registration errors for global shortcuts.
2017-11-22 19:11:25 +00:00

1269 lines
34 KiB
C++

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2016-2017 Baldur Karlsson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#include "EventBrowser.h"
#include <QDialogButtonBox>
#include <QKeyEvent>
#include <QMenu>
#include <QShortcut>
#include <QTimer>
#include "3rdparty/flowlayout/FlowLayout.h"
#include "Code/QRDUtils.h"
#include "Code/Resources.h"
#include "Widgets/Extended/RDHeaderView.h"
#include "Widgets/Extended/RDListWidget.h"
#include "ui_EventBrowser.h"
struct EventItemTag
{
EventItemTag() = default;
EventItemTag(uint32_t eventID) : EID(eventID), lastEID(eventID) {}
EventItemTag(uint32_t eventID, uint32_t lastEventID) : EID(eventID), lastEID(lastEventID) {}
uint32_t EID = 0;
uint32_t lastEID = 0;
double duration = -1.0;
bool current = false;
bool find = false;
bool bookmark = false;
};
Q_DECLARE_METATYPE(EventItemTag);
enum
{
COL_NAME,
COL_EID,
COL_DRAW,
COL_DURATION,
COL_COUNT,
};
EventBrowser::EventBrowser(ICaptureContext &ctx, QWidget *parent)
: QFrame(parent), ui(new Ui::EventBrowser), m_Ctx(ctx)
{
ui->setupUi(this);
clearBookmarks();
ui->jumpToEID->setFont(Formatter::PreferredFont());
ui->find->setFont(Formatter::PreferredFont());
ui->events->setFont(Formatter::PreferredFont());
ui->events->setColumns(
{tr("Name"), lit("EID"), lit("Draw #"), lit("Duration - replaced in UpdateDurationColumn")});
ui->events->setHeader(new RDHeaderView(Qt::Horizontal, this));
ui->events->header()->setStretchLastSection(true);
ui->events->header()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter);
// we set up the name column as column 0 so that it gets the tree controls.
ui->events->header()->setSectionResizeMode(COL_NAME, QHeaderView::Interactive);
ui->events->header()->setSectionResizeMode(COL_EID, QHeaderView::Interactive);
ui->events->header()->setSectionResizeMode(COL_DRAW, QHeaderView::Interactive);
ui->events->header()->setSectionResizeMode(COL_DURATION, QHeaderView::Interactive);
ui->events->setColumnAlignment(COL_DURATION, Qt::AlignRight | Qt::AlignCenter);
ui->events->header()->setMinimumSectionSize(40);
ui->events->header()->setSectionsMovable(true);
ui->events->header()->setCascadingSectionResizes(false);
ui->events->setItemVerticalMargin(3);
ui->events->setIgnoreIconSize(true);
// set up default section layout. This will be overridden in restoreState()
ui->events->header()->resizeSection(COL_EID, 80);
ui->events->header()->resizeSection(COL_DRAW, 60);
ui->events->header()->resizeSection(COL_NAME, 200);
ui->events->header()->resizeSection(COL_DURATION, 80);
ui->events->header()->hideSection(COL_DRAW);
ui->events->header()->hideSection(COL_DURATION);
ui->events->header()->moveSection(COL_NAME, 2);
UpdateDurationColumn();
m_FindHighlight = new QTimer(this);
m_FindHighlight->setInterval(400);
m_FindHighlight->setSingleShot(true);
connect(m_FindHighlight, &QTimer::timeout, this, &EventBrowser::findHighlight_timeout);
QObject::connect(ui->closeFind, &QToolButton::clicked, this, &EventBrowser::on_HideFindJump);
QObject::connect(ui->closeJump, &QToolButton::clicked, this, &EventBrowser::on_HideFindJump);
QObject::connect(ui->events, &RDTreeWidget::keyPress, this, &EventBrowser::events_keyPress);
ui->jumpStrip->hide();
ui->findStrip->hide();
ui->bookmarkStrip->hide();
m_BookmarkStripLayout = new FlowLayout(ui->bookmarkStrip, 0, 3, 3);
m_BookmarkSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
ui->bookmarkStrip->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
m_BookmarkStripLayout->addWidget(ui->bookmarkStripHeader);
m_BookmarkStripLayout->addItem(m_BookmarkSpacer);
Qt::Key keys[] = {
Qt::Key_1, Qt::Key_2, Qt::Key_3, Qt::Key_4, Qt::Key_5,
Qt::Key_6, Qt::Key_7, Qt::Key_8, Qt::Key_9, Qt::Key_0,
};
for(int i = 0; i < 10; i++)
{
ctx.GetMainWindow()->RegisterShortcut(QKeySequence(keys[i] | Qt::ControlModifier).toString(),
NULL, [this, i]() { jumpToBookmark(i); });
}
ctx.GetMainWindow()->RegisterShortcut(QKeySequence(Qt::Key_Left | Qt::ControlModifier).toString(),
NULL, [this]() { on_stepPrev_clicked(); });
ctx.GetMainWindow()->RegisterShortcut(QKeySequence(Qt::Key_Right | Qt::ControlModifier).toString(),
NULL, [this]() { on_stepNext_clicked(); });
ctx.GetMainWindow()->RegisterShortcut(QKeySequence(Qt::Key_Escape).toString(), ui->findStrip,
[this]() { on_HideFindJump(); });
ctx.GetMainWindow()->RegisterShortcut(QKeySequence(Qt::Key_Escape).toString(), ui->jumpStrip,
[this]() { on_HideFindJump(); });
ui->events->setContextMenuPolicy(Qt::CustomContextMenu);
QObject::connect(ui->events, &RDTreeWidget::customContextMenuRequested, this,
&EventBrowser::events_contextMenu);
ui->events->header()->setContextMenuPolicy(Qt::CustomContextMenu);
QObject::connect(ui->events->header(), &QHeaderView::customContextMenuRequested, this,
&EventBrowser::events_contextMenu);
OnCaptureClosed();
m_redPalette = palette();
m_redPalette.setColor(QPalette::Base, Qt::red);
m_Ctx.AddCaptureViewer(this);
}
EventBrowser::~EventBrowser()
{
// unregister any shortcuts we registered
Qt::Key keys[] = {
Qt::Key_1, Qt::Key_2, Qt::Key_3, Qt::Key_4, Qt::Key_5,
Qt::Key_6, Qt::Key_7, Qt::Key_8, Qt::Key_9, Qt::Key_0,
};
for(int i = 0; i < 10; i++)
{
m_Ctx.GetMainWindow()->UnregisterShortcut(
QKeySequence(keys[i] | Qt::ControlModifier).toString(), NULL);
}
m_Ctx.GetMainWindow()->UnregisterShortcut(
QKeySequence(Qt::Key_Left | Qt::ControlModifier).toString(), NULL);
m_Ctx.GetMainWindow()->UnregisterShortcut(
QKeySequence(Qt::Key_Right | Qt::ControlModifier).toString(), NULL);
m_Ctx.GetMainWindow()->UnregisterShortcut(QString(), ui->findStrip);
m_Ctx.GetMainWindow()->UnregisterShortcut(QString(), ui->jumpStrip);
m_Ctx.BuiltinWindowClosed(this);
m_Ctx.RemoveCaptureViewer(this);
delete ui;
}
void EventBrowser::OnCaptureLoaded()
{
RDTreeWidgetItem *frame = new RDTreeWidgetItem(
{QFormatStr("Frame #%1").arg(m_Ctx.FrameInfo().frameNumber), QString(), QString(), QString()});
RDTreeWidgetItem *framestart =
new RDTreeWidgetItem({tr("Frame Start"), lit("0"), lit("0"), QString()});
framestart->setTag(QVariant::fromValue(EventItemTag(0, 0)));
frame->addChild(framestart);
QPair<uint32_t, uint32_t> lastEIDDraw = AddDrawcalls(frame, m_Ctx.CurDrawcalls());
frame->setTag(QVariant::fromValue(EventItemTag(0, lastEIDDraw.first)));
ui->events->addTopLevelItem(frame);
ui->events->expandItem(frame);
clearBookmarks();
repopulateBookmarks();
ui->find->setEnabled(true);
ui->gotoEID->setEnabled(true);
ui->timeDraws->setEnabled(true);
ui->bookmark->setEnabled(true);
ui->exportDraws->setEnabled(true);
ui->stepPrev->setEnabled(true);
ui->stepNext->setEnabled(true);
m_Ctx.SetEventID({this}, lastEIDDraw.first, lastEIDDraw.first);
}
void EventBrowser::OnCaptureClosed()
{
clearBookmarks();
ui->events->clear();
ui->find->setEnabled(false);
ui->gotoEID->setEnabled(false);
ui->timeDraws->setEnabled(false);
ui->bookmark->setEnabled(false);
ui->exportDraws->setEnabled(false);
ui->stepPrev->setEnabled(false);
ui->stepNext->setEnabled(false);
}
void EventBrowser::OnEventChanged(uint32_t eventID)
{
SelectEvent(eventID);
repopulateBookmarks();
highlightBookmarks();
}
QPair<uint32_t, uint32_t> EventBrowser::AddDrawcalls(RDTreeWidgetItem *parent,
const rdcarray<DrawcallDescription> &draws)
{
uint lastEID = 0, lastDraw = 0;
for(int32_t i = 0; i < draws.count(); i++)
{
const DrawcallDescription &d = draws[i];
RDTreeWidgetItem *child = new RDTreeWidgetItem(
{d.name, QString::number(d.eventID), QString::number(d.drawcallID), lit("---")});
QPair<uint32_t, uint32_t> last = AddDrawcalls(child, d.children);
lastEID = last.first;
lastDraw = last.second;
if(lastEID > d.eventID)
{
child->setText(COL_EID, QFormatStr("%1-%2").arg(d.eventID).arg(lastEID));
child->setText(COL_DRAW, QFormatStr("%1-%2").arg(d.drawcallID).arg(lastDraw));
}
if(lastEID == 0)
{
lastEID = d.eventID;
lastDraw = d.drawcallID;
if((draws[i].flags & DrawFlags::SetMarker) && i + 1 < draws.count())
lastEID = draws[i + 1].eventID;
}
child->setTag(QVariant::fromValue(EventItemTag(draws[i].eventID, lastEID)));
if(m_Ctx.Config().EventBrowser_ApplyColors)
{
// if alpha isn't 0, assume the colour is valid
if((d.flags & (DrawFlags::PushMarker | DrawFlags::SetMarker)) && d.markerColor[3] > 0.0f)
{
QColor col = QColor::fromRgb(
qRgb(d.markerColor[0] * 255.0f, d.markerColor[1] * 255.0f, d.markerColor[2] * 255.0f));
child->setTreeColor(col, 3.0f);
if(m_Ctx.Config().EventBrowser_ColorEventRow)
{
QColor textCol = ui->events->palette().color(QPalette::Text);
child->setBackgroundColor(col);
child->setForegroundColor(contrastingColor(col, textCol));
}
}
}
parent->addChild(child);
}
return qMakePair(lastEID, lastDraw);
}
void EventBrowser::SetDrawcallTimes(RDTreeWidgetItem *node, const rdcarray<CounterResult> &results)
{
if(node == NULL)
return;
// parent nodes take the value of the sum of their children
double duration = 0.0;
// look up leaf nodes in the dictionary
if(node->childCount() == 0)
{
uint32_t eid = node->tag().value<EventItemTag>().EID;
duration = -1.0;
for(const CounterResult &r : results)
{
if(r.eventID == eid)
duration = r.value.d;
}
double secs = duration;
if(m_TimeUnit == TimeUnit::Milliseconds)
secs *= 1000.0;
else if(m_TimeUnit == TimeUnit::Microseconds)
secs *= 1000000.0;
else if(m_TimeUnit == TimeUnit::Nanoseconds)
secs *= 1000000000.0;
node->setText(COL_DURATION, duration < 0.0f ? QString() : Formatter::Format(secs));
EventItemTag tag = node->tag().value<EventItemTag>();
tag.duration = duration;
node->setTag(QVariant::fromValue(tag));
return;
}
for(int i = 0; i < node->childCount(); i++)
{
SetDrawcallTimes(node->child(i), results);
double nd = node->child(i)->tag().value<EventItemTag>().duration;
if(nd > 0.0)
duration += nd;
}
double secs = duration;
if(m_TimeUnit == TimeUnit::Milliseconds)
secs *= 1000.0;
else if(m_TimeUnit == TimeUnit::Microseconds)
secs *= 1000000.0;
else if(m_TimeUnit == TimeUnit::Nanoseconds)
secs *= 1000000000.0;
node->setText(COL_DURATION, duration < 0.0f ? QString() : Formatter::Format(secs));
EventItemTag tag = node->tag().value<EventItemTag>();
tag.duration = duration;
node->setTag(QVariant::fromValue(tag));
}
void EventBrowser::on_find_clicked()
{
ui->jumpStrip->hide();
ui->findStrip->show();
ui->findEvent->setFocus();
}
void EventBrowser::on_gotoEID_clicked()
{
ui->jumpStrip->show();
ui->findStrip->hide();
ui->jumpToEID->setFocus();
}
void EventBrowser::on_bookmark_clicked()
{
RDTreeWidgetItem *n = ui->events->currentItem();
if(n)
toggleBookmark(n->tag().value<EventItemTag>().lastEID);
}
void EventBrowser::on_timeDraws_clicked()
{
ui->events->header()->showSection(COL_DURATION);
m_Ctx.Replay().AsyncInvoke([this](IReplayController *r) {
m_Times = r->FetchCounters({GPUCounter::EventGPUDuration});
GUIInvoke::call([this]() {
SetDrawcallTimes(ui->events->topLevelItem(0), m_Times);
ui->events->update();
});
});
}
void EventBrowser::on_events_currentItemChanged(RDTreeWidgetItem *current, RDTreeWidgetItem *previous)
{
if(previous)
{
EventItemTag tag = previous->tag().value<EventItemTag>();
tag.current = false;
previous->setTag(QVariant::fromValue(tag));
RefreshIcon(previous, tag);
}
if(!current)
return;
EventItemTag tag = current->tag().value<EventItemTag>();
tag.current = true;
current->setTag(QVariant::fromValue(tag));
RefreshIcon(current, tag);
m_Ctx.SetEventID({this}, tag.EID, tag.lastEID);
const DrawcallDescription *draw = m_Ctx.GetDrawcall(tag.lastEID);
ui->stepPrev->setEnabled(draw && draw->previous);
ui->stepNext->setEnabled(draw && draw->next);
// special case for the first draw in the frame
if(tag.lastEID == 0)
ui->stepNext->setEnabled(true);
// special case for the first 'virtual' draw at EID 0
if(m_Ctx.GetFirstDrawcall() && tag.lastEID == m_Ctx.GetFirstDrawcall()->eventID)
ui->stepPrev->setEnabled(true);
highlightBookmarks();
}
void EventBrowser::on_HideFindJump()
{
ui->jumpStrip->hide();
ui->findStrip->hide();
ui->jumpToEID->setText(QString());
ClearFindIcons();
ui->findEvent->setPalette(palette());
}
void EventBrowser::on_jumpToEID_returnPressed()
{
bool ok = false;
uint eid = ui->jumpToEID->text().toUInt(&ok);
if(ok)
{
SelectEvent(eid);
}
}
void EventBrowser::findHighlight_timeout()
{
ClearFindIcons();
int results = SetFindIcons(ui->findEvent->text());
if(results > 0)
ui->findEvent->setPalette(palette());
else
ui->findEvent->setPalette(m_redPalette);
}
void EventBrowser::on_findEvent_textEdited(const QString &arg1)
{
if(arg1.isEmpty())
{
m_FindHighlight->stop();
ui->findEvent->setPalette(palette());
ClearFindIcons();
}
else
{
m_FindHighlight->start(); // restart
}
}
void EventBrowser::on_findEvent_returnPressed()
{
// stop the timer, we'll manually fire it instantly
if(m_FindHighlight->isActive())
m_FindHighlight->stop();
if(!ui->findEvent->text().isEmpty())
Find(true);
findHighlight_timeout();
}
void EventBrowser::on_findEvent_keyPress(QKeyEvent *event)
{
if(event->key() == Qt::Key_F3)
{
// stop the timer, we'll manually fire it instantly
if(m_FindHighlight->isActive())
m_FindHighlight->stop();
if(!ui->findEvent->text().isEmpty())
Find(event->modifiers() & Qt::ShiftModifier ? false : true);
findHighlight_timeout();
event->accept();
}
}
void EventBrowser::on_findNext_clicked()
{
Find(true);
}
void EventBrowser::on_findPrev_clicked()
{
Find(false);
}
void EventBrowser::on_stepNext_clicked()
{
if(!m_Ctx.IsCaptureLoaded() || !ui->stepNext->isEnabled())
return;
const DrawcallDescription *draw = m_Ctx.CurDrawcall();
if(draw && draw->next > 0)
SelectEvent(draw->next);
// special case for the first 'virtual' draw at EID 0
if(m_Ctx.CurEvent() == 0)
SelectEvent(m_Ctx.GetFirstDrawcall()->eventID);
}
void EventBrowser::on_stepPrev_clicked()
{
if(!m_Ctx.IsCaptureLoaded() || !ui->stepPrev->isEnabled())
return;
const DrawcallDescription *draw = m_Ctx.CurDrawcall();
if(draw && draw->previous > 0)
SelectEvent(draw->previous);
// special case for the first 'virtual' draw at EID 0
if(m_Ctx.CurEvent() == m_Ctx.GetFirstDrawcall()->eventID)
SelectEvent(0);
}
void EventBrowser::on_exportDraws_clicked()
{
QString filename =
RDDialog::getSaveFileName(this, tr("Save Event List"), QString(), tr("Text files (*.txt)"));
if(!filename.isEmpty())
{
QDir dirinfo = QFileInfo(filename).dir();
if(dirinfo.exists())
{
QFile f(filename);
if(f.open(QIODevice::WriteOnly | QIODevice::Truncate))
{
QTextStream stream(&f);
stream << tr("%1 - Frame #%2\n\n")
.arg(m_Ctx.GetCaptureFilename())
.arg(m_Ctx.FrameInfo().frameNumber);
int maxNameLength = 0;
for(const DrawcallDescription &d : m_Ctx.CurDrawcalls())
GetMaxNameLength(maxNameLength, 0, false, d);
QString line = QFormatStr(" EID | %1 | Draw #").arg(lit("Event"), -maxNameLength);
if(!m_Times.empty())
{
line += QFormatStr(" | %1").arg(ui->events->headerText(COL_DURATION));
}
stream << line << "\n";
line = QFormatStr("--------%1-----------").arg(QString(), maxNameLength, QLatin1Char('-'));
if(!m_Times.empty())
{
int maxDurationLength = 0;
maxDurationLength = qMax(maxDurationLength, Formatter::Format(1.0).length());
maxDurationLength = qMax(maxDurationLength, Formatter::Format(1.2345e-200).length());
maxDurationLength =
qMax(maxDurationLength, Formatter::Format(123456.7890123456789).length());
line += QString(3 + maxDurationLength, QLatin1Char('-')); // 3 extra for " | "
}
stream << line << "\n";
for(const DrawcallDescription &d : m_Ctx.CurDrawcalls())
ExportDrawcall(stream, maxNameLength, 0, false, d);
}
else
{
RDDialog::critical(
this, tr("Error saving event list"),
tr("Couldn't open path %1 for write.\n%2").arg(filename).arg(f.errorString()));
return;
}
}
else
{
RDDialog::critical(this, tr("Invalid directory"),
tr("Cannot find target directory to save to"));
return;
}
}
}
void EventBrowser::on_colSelect_clicked()
{
QDialog dialog;
RDListWidget list;
QDialogButtonBox buttons;
dialog.setWindowTitle(tr("Select Event Browser Columns"));
dialog.setWindowFlags(dialog.windowFlags() & ~Qt::WindowContextHelpButtonHint);
for(int visIdx = 0; visIdx < COL_COUNT; visIdx++)
{
int logIdx = ui->events->header()->logicalIndex(visIdx);
QListWidgetItem *item = new QListWidgetItem(ui->events->headerText(logIdx), &list);
item->setData(Qt::UserRole, logIdx);
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
// this must stay enabled
if(logIdx == COL_NAME)
item->setFlags(item->flags() & ~Qt::ItemIsEnabled);
item->setCheckState(ui->events->header()->isSectionHidden(logIdx) ? Qt::Unchecked : Qt::Checked);
}
list.setSelectionMode(QAbstractItemView::SingleSelection);
list.setDragDropMode(QAbstractItemView::DragDrop);
list.setDefaultDropAction(Qt::MoveAction);
buttons.setOrientation(Qt::Horizontal);
buttons.setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
buttons.setCenterButtons(true);
QObject::connect(&buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
QObject::connect(&buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
QVBoxLayout *layout = new QVBoxLayout(&dialog);
layout->addWidget(new QLabel(tr("Select the columns to enable."), &dialog));
layout->addWidget(&list);
layout->addWidget(&buttons);
int res = RDDialog::show(&dialog);
if(res)
{
for(int i = 0; i < COL_COUNT; i++)
{
int logicalIdx = list.item(i)->data(Qt::UserRole).toInt();
if(list.item(i)->checkState() == Qt::Unchecked)
ui->events->header()->hideSection(logicalIdx);
else
ui->events->header()->showSection(logicalIdx);
ui->events->header()->moveSection(ui->events->header()->visualIndex(logicalIdx), i);
}
}
}
QString EventBrowser::GetExportDrawcallString(int indent, bool firstchild,
const DrawcallDescription &drawcall)
{
QString prefix = QString(indent * 2 - (firstchild ? 1 : 0), QLatin1Char(' '));
if(firstchild)
prefix += QLatin1Char('\\');
return QFormatStr("%1- %2").arg(prefix).arg(drawcall.name);
}
double EventBrowser::GetDrawTime(const DrawcallDescription &drawcall)
{
if(!drawcall.children.empty())
{
double total = 0.0;
for(const DrawcallDescription &d : drawcall.children)
{
double f = GetDrawTime(d);
if(f >= 0)
total += f;
}
return total;
}
for(const CounterResult &r : m_Times)
{
if(r.eventID == drawcall.eventID)
return r.value.d;
}
return -1.0;
}
void EventBrowser::GetMaxNameLength(int &maxNameLength, int indent, bool firstchild,
const DrawcallDescription &drawcall)
{
QString nameString = GetExportDrawcallString(indent, firstchild, drawcall);
maxNameLength = qMax(maxNameLength, nameString.count());
firstchild = true;
for(const DrawcallDescription &d : drawcall.children)
{
GetMaxNameLength(maxNameLength, indent + 1, firstchild, d);
firstchild = false;
}
}
void EventBrowser::ExportDrawcall(QTextStream &writer, int maxNameLength, int indent,
bool firstchild, const DrawcallDescription &drawcall)
{
QString eidString = drawcall.children.empty() ? QString::number(drawcall.eventID) : QString();
QString nameString = GetExportDrawcallString(indent, firstchild, drawcall);
QString line = QFormatStr("%1 | %2 | %3")
.arg(eidString, -5)
.arg(nameString, -maxNameLength)
.arg(drawcall.drawcallID, -6);
if(!m_Times.empty())
{
double f = GetDrawTime(drawcall);
if(f >= 0)
{
if(m_TimeUnit == TimeUnit::Milliseconds)
f *= 1000.0;
else if(m_TimeUnit == TimeUnit::Microseconds)
f *= 1000000.0;
else if(m_TimeUnit == TimeUnit::Nanoseconds)
f *= 1000000000.0;
line += QFormatStr(" | %1").arg(Formatter::Format(f));
}
else
{
line += lit(" |");
}
}
writer << line << "\n";
firstchild = true;
for(const DrawcallDescription &d : drawcall.children)
{
ExportDrawcall(writer, maxNameLength, indent + 1, firstchild, d);
firstchild = false;
}
}
QVariant EventBrowser::persistData()
{
QVariantMap state;
// temporarily turn off stretching the last section so we can get the real sizes.
ui->events->header()->setStretchLastSection(false);
QVariantList columns;
for(int i = 0; i < COL_COUNT; i++)
{
QVariantMap col;
bool hidden = ui->events->header()->isSectionHidden(i);
// we temporarily make the section visible to get its size, since otherwise it returns 0.
// There's no other way to access the 'hidden section sizes' which are transient and will be
// lost otherwise.
ui->events->header()->showSection(i);
int size = ui->events->header()->sectionSize(i);
if(hidden)
ui->events->header()->hideSection(i);
// name is just informative
col[lit("name")] = ui->events->headerText(i);
col[lit("index")] = ui->events->header()->visualIndex(i);
col[lit("hidden")] = hidden;
col[lit("size")] = size;
columns.push_back(col);
}
ui->events->header()->setStretchLastSection(true);
state[lit("columns")] = columns;
return state;
}
void EventBrowser::setPersistData(const QVariant &persistData)
{
QVariantMap state = persistData.toMap();
QVariantList columns = state[lit("columns")].toList();
for(int i = 0; i < columns.count() && i < COL_COUNT; i++)
{
QVariantMap col = columns[i].toMap();
int oldVisIdx = ui->events->header()->visualIndex(i);
int visIdx = col[lit("index")].toInt();
int size = col[lit("size")].toInt();
bool hidden = col[lit("hidden")].toBool();
ui->events->header()->moveSection(oldVisIdx, visIdx);
ui->events->header()->resizeSection(i, size);
if(hidden)
ui->events->header()->hideSection(i);
else
ui->events->header()->showSection(i);
}
}
void EventBrowser::events_keyPress(QKeyEvent *event)
{
if(!m_Ctx.IsCaptureLoaded())
return;
if(event->key() == Qt::Key_F3)
{
if(event->modifiers() == Qt::ShiftModifier)
Find(false);
else
Find(true);
}
if(event->modifiers() == Qt::ControlModifier)
{
if(event->key() == Qt::Key_F)
{
on_find_clicked();
event->accept();
}
else if(event->key() == Qt::Key_G)
{
on_gotoEID_clicked();
event->accept();
}
else if(event->key() == Qt::Key_B)
{
on_bookmark_clicked();
event->accept();
}
else if(event->key() == Qt::Key_T)
{
on_timeDraws_clicked();
event->accept();
}
}
}
void EventBrowser::events_contextMenu(const QPoint &pos)
{
RDTreeWidgetItem *item = ui->events->itemAt(pos);
QMenu contextMenu(this);
QAction expandAll(tr("&Expand All"), this);
QAction collapseAll(tr("&Collapse All"), this);
QAction selectCols(tr("&Select Columns..."), this);
contextMenu.addAction(&expandAll);
contextMenu.addAction(&collapseAll);
contextMenu.addAction(&selectCols);
expandAll.setIcon(Icons::arrow_out());
collapseAll.setIcon(Icons::arrow_in());
selectCols.setIcon(Icons::timeline_marker());
expandAll.setEnabled(item && item->childCount() > 0);
collapseAll.setEnabled(item && item->childCount() > 0);
QObject::connect(&expandAll, &QAction::triggered,
[this, item]() { ui->events->expandAllItems(item); });
QObject::connect(&collapseAll, &QAction::triggered,
[this, item]() { ui->events->collapseAllItems(item); });
QObject::connect(&selectCols, &QAction::triggered, this, &EventBrowser::on_colSelect_clicked);
RDDialog::show(&contextMenu, ui->events->viewport()->mapToGlobal(pos));
}
void EventBrowser::clearBookmarks()
{
for(QToolButton *b : m_BookmarkButtons)
delete b;
m_BookmarkButtons.clear();
ui->bookmarkStrip->setVisible(false);
}
void EventBrowser::repopulateBookmarks()
{
const QList<EventBookmark> bookmarks = m_Ctx.GetBookmarks();
// add any bookmark markers that we don't have
for(const EventBookmark &mark : bookmarks)
{
if(!m_BookmarkButtons.contains(mark.EID))
{
uint32_t EID = mark.EID;
QToolButton *but = new QToolButton(this);
but->setText(QString::number(EID));
but->setCheckable(true);
but->setAutoRaise(true);
but->setProperty("eid", EID);
QObject::connect(but, &QToolButton::clicked, [this, but, EID]() {
but->setChecked(true);
SelectEvent(EID);
highlightBookmarks();
});
m_BookmarkButtons[EID] = but;
highlightBookmarks();
RDTreeWidgetItem *found = NULL;
FindEventNode(found, ui->events->topLevelItem(0), EID);
if(found)
{
EventItemTag tag = found->tag().value<EventItemTag>();
tag.bookmark = true;
found->setTag(QVariant::fromValue(tag));
RefreshIcon(found, tag);
}
m_BookmarkStripLayout->removeItem(m_BookmarkSpacer);
m_BookmarkStripLayout->addWidget(but);
m_BookmarkStripLayout->addItem(m_BookmarkSpacer);
}
}
// remove any bookmark markers we shouldn't have
for(uint32_t EID : m_BookmarkButtons.keys())
{
if(!bookmarks.contains(EventBookmark(EID)))
{
delete m_BookmarkButtons[EID];
m_BookmarkButtons.remove(EID);
RDTreeWidgetItem *found = NULL;
FindEventNode(found, ui->events->topLevelItem(0), EID);
if(found)
{
EventItemTag tag = found->tag().value<EventItemTag>();
tag.bookmark = false;
found->setTag(QVariant::fromValue(tag));
RefreshIcon(found, tag);
}
}
}
ui->bookmarkStrip->setVisible(!bookmarks.isEmpty());
}
void EventBrowser::toggleBookmark(uint32_t EID)
{
EventBookmark mark(EID);
if(m_Ctx.GetBookmarks().contains(mark))
m_Ctx.RemoveBookmark(EID);
else
m_Ctx.SetBookmark(mark);
}
void EventBrowser::jumpToBookmark(int idx)
{
const QList<EventBookmark> bookmarks = m_Ctx.GetBookmarks();
if(idx < 0 || idx >= bookmarks.count() || !m_Ctx.IsCaptureLoaded())
return;
// don't exclude ourselves, so we're updated as normal
SelectEvent(bookmarks[idx].EID);
}
void EventBrowser::highlightBookmarks()
{
for(uint32_t eid : m_BookmarkButtons.keys())
{
if(eid == m_Ctx.CurEvent())
m_BookmarkButtons[eid]->setChecked(true);
else
m_BookmarkButtons[eid]->setChecked(false);
}
}
bool EventBrowser::hasBookmark(RDTreeWidgetItem *node)
{
if(node)
return hasBookmark(node->tag().value<EventItemTag>().EID);
return false;
}
bool EventBrowser::hasBookmark(uint32_t EID)
{
return m_Ctx.GetBookmarks().contains(EventBookmark(EID));
}
void EventBrowser::RefreshIcon(RDTreeWidgetItem *item, EventItemTag tag)
{
if(tag.current)
item->setIcon(COL_NAME, Icons::flag_green());
else if(tag.bookmark)
item->setIcon(COL_NAME, Icons::asterisk_orange());
else if(tag.find)
item->setIcon(COL_NAME, Icons::find());
else
item->setIcon(COL_NAME, QIcon());
}
bool EventBrowser::FindEventNode(RDTreeWidgetItem *&found, RDTreeWidgetItem *parent, uint32_t eventID)
{
// do a reverse search to find the last match (in case of 'set' markers that
// inherit the event of the next real draw).
for(int i = parent->childCount() - 1; i >= 0; i--)
{
RDTreeWidgetItem *n = parent->child(i);
uint nEID = n->tag().value<EventItemTag>().lastEID;
uint fEID = found ? found->tag().value<EventItemTag>().lastEID : 0;
if(nEID >= eventID && (found == NULL || nEID <= fEID))
found = n;
if(nEID == eventID && n->childCount() == 0)
return true;
if(n->childCount() > 0)
{
bool exact = FindEventNode(found, n, eventID);
if(exact)
return true;
}
}
return false;
}
void EventBrowser::ExpandNode(RDTreeWidgetItem *node)
{
RDTreeWidgetItem *n = node;
while(node != NULL)
{
ui->events->expandItem(node);
node = node->parent();
}
if(n)
ui->events->scrollToItem(n);
}
bool EventBrowser::SelectEvent(uint32_t eventID)
{
if(!m_Ctx.IsCaptureLoaded())
return false;
RDTreeWidgetItem *found = NULL;
FindEventNode(found, ui->events->topLevelItem(0), eventID);
if(found != NULL)
{
ui->events->setCurrentItem(found);
ui->events->setSelectedItem(found);
ExpandNode(found);
return true;
}
return false;
}
void EventBrowser::ClearFindIcons(RDTreeWidgetItem *parent)
{
for(int i = 0; i < parent->childCount(); i++)
{
RDTreeWidgetItem *n = parent->child(i);
EventItemTag tag = n->tag().value<EventItemTag>();
tag.find = false;
n->setTag(QVariant::fromValue(tag));
RefreshIcon(n, tag);
if(n->childCount() > 0)
ClearFindIcons(n);
}
}
void EventBrowser::ClearFindIcons()
{
if(m_Ctx.IsCaptureLoaded())
ClearFindIcons(ui->events->topLevelItem(0));
}
int EventBrowser::SetFindIcons(RDTreeWidgetItem *parent, QString filter)
{
int results = 0;
for(int i = 0; i < parent->childCount(); i++)
{
RDTreeWidgetItem *n = parent->child(i);
if(n->text(COL_NAME).contains(filter, Qt::CaseInsensitive))
{
EventItemTag tag = n->tag().value<EventItemTag>();
tag.find = true;
n->setTag(QVariant::fromValue(tag));
RefreshIcon(n, tag);
results++;
}
if(n->childCount() > 0)
{
results += SetFindIcons(n, filter);
}
}
return results;
}
int EventBrowser::SetFindIcons(QString filter)
{
if(filter.isEmpty())
return 0;
return SetFindIcons(ui->events->topLevelItem(0), filter);
}
RDTreeWidgetItem *EventBrowser::FindNode(RDTreeWidgetItem *parent, QString filter, uint32_t after)
{
for(int i = 0; i < parent->childCount(); i++)
{
RDTreeWidgetItem *n = parent->child(i);
uint eid = n->tag().value<EventItemTag>().lastEID;
if(eid > after && n->text(COL_NAME).contains(filter, Qt::CaseInsensitive))
return n;
if(n->childCount() > 0)
{
RDTreeWidgetItem *found = FindNode(n, filter, after);
if(found != NULL)
return found;
}
}
return NULL;
}
int EventBrowser::FindEvent(RDTreeWidgetItem *parent, QString filter, uint32_t after, bool forward)
{
if(parent == NULL)
return -1;
for(int i = forward ? 0 : parent->childCount() - 1; i >= 0 && i < parent->childCount();
i += forward ? 1 : -1)
{
auto n = parent->child(i);
uint eid = n->tag().value<EventItemTag>().lastEID;
bool matchesAfter = (forward && eid > after) || (!forward && eid < after);
if(matchesAfter)
{
QString name = n->text(COL_NAME);
if(name.contains(filter, Qt::CaseInsensitive))
return (int)eid;
}
if(n->childCount() > 0)
{
int found = FindEvent(n, filter, after, forward);
if(found > 0)
return found;
}
}
return -1;
}
int EventBrowser::FindEvent(QString filter, uint32_t after, bool forward)
{
if(!m_Ctx.IsCaptureLoaded())
return 0;
return FindEvent(ui->events->topLevelItem(0), filter, after, forward);
}
void EventBrowser::Find(bool forward)
{
if(ui->findEvent->text().isEmpty())
return;
uint32_t curEID = m_Ctx.CurEvent();
RDTreeWidgetItem *node = ui->events->selectedItem();
if(node)
curEID = node->tag().value<EventItemTag>().lastEID;
int eid = FindEvent(ui->findEvent->text(), curEID, forward);
if(eid >= 0)
{
SelectEvent((uint32_t)eid);
ui->findEvent->setPalette(palette());
}
else // if(WrapSearch)
{
eid = FindEvent(ui->findEvent->text(), forward ? 0 : ~0U, forward);
if(eid >= 0)
{
SelectEvent((uint32_t)eid);
ui->findEvent->setPalette(palette());
}
else
{
ui->findEvent->setPalette(m_redPalette);
}
}
}
void EventBrowser::UpdateDurationColumn()
{
if(m_TimeUnit == m_Ctx.Config().EventBrowser_TimeUnit)
return;
m_TimeUnit = m_Ctx.Config().EventBrowser_TimeUnit;
ui->events->setHeaderText(COL_DURATION, tr("Duration (%1)").arg(UnitSuffix(m_TimeUnit)));
if(!m_Times.empty())
SetDrawcallTimes(ui->events->topLevelItem(0), m_Times);
}