-
Notifications
You must be signed in to change notification settings - Fork 45
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add full file visual preview with navigation
- Loading branch information
1 parent
727a6f1
commit f61210b
Showing
11 changed files
with
316 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
#include "actionrenderprogress.h" | ||
|
||
ActionRenderProgress::ActionRenderProgress() : | ||
ActionProgress() | ||
{ | ||
|
||
} | ||
|
||
void ActionRenderProgress::setRenderPreview(QImage preview) | ||
{ | ||
QMutexLocker locker(&m_mutex); | ||
|
||
m_preview = preview; | ||
emit renderPreviewChanged(m_preview); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
#ifndef ACTIONRENDERPROGRESS_H | ||
#define ACTIONRENDERPROGRESS_H | ||
|
||
#include "actionprogress.h" | ||
#include <QImage> | ||
|
||
class HOBBITSCORESHARED_EXPORT ActionRenderProgress : public ActionProgress | ||
{ | ||
Q_OBJECT | ||
public: | ||
ActionRenderProgress(); | ||
|
||
void setRenderPreview(QImage preview); | ||
|
||
signals: | ||
void renderPreviewChanged(const QImage&); | ||
|
||
private: | ||
QImage m_preview; | ||
}; | ||
|
||
#endif // ACTIONRENDERPROGRESS_H |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
#include "previewscrollbar.h" | ||
#include <QPainter> | ||
#include <QtConcurrent/QtConcurrentRun> | ||
#include "settingsmanager.h" | ||
#include <QMouseEvent> | ||
|
||
PreviewScrollBar::PreviewScrollBar(QWidget *parent) : QWidget(parent) | ||
{ | ||
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); | ||
setMinimumWidth(50); | ||
} | ||
|
||
int PreviewScrollBar::getFrameOffset() | ||
{ | ||
return m_frameOffset; | ||
} | ||
|
||
void PreviewScrollBar::paintEvent(QPaintEvent *event) | ||
{ | ||
QPainter painter(this); | ||
painter.setRenderHint(QPainter::Antialiasing, true); | ||
painter.fillRect(QRect(QPoint(0,0), this->size()), Qt::black); | ||
|
||
if (m_manager.isNull() || m_manager->getCurrentContainer().isNull()) { | ||
return; | ||
} | ||
|
||
auto container = m_manager->getCurrentContainer(); | ||
auto containerPtr = reinterpret_cast<quint64>(container.data()); | ||
|
||
if (!m_imageCache.contains(containerPtr)) { | ||
if (!m_renderWatchers.contains(container)) { | ||
QSharedPointer<ActionRenderProgress> progress(new ActionRenderProgress()); | ||
QFuture<QImage> future = QtConcurrent::run(QThreadPool::globalInstance(), | ||
PreviewScrollBar::renderPreview, | ||
container, progress); | ||
|
||
connect(progress.data(), &ActionRenderProgress::renderPreviewChanged, this, [containerPtr, this](const QImage& preview) { | ||
m_imageCache.remove(containerPtr); | ||
m_imageCache.insert(containerPtr, QImage(preview)); | ||
this->repaint(); | ||
}, Qt::QueuedConnection); | ||
|
||
auto actionWatcher = QSharedPointer<ActionWatcher<QImage>>( | ||
new ActionWatcher<QImage>(future, progress)); | ||
|
||
m_renderWatchers.insert(container, actionWatcher); | ||
connect(actionWatcher->watcher(), SIGNAL(finished()), this, SLOT(checkRenderWatchers())); | ||
} | ||
} | ||
else { | ||
QImage preview = m_imageCache.value(containerPtr); | ||
painter.drawImage(QRect(QPoint(0,0), this->size()), preview); | ||
} | ||
|
||
// draw frame offset | ||
painter.setCompositionMode(QPainter::CompositionMode_Difference); | ||
painter.setPen(QPen(QBrush(Qt::white), 2)); | ||
int pos = int(this->height() * double(m_frameOffset) / double(container->frames().size())); | ||
painter.drawLine(0, pos, this->width(), pos); | ||
} | ||
|
||
void PreviewScrollBar::mouseMoveEvent(QMouseEvent *event) | ||
{ | ||
if (m_mousePressing) { | ||
getOffsetFromEvent(event); | ||
} | ||
} | ||
|
||
void PreviewScrollBar::mousePressEvent(QMouseEvent *event) | ||
{ | ||
m_mousePressing = true; | ||
getOffsetFromEvent(event); | ||
} | ||
|
||
void PreviewScrollBar::mouseReleaseEvent(QMouseEvent *event) | ||
{ | ||
m_mousePressing = false; | ||
} | ||
|
||
void PreviewScrollBar::leaveEvent(QEvent *event) | ||
{ | ||
m_mousePressing = false; | ||
} | ||
|
||
|
||
void PreviewScrollBar::getOffsetFromEvent(QMouseEvent* event) | ||
{ | ||
if (m_manager.isNull() || m_manager->getCurrentContainer().isNull()) { | ||
return; | ||
} | ||
double percent = double(event->y()) / this->height(); | ||
setFrameOffset(int(double(m_manager->getCurrentContainer()->frames().size()) * percent)); | ||
} | ||
|
||
void PreviewScrollBar::setFrameOffset(int offset) | ||
{ | ||
if (offset != m_frameOffset) { | ||
m_frameOffset = offset; | ||
if (!m_displayHandle.isNull() && m_frameOffset != m_displayHandle->getFrameOffset()) { | ||
m_displayHandle->setOffsets(m_displayHandle->getBitOffset(), m_frameOffset); | ||
} | ||
emit frameOffsetChanged(m_frameOffset); | ||
repaint(); | ||
} | ||
} | ||
|
||
void PreviewScrollBar::setBitContainerManager(QSharedPointer<BitContainerManager> manager) | ||
{ | ||
if (!m_manager.isNull()) { | ||
disconnect(m_manager.data(), SIGNAL(currSelectionChanged(QSharedPointer<BitContainer>, QSharedPointer<BitContainer>)), this, SLOT(repaint())); | ||
} | ||
|
||
m_manager = manager; | ||
|
||
connect(m_manager.data(), SIGNAL(currSelectionChanged(QSharedPointer<BitContainer>, QSharedPointer<BitContainer>)), this, SLOT(repaint())); | ||
|
||
repaint(); | ||
} | ||
|
||
|
||
void PreviewScrollBar::setDisplayHandle(QSharedPointer<DisplayHandle> displayHandle) | ||
{ | ||
if (!m_displayHandle.isNull()) { | ||
disconnect(m_displayHandle.data(), &DisplayHandle::newOffsets, this, &PreviewScrollBar::checkDisplayHandleOffset); | ||
} | ||
|
||
m_displayHandle = displayHandle; | ||
|
||
connect(m_displayHandle.data(), &DisplayHandle::newOffsets, this, &PreviewScrollBar::checkDisplayHandleOffset); | ||
checkDisplayHandleOffset(); | ||
} | ||
|
||
void PreviewScrollBar::checkDisplayHandleOffset() | ||
{ | ||
if (m_displayHandle.isNull()) { | ||
return; | ||
} | ||
|
||
setFrameOffset(m_displayHandle->getFrameOffset()); | ||
} | ||
|
||
void PreviewScrollBar::newContainer() | ||
{ | ||
repaint(); | ||
} | ||
|
||
void PreviewScrollBar::checkRenderWatchers() | ||
{ | ||
QList<QSharedPointer<BitContainer>> removals; | ||
for (auto container: m_renderWatchers.keys()) { | ||
if (m_renderWatchers.value(container)->watcher()->isFinished()) { | ||
auto containerPtr = reinterpret_cast<quint64>(container.data()); | ||
removals.append(container); | ||
m_imageCache.remove(containerPtr); | ||
m_imageCache.insert(containerPtr, m_renderWatchers.value(container)->result()); | ||
} | ||
} | ||
|
||
for (auto removal : removals) { | ||
m_renderWatchers.remove(removal); | ||
} | ||
|
||
// TODO: clean up cache using weak ref checks and least-recently-used size limit | ||
|
||
repaint(); | ||
} | ||
|
||
QImage PreviewScrollBar::renderPreview(QSharedPointer<BitContainer> container, QSharedPointer<ActionRenderProgress> progress) | ||
{ | ||
int width = int(container->bitInfo()->maxFrameWidth() / 8); | ||
int height = qMin(10000, container->frames().size()); | ||
QImage image(width, height, QImage::Format_ARGB32); | ||
image.fill(qRgb(50, 50, 90)); | ||
QPainter imagePainter(&image); | ||
imagePainter.setRenderHint(QPainter::Antialiasing, true); | ||
|
||
QColor c = SettingsManager::getInstance().getUiSetting(SettingsData::BYTE_HUE_SAT_KEY).value<QColor>(); | ||
int hue = c.hue(); | ||
int saturation = c.saturation(); | ||
int chunkSize = qMin(container->frames().size(), 10000); | ||
double chunkHeightRatio = double(chunkSize)/double(container->frames().size()); | ||
double targetChunkHeight = chunkHeightRatio * height; | ||
QImage bufferChunk(width, chunkSize, QImage::Format_ARGB32); | ||
for (int frame = 0; frame < container->frames().size(); frame += chunkSize) { | ||
bufferChunk.fill(qRgb(0, 0, 0)); | ||
int offset = 0; | ||
for (; offset < chunkSize && offset + frame < container->frames().size(); offset++) { | ||
Frame f = container->frames().at(offset + frame); | ||
qint64 byteOffset = f.start()/8; | ||
for (int i = 0; i < f.size()/8 && byteOffset + i < container->bits()->sizeInBytes(); i++) { | ||
char byteValue = container->bits()->byteAt(byteOffset + i); | ||
c.setHsl(hue, saturation, reinterpret_cast<quint8&>(byteValue)); | ||
bufferChunk.setPixel(i, offset, c.rgba()); | ||
} | ||
} | ||
double heightRatio = double(offset)/double(chunkSize); | ||
QRectF target(0, targetChunkHeight * (frame / chunkSize), width, heightRatio * targetChunkHeight); | ||
QRectF source(0, 0, width, offset); | ||
imagePainter.drawImage(target, bufferChunk, source); | ||
progress->setRenderPreview(image); | ||
} | ||
|
||
return image; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
#ifndef PREVIEWSCROLLBAR_H | ||
#define PREVIEWSCROLLBAR_H | ||
|
||
#include <QWidget> | ||
#include <QFuture> | ||
#include "bitcontainermanager.h" | ||
#include "actionwatcher.h" | ||
#include "actionrenderprogress.h" | ||
#include "displayhandle.h" | ||
|
||
class HOBBITSCORESHARED_EXPORT PreviewScrollBar : public QWidget | ||
{ | ||
Q_OBJECT | ||
public: | ||
explicit PreviewScrollBar(QWidget *parent = nullptr); | ||
int getFrameOffset(); | ||
|
||
void paintEvent(QPaintEvent *event) override; | ||
void mouseMoveEvent(QMouseEvent *event) override; | ||
void mousePressEvent(QMouseEvent *event) override; | ||
void mouseReleaseEvent(QMouseEvent *event) override; | ||
void leaveEvent(QEvent *event) override; | ||
|
||
signals: | ||
void frameOffsetChanged(int); | ||
|
||
public slots: | ||
void setFrameOffset(int); | ||
void setBitContainerManager(QSharedPointer<BitContainerManager> manager); | ||
void setDisplayHandle(QSharedPointer<DisplayHandle> displayHandle); | ||
|
||
private slots: | ||
void newContainer(); | ||
void checkRenderWatchers(); | ||
void checkDisplayHandleOffset(); | ||
|
||
private: | ||
void getOffsetFromEvent(QMouseEvent* event); | ||
static QImage renderPreview(QSharedPointer<BitContainer> bits, QSharedPointer<ActionRenderProgress> progress); | ||
|
||
int m_frameOffset = 0; | ||
bool m_mousePressing = false; | ||
QSharedPointer<BitContainerManager> m_manager; | ||
QSharedPointer<DisplayHandle> m_displayHandle; | ||
|
||
QHash<quint64, QWeakPointer<BitContainer>> m_weakRefMap; | ||
QHash<quint64, QImage> m_imageCache; | ||
|
||
QHash<QSharedPointer<BitContainer>, QSharedPointer<ActionWatcher<QImage>>> m_renderWatchers; | ||
}; | ||
|
||
#endif // PREVIEWSCROLLBAR_H |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.