Extract out most code for text-with-ResourceId-links from RDTreeWidget

* The RDTreeWidget items still use this code, but it can now be reused
  on other widgets like RDLabel.
This commit is contained in:
baldurk
2017-11-21 14:45:22 +00:00
parent ec8a4e82fb
commit 32a74e20e1
4 changed files with 368 additions and 328 deletions
+271
View File
@@ -23,6 +23,7 @@
******************************************************************************/
#include "QRDUtils.h"
#include <QAbstractTextDocumentLayout>
#include <QApplication>
#include <QElapsedTimer>
#include <QFileSystemModel>
@@ -34,11 +35,15 @@
#include <QLabel>
#include <QMenu>
#include <QMetaMethod>
#include <QMouseEvent>
#include <QPainter>
#include <QProcess>
#include <QProgressDialog>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
#include <QStandardPaths>
#include <QTextBlock>
#include <QTextDocument>
#include <QtMath>
#include "Widgets/Extended/RDTreeWidget.h"
@@ -60,6 +65,272 @@ std::string DoStringise(const ResourceId &el)
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;
// cache the context once we've obtained it.
ICaptureContext *ctxptr = NULL;
void cacheDocument(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;
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 = ctx.GetResourceName(v.value<ResourceId>());
html += lit("<td><b>%1</b></td><td><img src=':/link.png'></td>").arg(resname);
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());
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]);
}
};
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(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(rect.width());
linkedText->doc.setDefaultFont(font);
linkedText->doc.drawContents(painter);
if(mouseOver)
{
painter->setPen(QPen(palette.brush(QPalette::WindowText), 1.0));
QAbstractTextDocumentLayout *layout = linkedText->doc.documentLayout();
QPoint p = mousePos - rect.topLeft();
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);
painter->drawLine(blockrect.bottomLeft(), blockrect.bottomRight());
}
}
}
}
}
int RichResourceTextWidthHint(QWidget *owner, const QVariant &var)
{
RichResourceTextPtr linkedText = var.value<RichResourceTextPtr>();
linkedText->cacheDocument(owner);
return linkedText->doc.idealWidth();
}
bool RichResourceTextMouseEvent(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();
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)
+41
View File
@@ -31,6 +31,7 @@
#include <QMessageBox>
#include <QProcess>
#include <QSemaphore>
#include <QSharedPointer>
#include <QSortFilterProxyModel>
#include <QStyledItemDelegate>
#include "Code/Interface/QRDInterface.h"
@@ -107,6 +108,46 @@ class RDTreeWidgetItem;
void addStructuredObjects(RDTreeWidgetItem *parent, const StructuredObjectList &objs,
bool parentIsArray);
// this is an opaque struct that contains the data to render, hit-test, etc for some text that
// contains links to resources. It will update and cache the names of the resources.
struct RichResourceText;
// we use QSharedPointer to refer to the text since the lifetime management of these objects would
// get quite complicated. There's not necessarily an obvious QObject parent to assign to if the text
// is being initialised before being assigned to a widget and we want the most seamless interface we
// can get.
typedef QSharedPointer<RichResourceText> RichResourceTextPtr;
// this will check the variant, and if it contains a ResourceId directly or text with ResourceId
// identifiers then it will be converted into a RichResourceTextPtr in-place. The new QVariant will
// still convert to QString so it doesn't have to be special-cased. However it must be processed
// through one of the functions below (generally painting) to cache the rendered text.
// If the variant doesn't match the above conditions, it's unchanged. So it's safe to apply this
// reasonably liberally.
// NOTE: It is not possible to move a RichResourceText instance from one ICaptureContext to another
// as the pointer is cached internally. Instead you should delete the old and re-initialise from
// scratch.
void RichResourceTextInitialise(QVariant &var);
// checks if a variant is rich resource text
bool RichResourceTextCheck(const QVariant &var);
// paint the given variant containing rich text with the given parameters.
void RichResourceTextPaint(QWidget *owner, QPainter *painter, QRect rect, QFont font,
QPalette palette, bool mouseOver, QPoint mousePos, const QVariant &var);
// gives the width for a size hint for the rich text (since it might be larger than the original
// text)
int RichResourceTextWidthHint(QWidget *owner, const QVariant &var);
// handle a mouse event on some rich resource text.
// returns true if the event is processed - for mouse move events, this means that the mouse is over
// a resource link (which can be used to change the cursor to a pointing hand, for example).
bool RichResourceTextMouseEvent(QWidget *owner, const QVariant &var, QRect rect, QMouseEvent *event);
// register runtime conversions for custom Qt metatypes
void RegisterMetatypeConversions();
struct Formatter
{
static void setParams(const PersistantConfig &config);
+2
View File
@@ -179,6 +179,8 @@ int main(int argc, char *argv[])
QApplication application(argc, argv);
RegisterMetatypeConversions();
{
PersistantConfig config;