diff options
author | RazrFalcon <razrfalcon@gmail.com> | 2019-01-03 14:10:12 +0200 |
---|---|---|
committer | RazrFalcon <razrfalcon@gmail.com> | 2019-01-03 14:10:12 +0200 |
commit | cf365efd4ab280226f6b2f388f1620ea88f72d1d (patch) | |
tree | 758bf4e1acaaecab37a7f90120129d179c486bb6 /tools/viewsvg | |
parent | 3afab96590fe632c7912b3748c37d25e47c2db3d (diff) |
Replace examples/qt-demo with tools/viewsvg.
Diffstat (limited to 'tools/viewsvg')
-rw-r--r-- | tools/viewsvg/README.md | 21 | ||||
-rw-r--r-- | tools/viewsvg/main.cpp | 15 | ||||
-rw-r--r-- | tools/viewsvg/mainwindow.cpp | 59 | ||||
-rw-r--r-- | tools/viewsvg/mainwindow.h | 23 | ||||
-rw-r--r-- | tools/viewsvg/mainwindow.ui | 193 | ||||
-rw-r--r-- | tools/viewsvg/svgview.cpp | 314 | ||||
-rw-r--r-- | tools/viewsvg/svgview.h | 90 | ||||
-rw-r--r-- | tools/viewsvg/viewsvg.pro | 23 |
8 files changed, 738 insertions, 0 deletions
diff --git a/tools/viewsvg/README.md b/tools/viewsvg/README.md new file mode 100644 index 0000000..957f55b --- /dev/null +++ b/tools/viewsvg/README.md @@ -0,0 +1,21 @@ +# viewsvg + +A simple SVG viewer using Qt and *resvg* C-API. + +## Dependencies + +- Qt >= 5.6 + +## Run + +```bash +# build C-API first +cargo build --release --features "qt-backend" --manifest-path ../../capi/Cargo.toml +# build viewsvg +qmake +make +# run +LD_LIBRARY_PATH=../../target/release ./viewsvg +``` + +See [BUILD.adoc](../../BUILD.adoc) for details. diff --git a/tools/viewsvg/main.cpp b/tools/viewsvg/main.cpp new file mode 100644 index 0000000..f900905 --- /dev/null +++ b/tools/viewsvg/main.cpp @@ -0,0 +1,15 @@ +#include <QApplication> + +#include "mainwindow.h" + +int main(int argc, char *argv[]) +{ + QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + + QApplication a(argc, argv); + + MainWindow w; + w.show(); + + return a.exec(); +} diff --git a/tools/viewsvg/mainwindow.cpp b/tools/viewsvg/mainwindow.cpp new file mode 100644 index 0000000..10215b8 --- /dev/null +++ b/tools/viewsvg/mainwindow.cpp @@ -0,0 +1,59 @@ +#include <QMessageBox> +#include <QTimer> +#include <QFile> + +#include "mainwindow.h" +#include "ui_mainwindow.h" + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::MainWindow) +{ + ui->setupUi(this); + + SvgView::init(); + + ui->cmbBoxSize->setCurrentIndex(1); + ui->cmbBoxBackground->setCurrentIndex(1); + + ui->svgView->setFitToView(true); + ui->svgView->setBackgound(SvgView::Backgound::White); + + connect(ui->svgView, &SvgView::loadError, this, [this](const QString &msg){ + QMessageBox::critical(this, "Error", msg); + }); + + QTimer::singleShot(5, this, &MainWindow::onStart); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + +void MainWindow::onStart() +{ + ui->svgView->setFocus(); + + const auto args = QCoreApplication::arguments(); + if (args.size() != 2) { + return; + } + + ui->svgView->loadFile(args.at(1)); +} + +void MainWindow::on_cmbBoxSize_activated(int index) +{ + ui->svgView->setFitToView(index == 1); +} + +void MainWindow::on_cmbBoxBackground_activated(int index) +{ + ui->svgView->setBackgound(SvgView::Backgound(index)); +} + +void MainWindow::on_chBoxDrawBorder_toggled(bool checked) +{ + ui->svgView->setDrawImageBorder(checked); +} diff --git a/tools/viewsvg/mainwindow.h b/tools/viewsvg/mainwindow.h new file mode 100644 index 0000000..58eb4f6 --- /dev/null +++ b/tools/viewsvg/mainwindow.h @@ -0,0 +1,23 @@ +#pragma once + +#include <QMainWindow> + +namespace Ui { class MainWindow; } + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +private slots: + void onStart(); + void on_cmbBoxBackground_activated(int index); + void on_chBoxDrawBorder_toggled(bool checked); + void on_cmbBoxSize_activated(int index); + +private: + Ui::MainWindow *ui; +}; diff --git a/tools/viewsvg/mainwindow.ui b/tools/viewsvg/mainwindow.ui new file mode 100644 index 0000000..b8a20a4 --- /dev/null +++ b/tools/viewsvg/mainwindow.ui @@ -0,0 +1,193 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MainWindow</class> + <widget class="QMainWindow" name="MainWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>787</width> + <height>424</height> + </rect> + </property> + <property name="windowTitle"> + <string>Demo</string> + </property> + <widget class="QWidget" name="centralWidget"> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="leftMargin"> + <number>2</number> + </property> + <item> + <widget class="QLabel" name="label_3"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Size:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="cmbBoxSize"> + <item> + <property name="text"> + <string>Original</string> + </property> + </item> + <item> + <property name="text"> + <string>Fit to View</string> + </property> + </item> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>1</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Background:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="cmbBoxBackground"> + <item> + <property name="text"> + <string>None</string> + </property> + </item> + <item> + <property name="text"> + <string>White</string> + </property> + </item> + <item> + <property name="text"> + <string>Check board</string> + </property> + </item> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>1</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QCheckBox" name="chBoxDrawBorder"> + <property name="text"> + <string>Draw image border</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <widget class="QWidget" name="widget" native="true"> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="leftMargin"> + <number>2</number> + </property> + <property name="topMargin"> + <number>2</number> + </property> + <property name="rightMargin"> + <number>2</number> + </property> + <property name="bottomMargin"> + <number>2</number> + </property> + </layout> + </widget> + </item> + <item> + <widget class="SvgView" name="svgView"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Sunken</enum> + </property> + </widget> + </item> + </layout> + </widget> + </widget> + <layoutdefault spacing="6" margin="11"/> + <customwidgets> + <customwidget> + <class>SvgView</class> + <extends>QFrame</extends> + <header>svgview.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/tools/viewsvg/svgview.cpp b/tools/viewsvg/svgview.cpp new file mode 100644 index 0000000..0fb844f --- /dev/null +++ b/tools/viewsvg/svgview.cpp @@ -0,0 +1,314 @@ +#include <QDropEvent> +#include <QFileInfo> +#include <QMessageBox> +#include <QMimeData> +#include <QPainter> +#include <QThread> +#include <QTimer> + +#include "svgview.h" + +SvgViewWorker::SvgViewWorker(QObject *parent) + : QObject(parent) + , m_dpiRatio(qApp->screens().first()->devicePixelRatio()) +{ +} + +QRect SvgViewWorker::viewBox() const +{ + QMutexLocker lock(&m_mutex); + return m_renderer.viewBox(); +} + +QString SvgViewWorker::loadData(const QByteArray &data) +{ + QMutexLocker lock(&m_mutex); + + m_renderer.load(data); + if (!m_renderer.isValid()) { + return m_renderer.errorString(); + } + + return QString(); +} + +QString SvgViewWorker::loadFile(const QString &path) +{ + QMutexLocker lock(&m_mutex); + + m_renderer.load(path); + if (!m_renderer.isValid()) { + return m_renderer.errorString(); + } + + return QString(); +} + +void SvgViewWorker::render(const QSize &viewSize) +{ + Q_ASSERT(QThread::currentThread() != qApp->thread()); + + QMutexLocker lock(&m_mutex); + + if (m_renderer.isEmpty()) { + return; + } + + const auto s = m_renderer.defaultSize().scaled(viewSize, Qt::KeepAspectRatio); + QImage img(s * m_dpiRatio, QImage::Format_ARGB32_Premultiplied); + img.fill(Qt::transparent); + + QPainter p; + p.begin(&img); + p.setRenderHint(QPainter::Antialiasing); + m_renderer.render(&p); + p.end(); + + img.setDevicePixelRatio(m_dpiRatio); + + emit rendered(img); +} + +static QImage genCheckedTexture() +{ + int l = 20; + + QImage pix = QImage(l, l, QImage::Format_RGB32); + int b = pix.width() / 2.0; + pix.fill(QColor("#c0c0c0")); + + QPainter p; + p.begin(&pix); + p.fillRect(QRect(0,0,b,b), QColor("#808080")); + p.fillRect(QRect(b,b,b,b), QColor("#808080")); + p.end(); + + return pix; +} + +SvgView::SvgView(QWidget *parent) + : QFrame(parent) + , m_checkboardImg(genCheckedTexture()) + , m_worker(new SvgViewWorker()) + , m_resizeTimer(new QTimer(this)) +{ + setAcceptDrops(true); + setMinimumSize(10, 10); + + QThread *th = new QThread(this); + m_worker->moveToThread(th); + th->start(); + + const auto *screen = qApp->screens().first(); + m_dpiRatio = screen->devicePixelRatio(); + + connect(m_worker, &SvgViewWorker::rendered, this, &SvgView::onRendered); + + m_resizeTimer->setSingleShot(true); + connect(m_resizeTimer, &QTimer::timeout, this, &SvgView::requestUpdate); +} + +SvgView::~SvgView() +{ + QThread *th = m_worker->thread(); + th->quit(); + th->wait(10000); + delete m_worker; +} + +void SvgView::init() +{ + ResvgRenderer::initLog(); +} + +void SvgView::setFitToView(bool flag) +{ + m_isFitToView = flag; + requestUpdate(); +} + +void SvgView::setBackgound(SvgView::Backgound backgound) +{ + m_backgound = backgound; + update(); +} + +void SvgView::setDrawImageBorder(bool flag) +{ + m_isDrawImageBorder = flag; + update(); +} + +void SvgView::loadData(const QByteArray &ba) +{ + const QString errMsg = m_worker->loadData(ba); + afterLoad(errMsg); +} + +void SvgView::loadFile(const QString &path) +{ + const QString errMsg = m_worker->loadFile(path); + afterLoad(errMsg); +} + +void SvgView::afterLoad(const QString &errMsg) +{ + m_img = QImage(); + + if (errMsg.isEmpty()) { + m_isHasImage = true; + requestUpdate(); + } else { + emit loadError(errMsg); + m_isHasImage = false; + update(); + } +} + +void SvgView::drawSpinner(QPainter &p) +{ + const int outerRadius = 20; + const int innerRadius = outerRadius * 0.45; + + const int capsuleHeight = outerRadius - innerRadius; + const int capsuleWidth = capsuleHeight * 0.35; + const int capsuleRadius = capsuleWidth / 2; + + for (int i = 0; i < 12; ++i) { + QColor color = Qt::black; + color.setAlphaF(1.0f - (i / 12.0f)); + p.setRenderHint(QPainter::Antialiasing); + p.setPen(Qt::NoPen); + p.setBrush(color); + p.save(); + p.translate(width()/2, height()/2); + p.rotate(m_angle - i * 30.0f); + p.drawRoundedRect(-capsuleWidth * 0.5, -(innerRadius + capsuleHeight), capsuleWidth, + capsuleHeight, capsuleRadius, capsuleRadius); + p.restore(); + } +} + +void SvgView::paintEvent(QPaintEvent *e) +{ + QPainter p(this); + const auto r = contentsRect(); + p.setClipRect(r); + + switch (m_backgound) { + case Backgound::None : break; + case Backgound::White : { + p.fillRect(contentsRect(), Qt::white); + } break; + case Backgound::CheckBoard : { + p.fillRect(contentsRect(), QBrush(m_checkboardImg)); + } break; + } + + if (m_img.isNull() && !m_timer.isActive()) { + p.setPen(Qt::black); + p.drawText(rect(), Qt::AlignCenter, "Drop an SVG image here."); + } else if (m_timer.isActive()) { + drawSpinner(p); + } else { + const QRect imgRect(0, 0, m_img.width() / m_dpiRatio, m_img.height() / m_dpiRatio); + + p.translate(r.x() + (r.width() - imgRect.width())/ 2, + r.y() + (r.height() - imgRect.height()) / 2); + + p.drawImage(0, 0, m_img); + + if (m_isDrawImageBorder) { + p.setRenderHint(QPainter::Antialiasing, false); + p.setPen(Qt::green); + p.setBrush(Qt::NoBrush); + p.drawRect(imgRect); + } + } + + QFrame::paintEvent(e); +} + +void SvgView::dragEnterEvent(QDragEnterEvent *event) +{ + event->accept(); +} + +void SvgView::dragMoveEvent(QDragMoveEvent *event) +{ + event->accept(); +} + +void SvgView::dropEvent(QDropEvent *event) +{ + const QMimeData *mime = event->mimeData(); + if (!mime->hasUrls()) { + event->ignore(); + return; + } + + for (const QUrl &url : mime->urls()) { + if (!url.isLocalFile()) { + continue; + } + + QString path = url.toLocalFile(); + QFileInfo fi = QFileInfo(path); + + if (fi.isFile()) { + QString suffix = fi.suffix().toLower(); + if (suffix == "svg" || suffix == "svgz") { + loadFile(path); + } else { + QMessageBox::warning(this, tr("Warning"), + tr("You can drop only SVG and SVGZ files.")); + } + } + } + + event->acceptProposedAction(); +} + +void SvgView::resizeEvent(QResizeEvent *) +{ + m_resizeTimer->start(200); +} + +void SvgView::timerEvent(QTimerEvent *event) +{ + if (event->timerId() == m_timer.timerId()) { + m_angle = (m_angle + 30) % 360; + update(); + } else { + QWidget::timerEvent(event); + } +} + +void SvgView::requestUpdate() +{ + if (!m_isHasImage) { + return; + } + + const auto s = m_isFitToView ? size() : m_worker->viewBox().size(); + + if (s * m_dpiRatio == m_img.size()) { + return; + } + + m_timer.start(100, this); + + // Run method in the m_worker thread scope. + QTimer::singleShot(1, m_worker, [=](){ + m_worker->render(s); + }); +} + +void SvgView::onRendered(const QImage &img) +{ + m_timer.stop(); + + m_img = img; + update(); +} + diff --git a/tools/viewsvg/svgview.h b/tools/viewsvg/svgview.h new file mode 100644 index 0000000..81bd5f7 --- /dev/null +++ b/tools/viewsvg/svgview.h @@ -0,0 +1,90 @@ +#pragma once + +#include <QFrame> +#include <QMutex> +#include <QBasicTimer> + +#include <ResvgQt.h> + +class SvgViewWorker : public QObject +{ + Q_OBJECT + +public: + SvgViewWorker(QObject *parent = nullptr); + + QRect viewBox() const; + +public slots: + QString loadData(const QByteArray &data); + QString loadFile(const QString &path); + void render(const QSize &viewSize); + +signals: + void rendered(QImage); + +private: + const float m_dpiRatio; + mutable QMutex m_mutex; + ResvgRenderer m_renderer; +}; + +class SvgView : public QFrame +{ + Q_OBJECT + +public: + enum class Backgound + { + None, + White, + CheckBoard, + }; + + explicit SvgView(QWidget *parent = nullptr); + ~SvgView(); + + static void init(); + + void setFitToView(bool flag); + void setBackgound(Backgound backgound); + void setDrawImageBorder(bool flag); + + void loadData(const QByteArray &data); + void loadFile(const QString &path); + +signals: + void loadError(QString); + +protected: + void paintEvent(QPaintEvent *); + void dragEnterEvent(QDragEnterEvent *event); + void dragMoveEvent(QDragMoveEvent *event); + void dropEvent(QDropEvent *event); + void resizeEvent(QResizeEvent *); + void timerEvent(QTimerEvent *); + +private: + void requestUpdate(); + void afterLoad(const QString &errMsg); + void drawSpinner(QPainter &p); + +private slots: + void onRendered(const QImage &img); + +private: + const QImage m_checkboardImg; + SvgViewWorker * const m_worker; + QTimer * const m_resizeTimer; + + QString m_path; + float m_dpiRatio = 1.0; + bool m_isFitToView = true; + Backgound m_backgound = Backgound::CheckBoard; + bool m_isDrawImageBorder = false; + bool m_isHasImage = false; + QImage m_img; + + QBasicTimer m_timer; + int m_angle = 0; +}; diff --git a/tools/viewsvg/viewsvg.pro b/tools/viewsvg/viewsvg.pro new file mode 100644 index 0000000..a91bb66 --- /dev/null +++ b/tools/viewsvg/viewsvg.pro @@ -0,0 +1,23 @@ +QT += core gui widgets + +TARGET = viewsvg +TEMPLATE = app +CONFIG += C++11 + +SOURCES += \ + main.cpp \ + mainwindow.cpp \ + svgview.cpp + +HEADERS += \ + mainwindow.h \ + svgview.h + +FORMS += \ + mainwindow.ui + +CONFIG(release, debug|release): LIBS += -L$$PWD/../../target/release/ -lresvg +else:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../target/debug/ -lresvg + +INCLUDEPATH += $$PWD/../../capi/include +DEPENDPATH += $$PWD/../../capi/include |