Files
renderdoc/qrenderdoc/Code/QRDUtils.cpp
T
2018-10-05 18:17:00 +01:00

1818 lines
52 KiB
C++

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2016-2018 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 "QRDUtils.h"
#include <QAbstractTextDocumentLayout>
#include <QApplication>
#include <QCollator>
#include <QDesktopServices>
#include <QElapsedTimer>
#include <QFileSystemModel>
#include <QFontDatabase>
#include <QGridLayout>
#include <QGuiApplication>
#include <QJsonDocument>
#include <QKeyEvent>
#include <QLabel>
#include <QMenu>
#include <QMetaMethod>
#include <QMouseEvent>
#include <QPainter>
#include <QProcess>
#include <QProgressBar>
#include <QProgressDialog>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
#include <QStandardPaths>
#include <QTextBlock>
#include <QTextDocument>
#include <QtMath>
#include "Code/Resources.h"
#include "Widgets/Extended/RDTreeWidget.h"
// normally this is in the renderdoc core library, but it's needed for the 'unknown enum' path,
// so we implement it here using QString. It's inefficient, but this is a very uncommon path -
// either for invalid values or for when a new enum is added and the code isn't updated
template <>
std::string DoStringise(const uint32_t &el)
{
return QString::number(el).toStdString();
}
// this one we do by hand as it requires formatting
template <>
std::string DoStringise(const ResourceId &el)
{
uint64_t num;
memcpy(&num, &el, sizeof(num));
return lit("ResourceId::%1").arg(num).toStdString();
}
struct RichResourceText
{
QVector<QVariant> fragments;
// cached formatted document. We use cacheId to check if it needs to be updated
QTextDocument doc;
int cacheId = 0;
// a plain-text version of the document, suitable for e.g. copy-paste
QString text;
// the ideal width for the document
int idealWidth = 0;
// cache the context once we've obtained it.
ICaptureContext *ctxptr = NULL;
void cacheDocument(const QWidget *widget)
{
if(!ctxptr)
ctxptr = getCaptureContext(widget);
if(!ctxptr)
return;
ICaptureContext &ctx = *(ICaptureContext *)ctxptr;
int refCache = ctx.ResourceNameCacheID();
if(cacheId == refCache)
return;
cacheId = refCache;
// use a table to ensure images don't screw up the baseline for text. DON'T JUDGE ME.
QString html = lit("<table><tr>");
int i = 0;
bool highdpi = widget->devicePixelRatioF() > 1.0;
QVector<int> fragmentIndexFromBlockIndex;
// there's an empty block at the start.
fragmentIndexFromBlockIndex.push_back(-1);
text.clear();
for(const QVariant &v : fragments)
{
if(v.userType() == qMetaTypeId<ResourceId>())
{
QString resname = QString(ctx.GetResourceName(v.value<ResourceId>())).toHtmlEscaped();
html += lit("<td><b>%1</b></td><td><img width=\"16\" src=':/link%3.png'></td>")
.arg(resname)
.arg(highdpi ? lit("@2x") : QString());
text += resname;
// these generate two blocks (one for each cell)
fragmentIndexFromBlockIndex.push_back(i);
fragmentIndexFromBlockIndex.push_back(i);
}
else
{
html += lit("<td>%1</td>").arg(v.toString().toHtmlEscaped());
text += v.toString();
// this only generates one block
fragmentIndexFromBlockIndex.push_back(i);
}
i++;
}
// there's another empty block at the end
fragmentIndexFromBlockIndex.push_back(-1);
html += lit("</tr></table>");
doc.setDocumentMargin(0);
doc.setHtml(html);
if(doc.blockCount() != fragmentIndexFromBlockIndex.count())
{
qCritical() << "Block count is not what's expected!" << doc.blockCount()
<< fragmentIndexFromBlockIndex.count();
for(i = 0; i < doc.blockCount(); i++)
doc.findBlockByNumber(i).setUserState(-1);
return;
}
for(i = 0; i < doc.blockCount(); i++)
doc.findBlockByNumber(i).setUserState(fragmentIndexFromBlockIndex[i]);
qreal old = doc.textWidth();
doc.setTextWidth(-1);
idealWidth = doc.idealWidth();
doc.setTextWidth(old);
}
};
Q_DECLARE_METATYPE(RichResourceTextPtr);
QString ResIdTextToString(RichResourceTextPtr ptr)
{
return ptr->text;
}
void RegisterMetatypeConversions()
{
QMetaType::registerConverter<RichResourceTextPtr, QString>(&ResIdTextToString);
}
void RichResourceTextInitialise(QVariant &var)
{
static QRegularExpression re(lit("(ResourceId::)([0-9]*)"));
if(var.userType() == qMetaTypeId<ResourceId>() || re.match(var.toString()).hasMatch())
{
QString text;
if(var.userType() == qMetaTypeId<ResourceId>())
text = ToQStr(var.value<ResourceId>());
else
text = var.toString();
RichResourceTextPtr linkedText(new RichResourceText);
// use regexp to split up into fragments of text and resourceid. The resourceid is then
// formatted on the fly in RichResourceText::cacheDocument
QRegularExpressionMatch match = re.match(text);
while(match.hasMatch())
{
qulonglong idnum = match.captured(2).toULongLong();
ResourceId id;
memcpy(&id, &idnum, sizeof(id));
// push any text that preceeded the ResourceId.
if(match.capturedStart(1) > 0)
linkedText->fragments.push_back(text.left(match.capturedStart(1)));
text.remove(0, match.capturedEnd(2));
linkedText->fragments.push_back(id);
match = re.match(text);
}
if(!text.isEmpty())
linkedText->fragments.push_back(text);
linkedText->doc.setHtml(text);
var = QVariant::fromValue(linkedText);
}
}
bool RichResourceTextCheck(const QVariant &var)
{
return var.userType() == qMetaTypeId<RichResourceTextPtr>();
}
void RichResourceTextPaint(const QWidget *owner, QPainter *painter, QRect rect, QFont font,
QPalette palette, bool mouseOver, QPoint mousePos, const QVariant &var)
{
RichResourceTextPtr linkedText = var.value<RichResourceTextPtr>();
linkedText->cacheDocument(owner);
painter->translate(rect.left(), rect.top());
linkedText->doc.setTextWidth(10000);
linkedText->doc.setDefaultFont(font);
// vertical align to the centre, if there's spare room.
int diff = rect.height() - linkedText->doc.size().height();
if(diff > 0)
painter->translate(0, diff / 2);
linkedText->doc.drawContents(painter, QRectF(0, 0, rect.width(), rect.height()));
if(mouseOver)
{
painter->setPen(QPen(palette.brush(QPalette::WindowText), 1.0));
QAbstractTextDocumentLayout *layout = linkedText->doc.documentLayout();
QPoint p = mousePos - rect.topLeft();
if(diff > 0)
p -= QPoint(0, diff / 2);
int pos = layout->hitTest(p, Qt::FuzzyHit);
if(pos >= 0)
{
QTextBlock block = linkedText->doc.findBlock(pos);
int frag = block.userState();
if(frag >= 0)
{
QVariant v = linkedText->fragments[frag];
if(v.userType() == qMetaTypeId<ResourceId>() && v.value<ResourceId>() != ResourceId())
{
layout->blockBoundingRect(block);
QRectF blockrect = layout->blockBoundingRect(block);
if(block.previous().userState() == frag)
{
blockrect = blockrect.united(layout->blockBoundingRect(block.previous()));
}
if(block.next().userState() == frag)
{
blockrect = blockrect.united(layout->blockBoundingRect(block.next()));
}
blockrect.translate(0.0, -2.0);
blockrect.setRight(qMin(blockrect.right(), (qreal)rect.width()));
painter->drawLine(blockrect.bottomLeft(), blockrect.bottomRight());
}
}
}
}
}
int RichResourceTextWidthHint(const QWidget *owner, const QVariant &var)
{
RichResourceTextPtr linkedText = var.value<RichResourceTextPtr>();
linkedText->cacheDocument(owner);
return linkedText->idealWidth;
}
bool RichResourceTextMouseEvent(const QWidget *owner, const QVariant &var, QRect rect,
QMouseEvent *event)
{
// only process clicks or moves
if(event->type() != QEvent::MouseButtonRelease && event->type() != QEvent::MouseMove)
return false;
RichResourceTextPtr linkedText = var.value<RichResourceTextPtr>();
linkedText->cacheDocument(owner);
QAbstractTextDocumentLayout *layout = linkedText->doc.documentLayout();
QPoint p = event->pos() - rect.topLeft();
// vertical align to the centre, if there's spare room.
int diff = rect.height() - linkedText->doc.size().height();
if(diff > 0)
p -= QPoint(0, diff / 2);
int pos = layout->hitTest(p, Qt::FuzzyHit);
if(pos >= 0)
{
QTextBlock block = linkedText->doc.findBlock(pos);
int frag = block.userState();
if(frag >= 0)
{
QVariant v = linkedText->fragments[frag];
if(v.userType() == qMetaTypeId<ResourceId>())
{
// empty resource ids are not clickable or hover-highlighted.
ResourceId res = v.value<ResourceId>();
if(res == ResourceId())
return false;
if(event->type() == QEvent::MouseButtonRelease && linkedText->ctxptr)
{
ICaptureContext &ctx = *(ICaptureContext *)linkedText->ctxptr;
if(!ctx.HasResourceInspector())
ctx.ShowResourceInspector();
ctx.GetResourceInspector()->Inspect(v.value<ResourceId>());
ctx.RaiseDockWindow(ctx.GetResourceInspector()->Widget());
}
return true;
}
}
}
return false;
}
#include "renderdoc_tostr.inl"
QString ToQStr(const ResourceUsage usage, const GraphicsAPI apitype)
{
if(IsD3D(apitype))
{
switch(usage)
{
case ResourceUsage::VertexBuffer: return lit("Vertex Buffer");
case ResourceUsage::IndexBuffer: return lit("Index Buffer");
case ResourceUsage::VS_Constants: return lit("VS - Constant Buffer");
case ResourceUsage::GS_Constants: return lit("GS - Constant Buffer");
case ResourceUsage::HS_Constants: return lit("HS - Constant Buffer");
case ResourceUsage::DS_Constants: return lit("DS - Constant Buffer");
case ResourceUsage::CS_Constants: return lit("CS - Constant Buffer");
case ResourceUsage::PS_Constants: return lit("PS - Constant Buffer");
case ResourceUsage::All_Constants: return lit("All - Constant Buffer");
case ResourceUsage::StreamOut: return lit("Stream Out");
case ResourceUsage::VS_Resource: return lit("VS - Resource");
case ResourceUsage::GS_Resource: return lit("GS - Resource");
case ResourceUsage::HS_Resource: return lit("HS - Resource");
case ResourceUsage::DS_Resource: return lit("DS - Resource");
case ResourceUsage::CS_Resource: return lit("CS - Resource");
case ResourceUsage::PS_Resource: return lit("PS - Resource");
case ResourceUsage::All_Resource: return lit("All - Resource");
case ResourceUsage::VS_RWResource: return lit("VS - UAV");
case ResourceUsage::HS_RWResource: return lit("HS - UAV");
case ResourceUsage::DS_RWResource: return lit("DS - UAV");
case ResourceUsage::GS_RWResource: return lit("GS - UAV");
case ResourceUsage::PS_RWResource: return lit("PS - UAV");
case ResourceUsage::CS_RWResource: return lit("CS - UAV");
case ResourceUsage::All_RWResource: return lit("All - UAV");
case ResourceUsage::InputTarget: return lit("Color Input");
case ResourceUsage::ColorTarget: return lit("Rendertarget");
case ResourceUsage::DepthStencilTarget: return lit("Depthstencil");
case ResourceUsage::Indirect: return lit("Indirect argument");
case ResourceUsage::Clear: return lit("Clear");
case ResourceUsage::GenMips: return lit("Generate Mips");
case ResourceUsage::Resolve: return lit("Resolve");
case ResourceUsage::ResolveSrc: return lit("Resolve - Source");
case ResourceUsage::ResolveDst: return lit("Resolve - Dest");
case ResourceUsage::Copy: return lit("Copy");
case ResourceUsage::CopySrc: return lit("Copy - Source");
case ResourceUsage::CopyDst: return lit("Copy - Dest");
case ResourceUsage::Barrier: return lit("Barrier");
default: break;
}
}
else if(apitype == GraphicsAPI::OpenGL || apitype == GraphicsAPI::Vulkan)
{
const bool vk = (apitype == GraphicsAPI::Vulkan);
switch(usage)
{
case ResourceUsage::VertexBuffer: return lit("Vertex Buffer");
case ResourceUsage::IndexBuffer: return lit("Index Buffer");
case ResourceUsage::VS_Constants: return lit("VS - Uniform Buffer");
case ResourceUsage::GS_Constants: return lit("GS - Uniform Buffer");
case ResourceUsage::HS_Constants: return lit("HS - Uniform Buffer");
case ResourceUsage::DS_Constants: return lit("DS - Uniform Buffer");
case ResourceUsage::CS_Constants: return lit("CS - Uniform Buffer");
case ResourceUsage::PS_Constants: return lit("PS - Uniform Buffer");
case ResourceUsage::All_Constants: return lit("All - Uniform Buffer");
case ResourceUsage::StreamOut: return lit("Transform Feedback");
case ResourceUsage::VS_Resource: return lit("VS - Texture");
case ResourceUsage::GS_Resource: return lit("GS - Texture");
case ResourceUsage::HS_Resource: return lit("HS - Texture");
case ResourceUsage::DS_Resource: return lit("DS - Texture");
case ResourceUsage::CS_Resource: return lit("CS - Texture");
case ResourceUsage::PS_Resource: return lit("PS - Texture");
case ResourceUsage::All_Resource: return lit("All - Texture");
case ResourceUsage::VS_RWResource: return lit("VS - Image/SSBO");
case ResourceUsage::HS_RWResource: return lit("HS - Image/SSBO");
case ResourceUsage::DS_RWResource: return lit("DS - Image/SSBO");
case ResourceUsage::GS_RWResource: return lit("GS - Image/SSBO");
case ResourceUsage::PS_RWResource: return lit("PS - Image/SSBO");
case ResourceUsage::CS_RWResource: return lit("CS - Image/SSBO");
case ResourceUsage::All_RWResource: return lit("All - Image/SSBO");
case ResourceUsage::InputTarget: return lit("FBO Input");
case ResourceUsage::ColorTarget: return lit("FBO Color");
case ResourceUsage::DepthStencilTarget: return lit("FBO Depthstencil");
case ResourceUsage::Indirect: return lit("Indirect argument");
case ResourceUsage::Clear: return lit("Clear");
case ResourceUsage::GenMips: return lit("Generate Mips");
case ResourceUsage::Resolve: return vk ? lit("Resolve") : lit("Framebuffer blit");
case ResourceUsage::ResolveSrc:
return vk ? lit("Resolve - Source") : lit("Framebuffer blit - Source");
case ResourceUsage::ResolveDst:
return vk ? lit("Resolve - Dest") : lit("Framebuffer blit - Dest");
case ResourceUsage::Copy: return lit("Copy");
case ResourceUsage::CopySrc: return lit("Copy - Source");
case ResourceUsage::CopyDst: return lit("Copy - Dest");
case ResourceUsage::Barrier: return lit("Barrier");
default: break;
}
}
return lit("Unknown");
}
QString ToQStr(const ShaderStage stage, const GraphicsAPI apitype)
{
if(IsD3D(apitype))
{
switch(stage)
{
case ShaderStage::Vertex: return lit("Vertex");
case ShaderStage::Hull: return lit("Hull");
case ShaderStage::Domain: return lit("Domain");
case ShaderStage::Geometry: return lit("Geometry");
case ShaderStage::Pixel: return lit("Pixel");
case ShaderStage::Compute: return lit("Compute");
default: break;
}
}
else if(apitype == GraphicsAPI::OpenGL || apitype == GraphicsAPI::Vulkan)
{
switch(stage)
{
case ShaderStage::Vertex: return lit("Vertex");
case ShaderStage::Tess_Control: return lit("Tess. Control");
case ShaderStage::Tess_Eval: return lit("Tess. Eval");
case ShaderStage::Geometry: return lit("Geometry");
case ShaderStage::Fragment: return lit("Fragment");
case ShaderStage::Compute: return lit("Compute");
default: break;
}
}
return lit("Unknown");
}
QString TypeString(const SigParameter &sig)
{
QString ret = lit("");
if(sig.compType == CompType::Float)
ret += lit("float");
else if(sig.compType == CompType::UInt || sig.compType == CompType::UScaled)
ret += lit("uint");
else if(sig.compType == CompType::SInt || sig.compType == CompType::SScaled)
ret += lit("int");
else if(sig.compType == CompType::UNorm)
ret += lit("unorm float");
else if(sig.compType == CompType::SNorm)
ret += lit("snorm float");
else if(sig.compType == CompType::Depth)
ret += lit("float");
if(sig.compCount > 1)
ret += QString::number(sig.compCount);
return ret;
}
QString D3DSemanticString(const SigParameter &sig)
{
if(sig.systemValue == ShaderBuiltin::Undefined)
return sig.semanticIdxName;
QString sysValues[ENUM_ARRAY_SIZE(ShaderBuiltin)] = {
lit("SV_Undefined"),
lit("SV_Position"),
lit("Unsupported (PointSize)"),
lit("SV_ClipDistance"),
lit("SV_CullDistance"),
lit("SV_RenderTargetIndex"),
lit("SV_ViewportIndex"),
lit("SV_VertexID"),
lit("SV_PrimitiveID"),
lit("SV_InstanceID"),
lit("Unsupported (DispatchSize)"),
lit("SV_DispatchThreadID"),
lit("SV_GroupID"),
lit("SV_GroupIndex"),
lit("SV_GroupThreadID"),
lit("SV_GSInstanceID"),
lit("SV_OutputControlPointID"),
lit("SV_DomainLocation"),
lit("SV_IsFrontFace"),
lit("SV_Coverage"),
lit("Unsupported (SamplePosition)"),
lit("SV_SampleIndex"),
lit("Unsupported (PatchNumVertices)"),
lit("SV_TessFactor"),
lit("SV_InsideTessFactor"),
lit("SV_Target"),
lit("SV_Depth"),
lit("SV_DepthGreaterEqual"),
lit("SV_DepthLessEqual"),
};
QString ret = sysValues[size_t(sig.systemValue)];
// need to include the index if it's a system value semantic that's numbered
if(sig.systemValue == ShaderBuiltin::ColorOutput ||
sig.systemValue == ShaderBuiltin::CullDistance || sig.systemValue == ShaderBuiltin::ClipDistance)
ret += QString::number(sig.semanticIndex);
return ret;
}
QString GetComponentString(byte mask)
{
QString ret;
if((mask & 0x1) > 0)
ret += lit("R");
if((mask & 0x2) > 0)
ret += lit("G");
if((mask & 0x4) > 0)
ret += lit("B");
if((mask & 0x8) > 0)
ret += lit("A");
return ret;
}
void CombineUsageEvents(ICaptureContext &ctx, const rdcarray<EventUsage> &usage,
std::function<void(uint32_t startEID, uint32_t endEID, ResourceUsage use)> callback)
{
uint32_t start = 0;
uint32_t end = 0;
ResourceUsage us = ResourceUsage::IndexBuffer;
for(const EventUsage u : usage)
{
if(start == 0)
{
start = end = u.eventId;
us = u.usage;
continue;
}
const DrawcallDescription *draw = ctx.GetDrawcall(u.eventId);
bool distinct = false;
// if the usage is different from the last, add a new entry,
// or if the previous draw link is broken.
if(u.usage != us || draw == NULL || draw->previous == 0)
{
distinct = true;
}
else
{
// otherwise search back through real draws, to see if the
// last event was where we were - otherwise it's a new
// distinct set of drawcalls and should have a separate
// entry in the context menu
const DrawcallDescription *prev = draw->previous;
while(prev != NULL && prev->eventId > end)
{
if(!(prev->flags & (DrawFlags::Dispatch | DrawFlags::Drawcall | DrawFlags::CmdList)))
{
prev = prev->previous;
}
else
{
distinct = true;
break;
}
if(prev == NULL)
distinct = true;
}
}
if(distinct)
{
callback(start, end, us);
start = end = u.eventId;
us = u.usage;
}
end = u.eventId;
}
if(start != 0)
callback(start, end, us);
}
void addStructuredObjects(RDTreeWidgetItem *parent, const StructuredObjectList &objs,
bool parentIsArray)
{
for(const SDObject *obj : objs)
{
if(obj->type.flags & SDTypeFlags::Hidden)
continue;
QVariant param;
if(parentIsArray)
param = QFormatStr("[%1]").arg(parent->childCount());
else
param = obj->name;
RDTreeWidgetItem *item = new RDTreeWidgetItem({param, QString()});
// we don't identify via the type name as many types could be serialised as a ResourceId -
// e.g. ID3D11Resource* or ID3D11Buffer* which would be the actual typename. We want to preserve
// that for the best raw structured data representation instead of flattening those out to just
// "ResourceId", and we also don't want to store two types ('fake' and 'real'), so instead we
// check the custom string.
if(obj->type.basetype == SDBasic::Resource)
{
ResourceId id;
static_assert(sizeof(id) == sizeof(obj->data.basic.u), "ResourceId is no longer uint64_t!");
memcpy(&id, &obj->data.basic.u, sizeof(id));
param = id;
}
else if(obj->type.flags & SDTypeFlags::NullString)
{
param = lit("NULL");
}
else if(obj->type.flags & SDTypeFlags::HasCustomString)
{
param = obj->data.str;
}
else
{
switch(obj->type.basetype)
{
case SDBasic::Chunk:
case SDBasic::Struct:
param = QFormatStr("%1()").arg(obj->type.name);
addStructuredObjects(item, obj->data.children, false);
break;
case SDBasic::Array:
param = QFormatStr("%1[]").arg(obj->type.name);
addStructuredObjects(item, obj->data.children, true);
break;
case SDBasic::Null: param = lit("NULL"); break;
case SDBasic::Buffer: param = lit("(%1 bytes)").arg(obj->type.byteSize); break;
case SDBasic::String: param = obj->data.str; break;
case SDBasic::Resource:
case SDBasic::Enum:
case SDBasic::UnsignedInteger: param = Formatter::HumanFormat(obj->data.basic.u); break;
case SDBasic::SignedInteger: param = Formatter::Format(obj->data.basic.i); break;
case SDBasic::Float: param = Formatter::Format(obj->data.basic.d); break;
case SDBasic::Boolean: param = (obj->data.basic.b ? lit("True") : lit("False")); break;
case SDBasic::Character: param = QString(QLatin1Char(obj->data.basic.c)); break;
}
}
item->setText(1, param);
parent->addChild(item);
}
}
bool SaveToJSON(QVariantMap &data, QIODevice &f, const char *magicIdentifier, uint32_t magicVersion)
{
// marker that this data is valid
if(magicIdentifier)
data[QString::fromLatin1(magicIdentifier)] = magicVersion;
QJsonDocument doc = QJsonDocument::fromVariant(data);
if(doc.isEmpty() || doc.isNull())
{
qCritical() << "Failed to convert data to JSON document";
return false;
}
QByteArray jsontext = doc.toJson(QJsonDocument::Indented);
qint64 ret = f.write(jsontext);
if(ret != jsontext.size())
{
qCritical() << "Failed to write JSON data: " << ret << " " << f.errorString();
return false;
}
return true;
}
bool LoadFromJSON(QVariantMap &data, QIODevice &f, const char *magicIdentifier, uint32_t magicVersion)
{
QByteArray json = f.readAll();
if(json.isEmpty())
{
qCritical() << "Read invalid empty JSON data from file " << f.errorString();
return false;
}
QJsonDocument doc = QJsonDocument::fromJson(json);
if(doc.isEmpty() || doc.isNull())
{
qCritical() << "Failed to convert file to JSON document";
return false;
}
data = doc.toVariant().toMap();
QString ident = QString::fromLatin1(magicIdentifier);
if(data.isEmpty() || !data.contains(ident))
{
qCritical() << "Converted config data is invalid or unrecognised";
return false;
}
if(data[ident].toUInt() != magicVersion)
{
qCritical() << "Converted config data is not the right version";
return false;
}
return true;
}
QString VariantToJSON(const QVariantMap &data)
{
return QString::fromUtf8(QJsonDocument::fromVariant(data).toJson(QJsonDocument::Indented));
}
QVariantMap JSONToVariant(const QString &json)
{
return QJsonDocument::fromJson(json.toUtf8()).toVariant().toMap();
}
int GUIInvoke::methodIndex = -1;
void GUIInvoke::init()
{
GUIInvoke *invoke = new GUIInvoke(NULL, {});
methodIndex = invoke->metaObject()->indexOfMethod(QMetaObject::normalizedSignature("doInvoke()"));
}
void GUIInvoke::call(QObject *obj, const std::function<void()> &f)
{
if(!obj)
qCritical() << "GUIInvoke::call called with NULL object";
if(onUIThread())
{
if(obj)
f();
return;
}
defer(obj, f);
}
void GUIInvoke::defer(QObject *obj, const std::function<void()> &f)
{
if(!obj)
qCritical() << "GUIInvoke::defer called with NULL object";
GUIInvoke *invoke = new GUIInvoke(obj, f);
invoke->moveToThread(qApp->thread());
invoke->metaObject()->method(methodIndex).invoke(invoke, Qt::QueuedConnection);
}
void GUIInvoke::blockcall(QObject *obj, const std::function<void()> &f)
{
if(!obj)
qCritical() << "GUIInvoke::blockcall called with NULL object";
if(onUIThread())
{
if(obj)
f();
return;
}
GUIInvoke *invoke = new GUIInvoke(obj, f);
invoke->moveToThread(qApp->thread());
invoke->metaObject()->method(methodIndex).invoke(invoke, Qt::BlockingQueuedConnection);
}
bool GUIInvoke::onUIThread()
{
return qApp->thread() == QThread::currentThread();
}
QString RDDialog::DefaultBrowsePath;
const QMessageBox::StandardButtons RDDialog::YesNoCancel =
QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
void RDDialog::show(QMenu *menu, QPoint pos)
{
// menus aren't always visible immediately, so we need to listen for aboutToHide to exit the event
// loop. As a safety precaution because I don't trust the damn signals, if we loop for over a
// second then we'll quit as soon as the menu is not visible
volatile bool menuHiding = false;
auto connection =
QObject::connect(menu, &QMenu::aboutToHide, [&menuHiding]() { menuHiding = true; });
menu->setWindowModality(Qt::ApplicationModal);
menu->popup(pos);
QElapsedTimer elapsed;
elapsed.start();
QEventLoop loop;
for(;;)
{
// stop processing once aboutToHide has been signalled
if(menuHiding)
break;
// stop processing if 1s has passed and the menu isn't visible anymore.
if(elapsed.hasExpired(1000) && !menu->isVisible())
break;
loop.processEvents(QEventLoop::WaitForMoreEvents);
QCoreApplication::sendPostedEvents();
}
QObject::disconnect(connection);
}
int RDDialog::show(QDialog *dialog)
{
// workaround for QTBUG-56382 needed on windows only - it can break on other platforms
#if defined(Q_OS_WIN32)
dialog->setWindowModality(Qt::ApplicationModal);
dialog->show();
QEventLoop loop;
while(dialog->isVisible())
{
loop.processEvents(QEventLoop::WaitForMoreEvents);
QCoreApplication::sendPostedEvents();
}
#else
dialog->exec();
#endif
return dialog->result();
}
QMessageBox::StandardButton RDDialog::messageBox(QMessageBox::Icon icon, QWidget *parent,
const QString &title, const QString &text,
QMessageBox::StandardButtons buttons,
QMessageBox::StandardButton defaultButton)
{
QMessageBox::StandardButton ret = defaultButton;
QObject *parentObj = parent;
if(parentObj == NULL)
{
// for 'global' message boxes with no parents, just use the app as the parent pointer
parentObj = qApp;
}
// if we're already on the right thread, this boils down to a function call
GUIInvoke::blockcall(parentObj, [&]() {
QMessageBox mb(icon, title, text, buttons, parent);
mb.setDefaultButton(defaultButton);
show(&mb);
ret = mb.standardButton(mb.clickedButton());
});
return ret;
}
QMessageBox::StandardButton RDDialog::messageBoxChecked(QMessageBox::Icon icon, QWidget *parent,
const QString &title, const QString &text,
QCheckBox *checkBox, bool &checked,
QMessageBox::StandardButtons buttons,
QMessageBox::StandardButton defaultButton)
{
QMessageBox::StandardButton ret = defaultButton;
// if we're already on the right thread, this boils down to a function call
GUIInvoke::blockcall(parent, [&]() {
QMessageBox mb(icon, title, text, buttons, parent);
mb.setDefaultButton(defaultButton);
mb.setCheckBox(checkBox);
show(&mb);
checked = mb.checkBox()->isChecked();
ret = mb.standardButton(mb.clickedButton());
});
return ret;
}
QString RDDialog::getExistingDirectory(QWidget *parent, const QString &caption, const QString &dir,
QFileDialog::Options options)
{
QFileDialog fd(parent, caption, dir, QString());
fd.setAcceptMode(QFileDialog::AcceptOpen);
fd.setFileMode(QFileDialog::DirectoryOnly);
fd.setOptions(options);
show(&fd);
if(fd.result() == QFileDialog::Accepted)
{
QStringList files = fd.selectedFiles();
if(!files.isEmpty())
return files[0];
}
return QString();
}
QString RDDialog::getOpenFileName(QWidget *parent, const QString &caption, const QString &dir,
const QString &filter, QString *selectedFilter,
QFileDialog::Options options)
{
QString d = dir;
if(d.isEmpty())
d = DefaultBrowsePath;
QFileDialog fd(parent, caption, d, filter);
fd.setFileMode(QFileDialog::ExistingFile);
fd.setAcceptMode(QFileDialog::AcceptOpen);
fd.setOptions(options);
show(&fd);
if(fd.result() == QFileDialog::Accepted)
{
if(selectedFilter)
*selectedFilter = fd.selectedNameFilter();
QStringList files = fd.selectedFiles();
if(!files.isEmpty())
{
DefaultBrowsePath = QFileInfo(files[0]).dir().absolutePath();
return files[0];
}
}
return QString();
}
QString RDDialog::getExecutableFileName(QWidget *parent, const QString &caption, const QString &dir,
QFileDialog::Options options)
{
QString d = dir;
if(d.isEmpty())
d = DefaultBrowsePath;
QString filter;
#if defined(Q_OS_WIN32)
// can't filter by executable bit on windows, but we have extensions
filter = QApplication::translate("RDDialog", "Executables (*.exe);;All Files (*)");
#endif
QFileDialog fd(parent, caption, d, filter);
fd.setOptions(options);
fd.setAcceptMode(QFileDialog::AcceptOpen);
fd.setFileMode(QFileDialog::ExistingFile);
{
QFileFilterModel *fileProxy = new QFileFilterModel(parent);
fileProxy->setRequirePermissions(QDir::Executable);
fd.setProxyModel(fileProxy);
}
show(&fd);
if(fd.result() == QFileDialog::Accepted)
{
QStringList files = fd.selectedFiles();
if(!files.isEmpty())
{
DefaultBrowsePath = QFileInfo(files[0]).dir().absolutePath();
return files[0];
}
}
return QString();
}
static QStringList getDefaultSuffixesFromFilter(const QString &filter)
{
// capture the first suffix found and discard the rest
static const QRegularExpression regex(lit("\\*\\.([\\w.]+).*"));
QStringList suffixes;
for(const QString &s : filter.split(lit(";;")))
{
suffixes << regex.match(s).captured(1);
}
return suffixes;
}
QString RDDialog::getSaveFileName(QWidget *parent, const QString &caption, const QString &dir,
const QString &filter, QString *selectedFilter,
QFileDialog::Options options)
{
QString d = dir;
if(d.isEmpty())
d = DefaultBrowsePath;
QFileDialog fd(parent, caption, d, filter);
fd.setAcceptMode(QFileDialog::AcceptSave);
fd.setOptions(options);
const QStringList &defaultSuffixes = getDefaultSuffixesFromFilter(filter);
if(!defaultSuffixes.isEmpty())
fd.setDefaultSuffix(defaultSuffixes.first());
QObject::connect(&fd, &QFileDialog::filterSelected, [&](const QString &filter) {
int i = fd.nameFilters().indexOf(filter);
fd.setDefaultSuffix(defaultSuffixes.value(i));
});
show(&fd);
if(fd.result() == QFileDialog::Accepted)
{
if(selectedFilter)
*selectedFilter = fd.selectedNameFilter();
QStringList files = fd.selectedFiles();
if(!files.isEmpty())
{
DefaultBrowsePath = QFileInfo(files[0]).dir().absolutePath();
return files[0];
}
}
return QString();
}
bool QFileFilterModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
QModelIndex idx = sourceModel()->index(source_row, 0, source_parent);
QFileSystemModel *fs = qobject_cast<QFileSystemModel *>(sourceModel());
if(!fs)
{
qCritical() << "Expected a QFileSystemModel as the source model!";
return true;
}
if(fs->isDir(idx))
return true;
QFile::Permissions permissions =
(QFile::Permissions)sourceModel()->data(idx, QFileSystemModel::FilePermissions).toInt();
if((m_requireMask & QDir::Readable) && !(permissions & QFile::ReadUser))
return false;
if((m_requireMask & QDir::Writable) && !(permissions & QFile::WriteUser))
return false;
if((m_requireMask & QDir::Executable) && !(permissions & QFile::ExeUser))
return false;
if((m_excludeMask & QDir::Readable) && (permissions & QFile::ReadUser))
return false;
if((m_excludeMask & QDir::Writable) && (permissions & QFile::WriteUser))
return false;
if((m_excludeMask & QDir::Executable) && (permissions & QFile::ExeUser))
return false;
return true;
}
QCollatorSortFilterProxyModel::QCollatorSortFilterProxyModel(QObject *parent)
: QSortFilterProxyModel(parent)
{
m_collator = new QCollator();
}
QCollatorSortFilterProxyModel::~QCollatorSortFilterProxyModel()
{
delete m_collator;
}
bool QCollatorSortFilterProxyModel::lessThan(const QModelIndex &source_left,
const QModelIndex &source_right) const
{
return m_collator->compare(sourceModel()->data(source_left, sortRole()).toString(),
sourceModel()->data(source_right, sortRole()).toString()) < 0;
}
void addGridLines(QGridLayout *grid, QColor gridColor)
{
QString style =
QFormatStr("border: solid #%1%2%3; border-bottom-width: 1px; border-right-width: 1px;")
.arg(gridColor.red(), 2, 16, QLatin1Char('0'))
.arg(gridColor.green(), 2, 16, QLatin1Char('0'))
.arg(gridColor.blue(), 2, 16, QLatin1Char('0'));
for(int y = 0; y < grid->rowCount(); y++)
{
for(int x = 0; x < grid->columnCount(); x++)
{
QLayoutItem *item = grid->itemAtPosition(y, x);
if(item == NULL)
continue;
QWidget *w = item->widget();
if(w == NULL)
continue;
QString cellStyle = style;
if(x == 0)
cellStyle += lit("border-left-width: 1px;");
if(y == 0)
cellStyle += lit("border-top-width: 1px;");
w->setStyleSheet(cellStyle);
}
}
}
int Formatter::m_minFigures = 2, Formatter::m_maxFigures = 5, Formatter::m_expNegCutoff = 5,
Formatter::m_expPosCutoff = 7;
double Formatter::m_expNegValue = 0.00001; // 10^(-5)
double Formatter::m_expPosValue = 10000000.0; // 10^7
QFont *Formatter::m_Font = NULL;
QColor Formatter::m_DarkChecker, Formatter::m_LightChecker;
void Formatter::setParams(const PersistantConfig &config)
{
m_minFigures = qMax(0, config.Formatter_MinFigures);
m_maxFigures = qMax(2, config.Formatter_MaxFigures);
m_expNegCutoff = qMax(0, config.Formatter_NegExp);
m_expPosCutoff = qMax(0, config.Formatter_PosExp);
m_expNegValue = qPow(10.0, -config.Formatter_NegExp);
m_expPosValue = qPow(10.0, config.Formatter_PosExp);
if(!m_Font)
m_Font = new QFont();
*m_Font =
config.Font_PreferMonospaced ? QFontDatabase::systemFont(QFontDatabase::FixedFont) : QFont();
Formatter::setPalette(QApplication::palette());
}
void Formatter::setPalette(QPalette palette)
{
m_DarkChecker = palette.color(QPalette::Mid);
m_LightChecker = m_DarkChecker.lighter(150);
RENDERDOC_SetColors(m_DarkChecker, m_LightChecker, IsDarkTheme());
}
void Formatter::shutdown()
{
delete m_Font;
}
QString Formatter::Format(double f, bool)
{
if(f != 0.0 && (qAbs(f) < m_expNegValue || qAbs(f) > m_expPosValue))
return QFormatStr("%1").arg(f, -m_minFigures, 'E', m_maxFigures);
QString ret = QFormatStr("%1").arg(f, 0, 'f', m_maxFigures);
// trim excess trailing 0s
int decimal = ret.lastIndexOf(QLatin1Char('.'));
if(decimal > 0)
{
decimal += m_minFigures;
const int len = ret.count();
int remove = 0;
while(len - remove - 1 > decimal && ret.at(len - remove - 1) == QLatin1Char('0'))
remove++;
if(remove > 0)
ret.chop(remove);
}
return ret;
}
QString Formatter::HumanFormat(uint64_t u)
{
if(u == UINT16_MAX)
return lit("UINT16_MAX");
if(u == UINT32_MAX)
return lit("UINT32_MAX");
if(u == UINT64_MAX)
return lit("UINT64_MAX");
// format as hex when over a certain threshold
if(u > 0xffffff)
return lit("0x") + Format(u, true);
return Format(u);
}
class RDProgressDialog : public QProgressDialog
{
public:
RDProgressDialog(const QString &labelText, QWidget *parent)
// we add 1 so that the progress value never hits maximum until we are actually finished
: QProgressDialog(labelText, QString(), 0, maxProgress + 1, parent),
m_Label(this)
{
setWindowTitle(tr("Please Wait"));
setWindowFlags(Qt::CustomizeWindowHint | Qt::Dialog | Qt::WindowTitleHint);
setWindowIcon(QIcon());
setMinimumSize(QSize(250, 0));
setMaximumSize(QSize(250, 10000));
setCancelButton(NULL);
setMinimumDuration(0);
setWindowModality(Qt::ApplicationModal);
setValue(0);
m_Label.setText(labelText);
m_Label.setAlignment(Qt::AlignCenter);
m_Label.setWordWrap(true);
setLabel(&m_Label);
}
void setPercentage(float percent) { setValue(int(maxProgress * percent)); }
void setInfinite(bool infinite)
{
if(infinite)
{
setMinimum(0);
setMaximum(0);
setValue(0);
}
else
{
setMinimum(0);
setMaximum(maxProgress + 1);
setValue(0);
}
}
void closeAndReset()
{
setValue(maxProgress);
hide();
reset();
}
protected:
void keyPressEvent(QKeyEvent *e) override
{
if(e->key() == Qt::Key_Escape)
return;
QProgressDialog::keyPressEvent(e);
}
QLabel m_Label;
static const int maxProgress = 1000;
};
#if defined(Q_OS_WIN32)
#include <windows.h>
#include <shellapi.h>
typedef LSTATUS(APIENTRY *PFN_RegCreateKeyExA)(HKEY hKey, LPCSTR lpSubKey, DWORD Reserved,
LPSTR lpClass, DWORD dwOptions, REGSAM samDesired,
CONST LPSECURITY_ATTRIBUTES lpSecurityAttributes,
PHKEY phkResult, LPDWORD lpdwDisposition);
typedef LSTATUS(APIENTRY *PFN_RegCloseKey)(HKEY hKey);
#else
#include <unistd.h>
#endif
bool IsRunningAsAdmin()
{
#if defined(Q_OS_WIN32)
// try to open HKLM\Software for write.
HKEY key = NULL;
// access dynamically to get around the pain of trying to link to extra window libs in qt
HMODULE mod = LoadLibraryA("advapi32.dll");
if(mod == NULL)
return false;
PFN_RegCreateKeyExA create = (PFN_RegCreateKeyExA)GetProcAddress(mod, "RegCreateKeyExA");
PFN_RegCloseKey close = (PFN_RegCloseKey)GetProcAddress(mod, "RegCloseKey");
LSTATUS ret = ERROR_PROC_NOT_FOUND;
if(create && close)
{
ret = create(HKEY_LOCAL_MACHINE, "SOFTWARE", 0, NULL, 0, KEY_READ | KEY_WRITE, NULL, &key, NULL);
if(key)
close(key);
}
FreeLibrary(mod);
return (ret == ERROR_SUCCESS);
#else
// this isn't ideal, we should check something else since a user may have permissions to do what
// we want to do
return geteuid() == 0;
#endif
}
bool RunProcessAsAdmin(const QString &fullExecutablePath, const QStringList &params,
QWidget *parent, std::function<void()> finishedCallback)
{
#if defined(Q_OS_WIN32)
std::wstring wideExe = QDir::toNativeSeparators(fullExecutablePath).toStdWString();
std::wstring wideParams;
for(QString p : params)
{
wideParams += L"\"";
wideParams += p.toStdWString();
wideParams += L"\" ";
}
SHELLEXECUTEINFOW info = {};
info.cbSize = sizeof(info);
info.fMask = SEE_MASK_NOCLOSEPROCESS;
info.lpVerb = L"runas";
info.lpFile = wideExe.c_str();
info.lpParameters = wideParams.c_str();
info.nShow = SW_SHOWNORMAL;
ShellExecuteExW(&info);
if((uintptr_t)info.hInstApp > 32 && info.hProcess != NULL)
{
if(finishedCallback)
{
HANDLE h = info.hProcess;
// do the wait on another thread
LambdaThread *thread = new LambdaThread([h, parent, finishedCallback]() {
WaitForSingleObject(h, 30000);
CloseHandle(h);
GUIInvoke::call(parent, finishedCallback);
});
thread->selfDelete(true);
thread->start();
}
else
{
CloseHandle(info.hProcess);
}
return true;
}
return false;
#else
// try to find a way to run the application elevated.
const QString graphicalSudo[] = {
lit("pkexec"), lit("kdesudo"), lit("gksudo"), lit("beesu"),
};
// if none of the graphical options, then look for sudo and either
const QString termEmulator[] = {
lit("x-terminal-emulator"), lit("gnome-terminal"), lit("konsole"), lit("xterm"),
};
for(const QString &sudo : graphicalSudo)
{
QString inPath = QStandardPaths::findExecutable(sudo);
// can't find in path
if(inPath.isEmpty())
continue;
QProcess *process = new QProcess;
QStringList sudoParams;
// these programs need a -- to indicate the end of their options, before the program
if(sudo == lit("kdesudo") || sudo == lit("gksudo"))
sudoParams << lit("--");
sudoParams << fullExecutablePath;
for(const QString &p : params)
sudoParams << p;
qInfo() << "Running" << sudo << "with params" << sudoParams;
// run with sudo
process->start(sudo, sudoParams);
// when the process exits, call the callback and delete
QObject::connect(process, OverloadedSlot<int>::of(&QProcess::finished),
[parent, process, finishedCallback](int exitCode) {
process->deleteLater();
GUIInvoke::call(parent, finishedCallback);
});
return true;
}
QString sudo = QStandardPaths::findExecutable(lit("sudo"));
if(sudo.isEmpty())
{
qCritical() << "Couldn't find graphical or terminal sudo program!\n"
<< "Please run " << fullExecutablePath << "with args" << params << "manually.";
return false;
}
for(const QString &term : termEmulator)
{
QString inPath = QStandardPaths::findExecutable(term);
// can't find in path
if(inPath.isEmpty())
continue;
QProcess *process = new QProcess;
// run terminal sudo with emulator
QStringList termParams;
termParams
<< lit("-e")
<< lit("bash -c 'sudo %1 %2'").arg(fullExecutablePath).arg(params.join(QLatin1Char(' ')));
process->start(term, termParams);
// when the process exits, call the callback and delete
QObject::connect(process, OverloadedSlot<int>::of(&QProcess::finished),
[parent, process, finishedCallback](int exitCode) {
process->deleteLater();
GUIInvoke::call(parent, finishedCallback);
});
return true;
}
qCritical() << "Couldn't find graphical or terminal emulator to launch sudo.\n"
<< "Please run " << fullExecutablePath << "with args" << params << "manually.";
return false;
#endif
}
void RevealFilenameInExternalFileBrowser(const QString &filePath)
{
#if defined(Q_OS_WIN32)
// on windows we can ask explorer to highlight the exact file.
QProcess::startDetached(lit("explorer.exe"), QStringList() << lit("/select,")
<< QDir::toNativeSeparators(filePath));
#else
// on all other platforms, we just use QDesktopServices to invoke the external file browser on the
// directory and hope that's close enough.
QDesktopServices::openUrl(QFileInfo(filePath).absoluteDir().absolutePath());
#endif
}
QStringList ParseArgsList(const QString &args)
{
QStringList ret;
if(args.isEmpty())
return ret;
// on windows just use the function provided by the system
#if defined(Q_OS_WIN32)
std::wstring wargs = args.toStdWString();
int argc = 0;
wchar_t **argv = CommandLineToArgvW(wargs.c_str(), &argc);
for(int i = 0; i < argc; i++)
ret << QString::fromWCharArray(argv[i]);
LocalFree(argv);
#else
std::string argString = args.toStdString();
// perform some kind of sane parsing
bool dquot = false, squot = false; // are we inside ''s or ""s
// current character
char *c = &argString[0];
// current argument we're building
std::string a;
while(*c)
{
if(!dquot && !squot && (*c == ' ' || *c == '\t'))
{
if(!a.empty())
ret << QString::fromStdString(a);
a = "";
}
else if(!dquot && *c == '"')
{
dquot = true;
}
else if(!squot && *c == '\'')
{
squot = true;
}
else if(dquot && *c == '"')
{
dquot = false;
}
else if(squot && *c == '\'')
{
squot = false;
}
else if(squot)
{
// single quotes don't escape, just copy literally until we leave single quote mode
a.push_back(*c);
}
else if(dquot)
{
// handle escaping
if(*c == '\\')
{
c++;
if(*c)
{
a.push_back(*c);
}
else
{
qCritical() << "Malformed args list:" << args;
return ret;
}
}
else
{
a.push_back(*c);
}
}
else
{
a.push_back(*c);
}
c++;
}
// if we were building an argument when we hit the end of the string
if(!a.empty())
ret << QString::fromStdString(a);
#endif
return ret;
}
void ShowProgressDialog(QWidget *window, const QString &labelText, ProgressFinishedMethod finished,
ProgressUpdateMethod update)
{
RDProgressDialog dialog(labelText, window);
// if we don't have an update function, set the progress display to be 'infinite spinner'
dialog.setInfinite(!update);
QSemaphore tickerSemaphore(1);
// start a lambda thread to tick our functions and close the progress dialog when we're done.
LambdaThread progressTickerThread([finished, update, &dialog, &tickerSemaphore]() {
while(tickerSemaphore.available())
{
QThread::msleep(30);
if(update)
GUIInvoke::call(&dialog, [update, &dialog]() { dialog.setPercentage(update()); });
GUIInvoke::call(&dialog, [finished, &tickerSemaphore]() {
if(finished())
tickerSemaphore.tryAcquire();
});
}
GUIInvoke::call(&dialog, [&dialog]() { dialog.closeAndReset(); });
});
progressTickerThread.start();
// show the dialog
RDDialog::show(&dialog);
// signal the thread to exit if somehow we got here without it finishing, then wait for it thread
// to clean itself up
tickerSemaphore.tryAcquire();
progressTickerThread.wait();
}
void UpdateTransferProgress(qint64 xfer, qint64 total, QElapsedTimer *timer,
QProgressBar *progressBar, QLabel *progressLabel, QString progressText)
{
if(xfer >= total)
{
progressBar->setMaximum(10000);
progressBar->setValue(10000);
return;
}
if(total <= 0)
{
progressBar->setMaximum(10000);
progressBar->setValue(0);
return;
}
progressBar->setMaximum(10000);
progressBar->setValue(int(10000.0 * (double(xfer) / double(total))));
double xferMB = double(xfer) / 1000000.0;
double totalMB = double(total) / 1000000.0;
double secondsElapsed = double(timer->nsecsElapsed()) * 1.0e-9;
double speedMBS = xferMB / secondsElapsed;
qulonglong secondsRemaining = qulonglong(double(totalMB - xferMB) / speedMBS);
if(secondsElapsed > 1.0)
{
QString remainString;
qulonglong minutesRemaining = (secondsRemaining / 60) % 60;
qulonglong hoursRemaining = (secondsRemaining / 3600);
secondsRemaining %= 60;
if(hoursRemaining > 0)
remainString = QFormatStr("%1:%2:%3")
.arg(hoursRemaining, 2, 10, QLatin1Char('0'))
.arg(minutesRemaining, 2, 10, QLatin1Char('0'))
.arg(secondsRemaining, 2, 10, QLatin1Char('0'));
else if(minutesRemaining > 0)
remainString = QFormatStr("%1:%2")
.arg(minutesRemaining, 2, 10, QLatin1Char('0'))
.arg(secondsRemaining, 2, 10, QLatin1Char('0'));
else
remainString = QApplication::translate("qrenderdoc", "%1 seconds").arg(secondsRemaining);
double speed = speedMBS;
bool MBs = true;
if(speedMBS < 1)
{
MBs = false;
speed *= 1000;
}
progressLabel->setText(
QApplication::translate("qrenderdoc", "%1\n%2 MB / %3 MB. %4 remaining (%5 %6)")
.arg(progressText)
.arg(xferMB, 0, 'f', 2)
.arg(totalMB, 0, 'f', 2)
.arg(remainString)
.arg(speed, 0, 'f', 2)
.arg(MBs ? lit("MB/s") : lit("KB/s")));
}
}
void setEnabledMultiple(const QList<QWidget *> &widgets, bool enabled)
{
for(QWidget *w : widgets)
w->setEnabled(enabled);
}
QString GetSystemUsername()
{
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
QString username = env.value(lit("USER"));
if(username == QString())
username = env.value(lit("USERNAME"));
if(username == QString())
username = lit("Unknown_User");
return username;
}
void BringToForeground(QWidget *window)
{
#ifdef Q_OS_WIN
SetWindowPos((HWND)window->winId(), HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
window->setWindowState(Qt::WindowActive);
window->raise();
window->showNormal();
window->show();
SetWindowPos((HWND)window->winId(), HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
#else
window->activateWindow();
window->raise();
window->showNormal();
#endif
}
bool IsDarkTheme()
{
float baseLum = getLuminance(QApplication::palette().color(QPalette::Base));
float textLum = getLuminance(QApplication::palette().color(QPalette::Text));
// if the base is dark than the text, then it's a light-on-dark theme (aka dark theme)
return (baseLum < textLum);
}
float getLuminance(const QColor &col)
{
return (float)(0.2126 * qPow(col.redF(), 2.2) + 0.7152 * qPow(col.greenF(), 2.2) +
0.0722 * qPow(col.blueF(), 2.2));
}
QColor contrastingColor(const QColor &col, const QColor &defaultCol)
{
float backLum = getLuminance(col);
float textLum = getLuminance(defaultCol);
bool backDark = backLum < 0.2f;
bool textDark = textLum < 0.2f;
// if they're contrasting, use the text colour desired
if(backDark != textDark)
return defaultCol;
// otherwise pick a contrasting colour
if(backDark)
return QColor(Qt::white);
else
return QColor(Qt::black);
}