diff --git a/src/acbf/AcbfBody.cpp b/src/acbf/AcbfBody.cpp index ecce44a..377dbfa 100644 --- a/src/acbf/AcbfBody.cpp +++ b/src/acbf/AcbfBody.cpp @@ -1,138 +1,139 @@ /* * Copyright (C) 2015 Dan Leinir Turthra Jensen * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * */ #include "AcbfBody.h" #include "AcbfPage.h" #include #include using namespace AdvancedComicBookFormat; class Body::Private { public: Private() {} QString bgcolor; QList pages; }; Body::Body(Document* parent) : QObject(parent) , d(new Private) { qRegisterMetaType("Body*"); } Body::~Body() = default; Document * Body::document() const { return qobject_cast(parent()); } void Body::toXml(QXmlStreamWriter *writer) { writer->writeStartElement(QStringLiteral("body")); Q_FOREACH(Page* page, d->pages) { page->toXml(writer); } writer->writeEndElement(); } bool Body::fromXml(QXmlStreamReader *xmlReader) { setBgcolor(xmlReader->attributes().value(QStringLiteral("bgcolor")).toString()); while(xmlReader->readNextStartElement()) { if(xmlReader->name() == QStringLiteral("page")) { Page* newPage = new Page(document()); if(!newPage->fromXml(xmlReader)) { return false; } d->pages.append(newPage); } else { qWarning() << Q_FUNC_INFO << "currently unsupported subsection:" << xmlReader->name(); xmlReader->skipCurrentElement(); } } if (xmlReader->hasError()) { qWarning() << Q_FUNC_INFO << "Failed to read ACBF XML document at token" << xmlReader->name() << "(" << xmlReader->lineNumber() << ":" << xmlReader->columnNumber() << ") The reported error was:" << xmlReader->errorString(); } qDebug() << Q_FUNC_INFO << "Created body with" << d->pages.count() << "pages"; return !xmlReader->hasError(); } QString Body::bgcolor() const { return d->bgcolor; } void Body::setBgcolor(const QString& newColor) { d->bgcolor = newColor; + emit bgcolorChanged(); } QList Body::pages() const { return d->pages; } Page * Body::page(int index) const { return d->pages.at(index); } int Body::pageIndex(Page* page) const { return d->pages.indexOf(page); } void Body::addPage(Page* page, int index) { if(index > -1 && d->pages.count() < index) { d->pages.insert(index, page); } else { d->pages.append(page); } } void Body::removePage(Page* page) { d->pages.removeAll(page); } bool Body::swapPages(Page* swapThis, Page* withThis) { int index1 = d->pages.indexOf(swapThis); int index2 = d->pages.indexOf(withThis); if(index1 > -1 && index2 > -1) { d->pages.swap(index1, index2); return true; } return false; } diff --git a/src/acbf/AcbfBody.h b/src/acbf/AcbfBody.h index 7ed79ef..50edbad 100644 --- a/src/acbf/AcbfBody.h +++ b/src/acbf/AcbfBody.h @@ -1,119 +1,123 @@ /* * Copyright (C) 2015 Dan Leinir Turthra Jensen * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * */ #ifndef ACBFBODY_H #define ACBFBODY_H #include #include "AcbfDocument.h" #include class QXmlStreamWriter; class QXmlStreamReader; /** * \brief Class to handle the body section of ACBF. * * ACBF's body section holds all the pages. Beyond that, * it has a bgcolor. The presence of the body section * is mandatory. * * This class can load and save the body section. * It also holds the page objects and allows * ordering/adding/removing them. */ namespace AdvancedComicBookFormat { class Page; class ACBF_EXPORT Body : public QObject { Q_OBJECT + Q_PROPERTY(QString bgcolor READ bgcolor WRITE setBgcolor NOTIFY bgcolorChanged) public: explicit Body(Document* parent = nullptr); ~Body(); Document* document() const; /** * \brief write body data into the XMLWriter. */ void toXml(QXmlStreamWriter *writer); /** * \brief Load data from the xml into this body object. * @return True if the xmlReader encountered no errors. */ bool fromXml(QXmlStreamReader *xmlReader); /** * @return the background color as a QString. * * It should be an 8bit per channel rgb hexcode. */ QString bgcolor() const; /** * \brief set the background color. * * @param newColor - a String with an 8bit per channel rgb hexcode (#ff00ff, or the like) */ void setBgcolor(const QString& newColor); + /** + * @brief fires when the background color changes. + */ + Q_SIGNAL void bgcolorChanged(); /** * @return a QList of all the pages stored currently. */ QList pages() const; /** * @param index - the index of the page. * @return the page object at the given index. */ - Page* page(int index) const; + Q_INVOKABLE Page* page(int index) const; /** * @param page - The page for which to return the index. * @return index of the page given, will return -1 if the page isn't in this document. */ int pageIndex(Page* page) const; // If afterIndex is larger than zero, the insertion will happen at that index void addPage(Page* page, int index = -1); /** * \brief remove the given page object from this body. * @param page - the page to remove. */ - void removePage(Page* page); - + void removePage(Page* page); /** * \brief Swap two pages in the list. * @param swapThis - the first page to swap. * @param withThis - the second page to swap. */ bool swapPages(Page* swapThis, Page* withThis); private: class Private; std::unique_ptr d; }; } #endif//ACBFBODY_H diff --git a/src/acbf/AcbfFrame.cpp b/src/acbf/AcbfFrame.cpp index 6a784c4..0343ad5 100644 --- a/src/acbf/AcbfFrame.cpp +++ b/src/acbf/AcbfFrame.cpp @@ -1,135 +1,179 @@ /* * Copyright 2018 Wolthera van Hövell tot Westerflier * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * */ #include "AcbfFrame.h" #include #include using namespace AdvancedComicBookFormat; class Frame::Private { public: Private() {} QString bgcolor; QList points; }; Frame::Frame(Page* parent) : QObject(parent) , d(new Private) { + qRegisterMetaType("Frame*"); + connect(this, SIGNAL(pointCountChanged()), this, SIGNAL(boundsChanged())); } Frame::~Frame() = default; void Frame::toXml(QXmlStreamWriter* writer) { writer->writeStartElement(QStringLiteral("frame")); QStringList points; Q_FOREACH(const QPoint& point, d->points) { points << QStringLiteral("%1,%2").arg(QString::number(point.x())).arg(QString::number(point.y())); } writer->writeAttribute(QStringLiteral("points"), points.join(' ')); if(!d->bgcolor.isEmpty()) { writer->writeAttribute(QStringLiteral("bgcolor"), d->bgcolor); } writer->writeEndElement(); } bool Frame::fromXml(QXmlStreamReader *xmlReader) { setBgcolor(xmlReader->attributes().value(QStringLiteral("bgcolor")).toString()); QStringList points = xmlReader->attributes().value(QStringLiteral("points")).toString().split(' '); Q_FOREACH(const QString& point, points) { QStringList elements = point.split(','); if(elements.length() == 2) { addPoint(QPoint(elements.at(0).toInt(), elements.at(1).toInt())); } else { qWarning() << "Failed to construct one of the points for a frame. Attempted to handle the point" << point << "in the data" << points; return false; } } if (xmlReader->hasError()) { qWarning() << Q_FUNC_INFO << "Failed to read ACBF XML document at token" << xmlReader->name() << "(" << xmlReader->lineNumber() << ":" << xmlReader->columnNumber() << ") The reported error was:" << xmlReader->errorString(); } qDebug() << Q_FUNC_INFO << "Created a frame with " << points.count() << "points"; return !xmlReader->hasError(); } QList Frame::points() const { return d->points; } QPoint Frame::point(int index) const { return d->points.at(index); } int Frame::pointIndex(const QPoint& point) const { return d->points.indexOf(point); } void Frame::addPoint(const QPoint& point, int index) { if(index > -1 && d->points.count() < index) { d->points.insert(index, point); } else { d->points.append(point); } + emit pointCountChanged(); } void Frame::removePoint(const QPoint& point) { d->points.removeAll(point); + emit pointCountChanged(); } bool Frame::swapPoints(const QPoint& swapThis, const QPoint& withThis) { int index1 = d->points.indexOf(swapThis); int index2 = d->points.indexOf(withThis); if(index1 > -1 && index2 > -1) { d->points.swap(index1, index2); + emit pointCountChanged(); return true; } return false; } +void Frame::setPointsFromRect(const QPoint &topLeft, const QPoint &bottomRight) +{ + QRect rect(topLeft, bottomRight); + d->points.clear(); + d->points.append(topLeft); + d->points.append(rect.topRight()); + d->points.append(rect.bottomRight()); + d->points.append(rect.bottomLeft()); + emit pointCountChanged(); +} + +int Frame::pointCount() const +{ + return d->points.size(); +} + +QRect Frame::bounds() const +{ + // Would use QPolygon here, but that requires including QTGUI. + QRect rect(d->points.at(0), d->points.at(1)); + for (int i = 2; i < d->points.size(); i++) { + QPoint p = d->points.at(i); + if (rect.left() > p.x()) { + rect.setLeft(p.x()); + } + if (rect.right() < p.x()) { + rect.setRight(p.x()); + } + if (rect.bottom() > p.y()) { + rect.setBottom(p.y()); + } + if (rect.top() < p.y()) { + rect.setTop(p.y()); + } + } + return rect; +} + QString Frame::bgcolor() const { return d->bgcolor; } void Frame::setBgcolor(const QString& newColor) { d->bgcolor = newColor; + emit bgcolorChanged(); } diff --git a/src/acbf/AcbfFrame.h b/src/acbf/AcbfFrame.h index ec5307a..101f7de 100644 --- a/src/acbf/AcbfFrame.h +++ b/src/acbf/AcbfFrame.h @@ -1,116 +1,144 @@ /* * Copyright 2018 Wolthera van Hövell tot Westerflier * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * */ #ifndef ACBFFRAME_H #define ACBFFRAME_H #include #include #include "AcbfPage.h" #include +#include /** * \brief a Class to handle comic panels. * * Frames are a polygon of points, which describe * a panel on a comic book page image in pixels. * * This can be used to give improved navigation on * smaller screens. * * Frames also have a background color which can be used * to enhance the reading experience by setting the background * color to the frame color. */ namespace AdvancedComicBookFormat { class ACBF_EXPORT Frame : public QObject { Q_OBJECT - + Q_PROPERTY(QString bgcolor READ bgcolor WRITE setBgcolor NOTIFY bgcolorChanged) + Q_PROPERTY(int pointCount READ pointCount NOTIFY pointCountChanged) + Q_PROPERTY(QRect bounds READ bounds NOTIFY boundsChanged) public: explicit Frame(Page* parent = nullptr); ~Frame() override; /** * \brief Write the frame into the xml writer. */ void toXml(QXmlStreamWriter* writer); /** * \brief load a frame element into this object. * @return True if the xmlReader encountered no errors. */ bool fromXml(QXmlStreamReader *xmlReader); /** * @return a list of points that encompasses the frame. */ QList points() const; /** * @param index - the index of the desired point. * @return a point for an index. */ - QPoint point(int index) const; + Q_INVOKABLE QPoint point(int index) const; /** * @param point - a point from the points list. * @return the index of the given point. */ - int pointIndex(const QPoint& point) const; + Q_INVOKABLE int pointIndex(const QPoint& point) const; /** * \brief add a point to the points list. * @param point - the point to add. Coordinates should be in pixels. * @param index - the index to add it at. If afterIndex is larger than zero, * the insertion will happen at that index */ - void addPoint(const QPoint& point, int index = -1); + Q_INVOKABLE void addPoint(const QPoint& point, int index = -1); /** * \brief remove a point from the list. * @param point - point to remove from the list. */ - void removePoint(const QPoint& point); + Q_INVOKABLE void removePoint(const QPoint& point); /** * \brief Swap two points in the list. * @param swapThis - the first points to swap. * @param withThis - the second points to swap. */ bool swapPoints(const QPoint& swapThis, const QPoint& withThis); + /** + * @brief set the points based on a top left and bottom right. + * @param topLeft the topleft corner of the rect. + * @param bottomRight the bottomright corner of the rect. + */ + Q_INVOKABLE void setPointsFromRect(const QPoint& topLeft, const QPoint& bottomRight); + + int pointCount() const; + /** + * @brief fires when the point counts changes. + */ + Q_SIGNAL void pointCountChanged(); + /** + * @brief convenience function to get the ractangle of the points. + * @return the bounds of the frame. + */ + QRect bounds() const; + /** + * @brief fires when the bounds change, which happens when the point count changes. + */ + Q_SIGNAL void boundsChanged(); /** * @return the background color as a QString. * * It should be an 8bit per channel rgb hexcode. */ QString bgcolor() const; /** * \brief set the background color. * * @param newColor - a String with an 8bit per channel rgb hexcode (#ff00ff, or the like) */ void setBgcolor(const QString& newColor = QString()); + /** + * @brief fires when the background color changes. + */ + Q_SIGNAL void bgcolorChanged(); private: class Private; std::unique_ptr d; }; } #endif // ACBFFRAME_H diff --git a/src/acbf/AcbfJump.cpp b/src/acbf/AcbfJump.cpp index 1a9e462..5991d35 100644 --- a/src/acbf/AcbfJump.cpp +++ b/src/acbf/AcbfJump.cpp @@ -1,134 +1,178 @@ /* * Copyright 2018 Wolthera van Hövell tot Westerflier * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * */ #include "AcbfJump.h" #include #include using namespace AdvancedComicBookFormat; class Jump::Private { public: Private() : pageIndex(0) {} QList points; int pageIndex; }; Jump::Jump(Page* parent) : QObject(parent) , d(new Private) { + qRegisterMetaType("Jump*"); + connect(this, SIGNAL(pointCountChanged()), this, SIGNAL(boundsChanged())); } Jump::~Jump() = default; void Jump::toXml(QXmlStreamWriter* writer) { writer->writeStartElement(QStringLiteral("jump")); QStringList points; Q_FOREACH(const QPoint& point, d->points) { points << QStringLiteral("%1,%2").arg(QString::number(point.x())).arg(QString::number(point.y())); } writer->writeAttribute(QStringLiteral("points"), points.join(' ')); writer->writeAttribute(QStringLiteral("page"), QString::number(d->pageIndex)); writer->writeEndElement(); } bool Jump::fromXml(QXmlStreamReader *xmlReader) { setPageIndex(xmlReader->attributes().value(QStringLiteral("page")).toInt()); QStringList points = xmlReader->attributes().value(QStringLiteral("points")).toString().split(' '); Q_FOREACH(const QString& point, points) { QStringList elements = point.split(','); if(elements.length() == 2) { addPoint(QPoint(elements.at(0).toInt(), elements.at(1).toInt())); } else { qWarning() << "Failed to construct one of the points for a jump. Attempted to handle the point" << point << "in the data" << points; return false; } } if (xmlReader->hasError()) { qWarning() << Q_FUNC_INFO << "Failed to read ACBF XML document at token" << xmlReader->name() << "(" << xmlReader->lineNumber() << ":" << xmlReader->columnNumber() << ") The reported error was:" << xmlReader->errorString(); } qDebug() << Q_FUNC_INFO << "Created a jump with " << points.count() << "points, to page " << d->pageIndex; return !xmlReader->hasError(); } QList Jump::points() const { return d->points; } QPoint Jump::point(int index) const { return d->points.at(index); } int Jump::pointIndex(const QPoint& point) const { return d->points.indexOf(point); } void Jump::addPoint(const QPoint& point, int index) { if(index > -1 && d->points.count() < index) { d->points.insert(index, point); } else { d->points.append(point); } + emit pointCountChanged(); } void Jump::removePoint(const QPoint& point) { d->points.removeAll(point); + emit pointCountChanged(); } bool Jump::swapPoints(const QPoint& swapThis, const QPoint& withThis) { int index1 = d->points.indexOf(swapThis); int index2 = d->points.indexOf(withThis); if(index1 > -1 && index2 > -1) { d->points.swap(index1, index2); + emit pointCountChanged(); return true; } return false; } +void Jump::setPointsFromRect(const QPoint &topLeft, const QPoint &bottomRight) +{ + QRect rect(topLeft, bottomRight); + d->points.clear(); + d->points.append(topLeft); + d->points.append(rect.topRight()); + d->points.append(rect.bottomRight()); + d->points.append(rect.bottomLeft()); + emit pointCountChanged(); +} + +int Jump::pointCount() const +{ + return d->points.size(); +} + +QRect Jump::bounds() const +{ + // Would use QPolygon here, but that requires including QTGUI. + QRect rect(d->points.at(0), d->points.at(1)); + for (int i = 2; i < d->points.size(); i++) { + QPoint p = d->points.at(i); + if (rect.left() > p.x()) { + rect.setLeft(p.x()); + } + if (rect.right() < p.x()) { + rect.setRight(p.x()); + } + if (rect.bottom() > p.y()) { + rect.setBottom(p.y()); + } + if (rect.top() < p.y()) { + rect.setTop(p.y()); + } + } + return rect; +} + int Jump::pageIndex() const { return d->pageIndex; } void Jump::setPageIndex(const int& pageNumber) { d->pageIndex = pageNumber; + emit pageIndexChanged(); } diff --git a/src/acbf/AcbfJump.h b/src/acbf/AcbfJump.h index 4f67b15..fda3b3c 100644 --- a/src/acbf/AcbfJump.h +++ b/src/acbf/AcbfJump.h @@ -1,121 +1,149 @@ /* * Copyright 2018 Wolthera van Hövell tot Westerflier * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * */ #ifndef ACBFJUMP_H #define ACBFJUMP_H #include #include #include "AcbfPage.h" #include +#include namespace AdvancedComicBookFormat { /** * \brief a Class to handle ACBF jumps. * * Jumps are areas that point at a specific page. * * This allows for a table of contents page that * can switch to a specific story by the user selecting * a jump area drawn around the icon for the specific * story and pointing at the beginning of the story. * * Other uses included choose your own adventure style books. * * Within ACBF, Jumps are areas defined by a polygon of points, * with an index pointing at the page to jump to. */ class ACBF_EXPORT Jump : public QObject { Q_OBJECT + Q_PROPERTY(int pointCount READ pointCount NOTIFY pointCountChanged) + Q_PROPERTY(QRect bounds READ bounds NOTIFY boundsChanged) + Q_PROPERTY(int pageIndex READ pageIndex WRITE setPageIndex NOTIFY pageIndexChanged) public: explicit Jump(Page* parent = nullptr); ~Jump() override; /** * \brief Write the jump into the xml writer. */ void toXml(QXmlStreamWriter* writer); /** * \brief load a jump element into this object. * @return True if the xmlReader encountered no errors. */ bool fromXml(QXmlStreamReader *xmlReader); /** * @return a list of points that encompasses the jump. */ QList points() const; /** * @param index - the index of the desired point. * @return a point for an index. */ - QPoint point(int index) const; + Q_INVOKABLE QPoint point(int index) const; /** * @param point - a point from the points list. * @return the index of the given point. */ - int pointIndex(const QPoint& point) const; + Q_INVOKABLE int pointIndex(const QPoint& point) const; /** * \brief add a point to the points list. * @param point - the point to add. Coordinates should be in pixels. * @param index - the index to add it at. If afterIndex is larger than zero, * the insertion will happen at that index */ - void addPoint(const QPoint& point, int index = -1); + Q_INVOKABLE void addPoint(const QPoint& point, int index = -1); /** * \brief remove a point from the list. * @param point - point to remove from the list. */ - void removePoint(const QPoint& point); + Q_INVOKABLE void removePoint(const QPoint& point); /** * \brief Swap two points in the list. * @param swapThis - the first points to swap. * @param withThis - the second points to swap. */ bool swapPoints(const QPoint& swapThis, const QPoint& withThis); - + /** + * @brief set the points based on a top left and bottom right. + * @param topLeft the topleft corner of the rect. + * @param bottomRight the bottomright corner of the rect. + */ + Q_INVOKABLE void setPointsFromRect(const QPoint& topLeft, const QPoint& bottomRight); + + int pointCount() const; + /** + * @brief fires when the point counts changes. + */ + Q_SIGNAL void pointCountChanged(); + /** + * @brief convenience function to get the ractangle of the points. + * @return the bounds of the frame. + */ + QRect bounds() const; + /** + * @brief fires when the bounds change, which happens when the point count changes. + */ + Q_SIGNAL void boundsChanged(); /** * @brief The page to jump to. * * @return int that points at the index of the page to jump to. */ int pageIndex() const; /** * @brief set the page to jump to. * * @param pageNumber An integer for the index of the page in the * page list. */ void setPageIndex(const int& pageNumber = 0); + /** + * @brief pageIndexChanged + */ + Q_SIGNAL void pageIndexChanged(); private: class Private; std::unique_ptr d; }; } #endif // ACBFJUMP_H diff --git a/src/acbf/AcbfPage.cpp b/src/acbf/AcbfPage.cpp index e9c5bab..9cc0249 100644 --- a/src/acbf/AcbfPage.cpp +++ b/src/acbf/AcbfPage.cpp @@ -1,340 +1,395 @@ /* * Copyright (C) 2015 Dan Leinir Turthra Jensen * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * */ #include "AcbfPage.h" #include "AcbfTextlayer.h" #include "AcbfFrame.h" #include "AcbfJump.h" #include #include #include using namespace AdvancedComicBookFormat; class Page::Private { public: Private() : isCoverPage(false) {} QString bgcolor; QString transition; QHash title; QString imageHref; QHash textLayers; QList frames; QList jumps; bool isCoverPage; }; Page::Page(Document* parent) : QObject(parent) , d(new Private) { + qRegisterMetaType("Page*"); } Page::~Page() = default; void Page::toXml(QXmlStreamWriter* writer) { if(d->isCoverPage) { writer->writeStartElement(QStringLiteral("coverpage")); } else { writer->writeStartElement(QStringLiteral("page")); } if(!d->bgcolor.isEmpty()) { writer->writeAttribute(QStringLiteral("bgcolor"), d->bgcolor); } if(!d->transition.isEmpty()) { writer->writeAttribute(QStringLiteral("transition"), d->transition); } QHashIterator titles(d->title); while(titles.hasNext()) { titles.next(); writer->writeStartElement(QStringLiteral("title")); writer->writeAttribute(QStringLiteral("lang"), titles.key()); writer->writeCharacters(titles.value()); writer->writeEndElement(); } writer->writeStartElement(QStringLiteral("image")); writer->writeAttribute(QStringLiteral("href"), d->imageHref); writer->writeEndElement(); Q_FOREACH(Textlayer* layer, d->textLayers.values()) { layer->toXml(writer); } Q_FOREACH(Frame* frame, d->frames) { frame->toXml(writer); } Q_FOREACH(Jump* jump, d->jumps) { jump->toXml(writer); } writer->writeEndElement(); } bool Page::fromXml(QXmlStreamReader *xmlReader) { setBgcolor(xmlReader->attributes().value(QStringLiteral("bgcolor")).toString()); setTransition(xmlReader->attributes().value(QStringLiteral("transition")).toString()); while(xmlReader->readNextStartElement()) { if(xmlReader->name() == QStringLiteral("title")) { d->title[xmlReader->attributes().value(QStringLiteral("lang")).toString()] = xmlReader->readElementText(); } else if(xmlReader->name() == QStringLiteral("image")) { /** * There are some acbf files out there that have backslashes in their * image href. This is probably a mistake from windows users, but not proper XML. * We should thus replace those with forward slashes so the image can be loaded. */ QString href = xmlReader->attributes().value(QStringLiteral("href")).toString(); setImageHref(href.replace(QStringLiteral("\\"), QStringLiteral("/"))); xmlReader->skipCurrentElement(); } else if(xmlReader->name() == QStringLiteral("text-layer")) { Textlayer* newLayer = new Textlayer(this); if(!newLayer->fromXml(xmlReader)) { return false; } d->textLayers[newLayer->language()] = newLayer; } else if(xmlReader->name() == QStringLiteral("frame")) { Frame* newFrame = new Frame(this); if(!newFrame->fromXml(xmlReader)) { return false; } d->frames.append(newFrame); // Frames have no child elements, so we need to force the reader to go to the next one. xmlReader->readNext(); } else if(xmlReader->name() == QStringLiteral("jump")) { Jump* newJump = new Jump(this); if(!newJump->fromXml(xmlReader)) { return false; } // Jumps have no child elements, so we need to force the reader to go to the next one. d->jumps.append(newJump); xmlReader->readNext(); } else { qWarning() << Q_FUNC_INFO << "currently unsupported subsection:" << xmlReader->name(); xmlReader->skipCurrentElement(); } } if (xmlReader->hasError()) { qWarning() << Q_FUNC_INFO << "Failed to read ACBF XML document at token" << xmlReader->name() << "(" << xmlReader->lineNumber() << ":" << xmlReader->columnNumber() << ") The reported error was:" << xmlReader->errorString(); } qDebug() << Q_FUNC_INFO << "Created page for image" << d->imageHref; return !xmlReader->hasError(); } QString Page::bgcolor() const { return d->bgcolor; } void Page::setBgcolor(const QString& newColor) { d->bgcolor = newColor; } QString Page::transition() const { return d->transition; } void Page::setTransition(const QString& transition) { d->transition = transition; + emit transitionChanged(); } QStringList Page::availableTransitions() { return { QStringLiteral("fade"), // (old page fades out into background, then new page fades in) QStringLiteral("blend"), // (new page blends in the image while old page blends out) QStringLiteral("scroll_right"), // (screen scrolls to the right to a new page; reversed behavior applies when moving to previous page) QStringLiteral("scroll_down"), // (screen scrolls down to a new page; reversed behavior applies when moving to previous page) QStringLiteral("none") // (no transition animation happens) }; } QStringList Page::titleForAllLanguages() const { return d->title.values(); } QString Page::title(const QString& language) const { return d->title.value(language); } void Page::setTitle(const QString& title, const QString& language) { if(title.isEmpty()) { d->title.remove(language); } else { d->title[language] = title; } + emit titlesChanged(); } QString Page::imageHref() const { return d->imageHref; } void Page::setImageHref(const QString& imageHref) { d->imageHref = imageHref; } QList Page::textLayersForAllLanguages() const { return d->textLayers.values(); } Textlayer * Page::textLayer(const QString& language) const { return d->textLayers.value(language); } void Page::setTextLayer(Textlayer* textlayer, const QString& language) { if(textlayer) { d->textLayers[language] = textlayer; } else { d->textLayers.remove(language); } + emit textLayerLanguagesChanged(); +} + +void Page::addTextLayer(const QString &language) +{ + Textlayer* textLayer = new Textlayer(this); + setTextLayer(textLayer, language); +} + +void Page::removeTextLayer(const QString &language) +{ + setTextLayer(nullptr, language); +} + +QStringList Page::textLayerLanguages() const +{ + return d->textLayers.keys(); } QList Page::frames() const { return d->frames; } Frame * Page::frame(int index) const { return d->frames.at(index); } int Page::frameIndex(Frame* frame) const { return d->frames.indexOf(frame); } void Page::addFrame(Frame* frame, int index) { if(index > -1 && d->frames.count() < index) { d->frames.insert(index, frame); } else { d->frames.append(frame); } + emit frameCountChanged(); } void Page::removeFrame(Frame* frame) { d->frames.removeAll(frame); + emit frameCountChanged(); } -bool Page::swapFrames(Frame* swapThis, Frame* withThis) +void Page::removeFrame(int index) { - int index1 = d->frames.indexOf(swapThis); - int index2 = d->frames.indexOf(withThis); - if(index1 > -1 && index2 > -1) { - d->frames.swap(index1, index2); + removeFrame(frame(index)); +} + +void Page::addFrame(int index) +{ + Frame* frame = new Frame(this); + addFrame(frame, index); +} + +bool Page::swapFrames(int swapThis, int withThis) +{ + if(swapThis > -1 && withThis > -1) { + d->frames.swap(swapThis, withThis); + emit frameCountChanged(); return true; } return false; } +int Page::frameCount() +{ + return d->frames.size(); +} + QList Page::jumps() const { return d->jumps; } Jump * Page::jump(int index) const { return d->jumps.at(index); } int Page::jumpIndex(Jump* jump) const { return d->jumps.indexOf(jump); } void Page::addJump(Jump* jump, int index) { if(index > -1 && d->jumps.count() < index) { d->jumps.insert(index, jump); } else { d->jumps.append(jump); } + emit jumpCountChanged(); +} + +void Page::addJump(int pageIndex, int index) +{ + Jump* jump = new Jump(this); + jump->setPageIndex(pageIndex); + addJump(jump, index); } void Page::removeJump(Jump* jump) { d->jumps.removeAll(jump); + emit jumpCountChanged(); } -bool Page::swapJumps(Jump* swapThis, Jump* withThis) +void Page::removeJump(int index) { - int index1 = d->jumps.indexOf(swapThis); - int index2 = d->jumps.indexOf(withThis); - if(index1 > -1 && index2 > -1) { - d->jumps.swap(index1, index2); + removeJump(jump(index)); +} + +bool Page::swapJumps(int swapThis, int withThis) +{ + if(swapThis > -1 && withThis > -1) { + d->jumps.swap(swapThis, withThis); + emit jumpCountChanged(); return true; } return false; } +int Page::jumpCount() +{ + return d->jumps.size(); +} + bool Page::isCoverPage() const { return d->isCoverPage; } void Page::setIsCoverPage(bool isCoverPage) { d->isCoverPage = isCoverPage; } diff --git a/src/acbf/AcbfPage.h b/src/acbf/AcbfPage.h index c55aeab..b9ecd40 100644 --- a/src/acbf/AcbfPage.h +++ b/src/acbf/AcbfPage.h @@ -1,236 +1,317 @@ /* * Copyright (C) 2015 Dan Leinir Turthra Jensen * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * */ #ifndef ACBFPAGE_H #define ACBFPAGE_H #include "AcbfDocument.h" /** * \brief Class to handle page objects. * * A proper ACBF document should have entries for all pages * and said entries should point at the images that make up * the comic. * * However, ACBF also has room from frame definitions, transcriptions, * translations, table of contents, jumps and more. * * The frame definitions are used to navigate a page and zoom efficiently on a * small screen. Similarly, transscriptions and translations can be used to show * text when the image itself is too blurry. * * Title is used to generate a table of contents. * * Transition is to indicate extra information about how the page should be entered. * * bgcolor is used by the reading software to determine what background color to give. * * Jumps can be used to move around in the comic. * * TODO: Frame and Jump seem to be missing classes despite being used here? */ class QXmlStreamWriter; class QXmlStreamReader; namespace AdvancedComicBookFormat { class Textlayer; class Frame; class Jump; class ACBF_EXPORT Page : public QObject { Q_OBJECT + Q_PROPERTY(QString bgcolor READ bgcolor WRITE setBgcolor NOTIFY bgcolorChanged) + Q_PROPERTY(QString transition READ transition WRITE setTransition NOTIFY transitionChanged) + Q_PROPERTY(QString imageHref READ imageHref WRITE setImageHref NOTIFY imageHrefChanged) + Q_PROPERTY(QStringList textLayerLanguages READ textLayerLanguages NOTIFY textLayerLanguagesChanged) + Q_PROPERTY(int frameCount READ frameCount NOTIFY frameCountChanged) + Q_PROPERTY(int jumpCount READ jumpCount NOTIFY jumpCountChanged) public: // Pages can also be cover pages, which means they can also be children of BookInfo explicit Page(Document* parent = nullptr); ~Page() override; /** * \brief Write the page into the xml writer. */ void toXml(QXmlStreamWriter* writer); /** * \brief load a page element into this object. * @return True if the xmlReader encountered no errors. */ bool fromXml(QXmlStreamReader *xmlReader); /** * @return the background color as a QString. * * It should be an 8bit per channel rgb hexcode. */ QString bgcolor() const; /** * \brief set the background color. * * @param newColor - a String with an 8bit per channel rgb hexcode (#ff00ff, or the like) */ void setBgcolor(const QString& newColor = QString()); - + /** + * @brief fires when the background color changes. + */ + Q_SIGNAL void bgcolorChanged(); /** * @return transition type as a string. */ QString transition() const; /** * \brief set the transition type. * @param transition - the transition type, possible entries are in the availableTransitions() stringlist. */ void setTransition(const QString& transition); + /** + * @brief fires when the transition type changes. + */ + Q_SIGNAL void transitionChanged(); /** * @returns a list of strings that can be used for the transition. */ static QStringList availableTransitions(); /** * @return all titles for this page in all languages. */ - QStringList titleForAllLanguages() const; + Q_INVOKABLE QStringList titleForAllLanguages() const; /** * @param language - the language of the entry in language code, country * code format joined by a dash (not an underscore). * @return the title for this language. */ - QString title(const QString& language = QString()) const; + Q_INVOKABLE QString title(const QString& language = QString()) const; /** * \brief set the title for this language. * @param language - the language of the entry in language code, country * code format joined by a dash (not an underscore). */ - void setTitle(const QString& title, const QString& language = QString()); + Q_INVOKABLE void setTitle(const QString& title, const QString& language = QString()); + /** + * @brief titlesChanged + */ + Q_SIGNAL void titlesChanged(); /** * @returns the URI for the image of this page as a QString */ QString imageHref() const; /** * \brief set the URI for the image of this page. * @param imageHref - the URI to an image. * * - A Binary representation will use the ID of that representation. * - A reference to a file on the internet will start with "http://" or "https://" * - A reference to a file in a zip will be prefixed with "zip:" * - Everything else is presumed to be a file on disk. */ void setImageHref(const QString& imageHref); + /** + * @brief fires when the image url changes. + */ + Q_SIGNAL void imageHrefChanged(); /** * @returns all the textlayers objects. */ QList textLayersForAllLanguages() const; /** * @param language - the language of the entry in language code, country * code format joined by a dash (not an underscore). * @returns the TextLayer object for that language. */ - Textlayer* textLayer(const QString& language = QString()) const; + Q_INVOKABLE Textlayer* textLayer(const QString& language = QString()) const; /** * * @param language - the language of the entry in language code, country * code format joined by a dash (not an underscore). Setting the textlayer * for a language to null removes that language (as with other translated * entries, though this one not being text warranted a comment) */ - void setTextLayer(Textlayer* textlayer, const QString& language = ""); + void setTextLayer(Textlayer* textlayer, const QString& language = QString()); + /** + * @brief add a textlayer for language. + * @param language code to add a textlayer for. + */ + Q_INVOKABLE void addTextLayer(const QString& language = QString()); + /** + * @brief remove a text layer by language. + * @param language code to remove the textlayer for. + */ + Q_INVOKABLE void removeTextLayer(const QString& language = QString()); + /** + * @brief get the possible translations. + * @return a stringlist with all the languages available. + */ + QStringList textLayerLanguages() const; + /** + * @brief fires when the textlayer languages list changes + * + * this can happen when text layers are added or removed. + */ + Q_SIGNAL void textLayerLanguagesChanged(); /** * @returns a list of frames in this page. */ QList frames() const; /** * @param index - index of the frame. * @return the frame of that index. */ - Frame* frame(int index) const; + Q_INVOKABLE Frame* frame(int index) const; /** * @param frame - the frame you want to index of. * @returns the index of the given frame. */ int frameIndex(Frame* frame) const; /** * \brief add a frame to the list of frames. * @param frame - the frame to add. * @param index - the index to add it at. If afterIndex is larger than * zero, the insertion will happen at that index */ void addFrame(Frame* frame, int index = -1); /** * \brief remove the given frame from the framelist. * @param frame - the frame to remove. */ void removeFrame(Frame* frame); + /** + * @brief remove frame by index. + * @param index index of the frame to remove. + */ + Q_INVOKABLE void removeFrame(int index); + /** + * @brief add a frame at index.. + * @param index - the index to add it at. If afterIndex is larger than + * zero, the insertion will happen at that index + */ + Q_INVOKABLE void addFrame(int index = -1); /** * \brief Swap two frames in the list. - * @param swapThis - the first frame to swap. - * @param withThis - the second frame to swap. + * @param swapThis - the first index to swap. + * @param withThis - the second index to swap. */ - bool swapFrames(Frame* swapThis, Frame* withThis); + Q_INVOKABLE bool swapFrames(int swapThis, int withThis); + /** + * @brief frameCount + * @return the total amount of frames. + */ + int frameCount(); + /** + * @brief fires when the frame count changes. + */ + Q_SIGNAL void frameCountChanged(); /** * @return the list of jump objects for this page. */ QList jumps() const; /** * @param index - the index for which you want the jump object. * @return a jump object for the given frame. */ - Jump* jump(int index) const; + Q_INVOKABLE Jump* jump(int index) const; /** * @param jump - the jump you want to index of. * @returns the index of the given jump. */ int jumpIndex(Jump* jump) const; /** * \brief add a jump to the list of frames. * @param jump - the jump to add. * @param index - the index to add it at. If afterIndex is larger than * zero, the insertion will happen at that index */ void addJump(Jump* jump, int index = -1); + /** + * @brief addJump + * @param index - the index to add it at. If afterIndex is larger than + * zero, the insertion will happen at that index + */ + Q_INVOKABLE void addJump(int pageIndex, int index = -1); /** * \brief remove the given jump from the list of jumps. * @param jump - the jump to remove. */ void removeJump(Jump* jump); + /** + * @brief removeJump + * @param index to remove the jump at. + */ + Q_INVOKABLE void removeJump(int index); /** * \brief Swap two jumps in the list. - * @param swapThis - the first jumps to swap. - * @param withThis - the second jumps to swap. + * @param swapThis - the first index to swap. + * @param withThis - the second index to swap. + */ + Q_INVOKABLE bool swapJumps(int swapThis, int withThis); + /** + * @brief the amount of jumps on this page. + */ + int jumpCount(); + /** + * @brief changes when the jumpcount changes. */ - bool swapJumps(Jump* swapThis, Jump* withThis); + Q_SIGNAL void jumpCountChanged(); /** * @returns whether this is the cover page. */ bool isCoverPage() const; /** * \brief toggle whether this is the cover page. * */ void setIsCoverPage(bool isCoverPage = false); private: class Private; std::unique_ptr d; }; } #endif//ACBFPAGE_H diff --git a/src/acbf/AcbfTextarea.cpp b/src/acbf/AcbfTextarea.cpp index 93ce0d9..83760df 100644 --- a/src/acbf/AcbfTextarea.cpp +++ b/src/acbf/AcbfTextarea.cpp @@ -1,245 +1,293 @@ /* * Copyright (C) 2016 Dan Leinir Turthra Jensen * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * */ #include "AcbfTextarea.h" #include #include using namespace AdvancedComicBookFormat; class Textarea::Private { public: Private() : textRotation(0) {} QString bgcolor; QList points; int textRotation; QString type; bool inverted; bool transparent; QStringList paragraphs; }; Textarea::Textarea(Textlayer* parent) : QObject(parent) , d(new Private) { + qRegisterMetaType("Textarea*"); + connect(this, SIGNAL(pointCountChanged()), this, SIGNAL(boundsChanged())); } Textarea::~Textarea() = default; void Textarea::toXml(QXmlStreamWriter* writer) { writer->writeStartElement(QStringLiteral("text-area")); QStringList points; Q_FOREACH(const QPoint& point, d->points) { points << QStringLiteral("%1,%2").arg(QString::number(point.x())).arg(QString::number(point.y())); } writer->writeAttribute(QStringLiteral("points"), points.join(' ')); if(!d->bgcolor.isEmpty()) { writer->writeAttribute(QStringLiteral("bgcolor"), d->bgcolor); } if(d->textRotation != 0) { writer->writeAttribute(QStringLiteral("text-rotation"), QString::number(d->textRotation)); } if(!d->type.isEmpty()) { writer->writeAttribute(QStringLiteral("type"), d->type); } if(d->inverted) { // because the default is false, no need to write it otherwise... writer->writeAttribute(QStringLiteral("inverted"), QStringLiteral("true")); } if(d->transparent) { // because the default is false, no need to write it otherwise... writer->writeAttribute(QStringLiteral("transparent"), QStringLiteral("true")); } Q_FOREACH(const QString& paragraph, d->paragraphs) { writer->writeStartElement(QStringLiteral("p")); writer->writeCharacters(paragraph); writer->writeEndElement(); } writer->writeEndElement(); } bool Textarea::fromXml(QXmlStreamReader *xmlReader) { setBgcolor(xmlReader->attributes().value(QStringLiteral("bgcolor")).toString()); setTextRotation(xmlReader->attributes().value(QStringLiteral("text-rotation")).toInt()); setType(xmlReader->attributes().value(QStringLiteral("type")).toString()); setInverted(xmlReader->attributes().value(QStringLiteral("inverted")).toString().toLower() == QStringLiteral("true")); setTransparent(xmlReader->attributes().value(QStringLiteral("transparent")).toString().toLower() == QStringLiteral("true")); QStringList points = xmlReader->attributes().value(QStringLiteral("points")).toString().split(' '); Q_FOREACH(const QString& point, points) { QStringList elements = point.split(','); if(elements.length() == 2) { addPoint(QPoint(elements.at(0).toInt(), elements.at(1).toInt())); } else { qWarning() << "Failed to construct one of the points for a text-area. Attempted to handle the point" << point << "in the data" << points; return false; } } while(xmlReader->readNextStartElement()) { if(xmlReader->name() == QStringLiteral("p")) { d->paragraphs.append(xmlReader->readElementText(QXmlStreamReader::IncludeChildElements)); } else { qWarning() << Q_FUNC_INFO << "currently unsupported subsection in text-area:" << xmlReader->name(); xmlReader->skipCurrentElement(); } } if (xmlReader->hasError()) { qWarning() << Q_FUNC_INFO << "Failed to read ACBF XML document at token" << xmlReader->name() << "(" << xmlReader->lineNumber() << ":" << xmlReader->columnNumber() << ") The reported error was:" << xmlReader->errorString(); } qDebug() << Q_FUNC_INFO << "Created a text area of type" << type(); return !xmlReader->hasError(); } QList Textarea::points() const { return d->points; } QPoint Textarea::point(int index) const { return d->points.at(index); } int Textarea::pointIndex(const QPoint& point) const { return d->points.indexOf(point); } void Textarea::addPoint(const QPoint& point, int index) { if(index > -1 && d->points.count() < index) { d->points.insert(index, point); } else { d->points.append(point); } + emit pointCountChanged(); } void Textarea::removePoint(const QPoint& point) { d->points.removeAll(point); + emit pointCountChanged(); } bool Textarea::swapPoints(const QPoint& swapThis, const QPoint& withThis) { int index1 = d->points.indexOf(swapThis); int index2 = d->points.indexOf(withThis); if(index1 > -1 && index2 > -1) { d->points.swap(index1, index2); + emit pointCountChanged(); return true; } return false; } +void Textarea::setPointsFromRect(const QPoint &topLeft, const QPoint &bottomRight) +{ + QRect rect(topLeft, bottomRight); + d->points.clear(); + d->points.append(topLeft); + d->points.append(rect.topRight()); + d->points.append(rect.bottomRight()); + d->points.append(rect.bottomLeft()); + emit pointCountChanged(); +} + +int Textarea::pointCount() const +{ + return d->points.size(); +} + +QRect Textarea::bounds() const +{ + // Would use QPolygon here, but that requires including QTGUI. + QRect rect(d->points.at(0), d->points.at(1)); + for (int i = 2; i < d->points.size(); i++) { + QPoint p = d->points.at(i); + if (rect.left() > p.x()) { + rect.setLeft(p.x()); + } + if (rect.right() < p.x()) { + rect.setRight(p.x()); + } + if (rect.bottom() > p.y()) { + rect.setBottom(p.y()); + } + if (rect.top() < p.y()) { + rect.setTop(p.y()); + } + } + return rect; +} QString Textarea::bgcolor() const { return d->bgcolor; } void Textarea::setBgcolor(const QString& newColor) { d->bgcolor = newColor; + emit bgcolorChanged(); } void Textarea::setTextRotation(int rotation) { d->textRotation = rotation; + emit textRotationChanged(); } int Textarea::textRotation() const { return d->textRotation; } QString Textarea::type() const { return d->type.isEmpty() ? "speech" : d->type; } void Textarea::setType(const QString& type) { d->type = type; + emit typeChanged(); } QStringList Textarea::availableTypes() { return { QStringLiteral("speech"), // (character is speaking, text is centered) QStringLiteral("commentary"), // (accompanying commentary, text is aligned to left) QStringLiteral("formal"), // (text alignment is - justify) QStringLiteral("letter"), // (rendered in handwriting font) QStringLiteral("code"), // (rendered in monospace font) QStringLiteral("heading"), // (e.g. chapter title) QStringLiteral("audio"), // (speech emanating from an audio device, e.g., television or radio speaker, telephone, walkie-talkie, etc.) QStringLiteral("thought"), QStringLiteral("sign"), // (any kind of sign/writing, text is centered) QStringLiteral("sound"), // /new in 1.2/ }; } bool Textarea::inverted() const { return d->inverted; } void Textarea::setInverted(bool inverted) { d->inverted = inverted; + emit invertedChanged(); } bool Textarea::transparent() const { return d->transparent; } void Textarea::setTransparent(bool transparent) { d->transparent = transparent; + emit transparentChanged(); } QStringList Textarea::paragraphs() const { return d->paragraphs; } void Textarea::setParagraphs(const QStringList& paragraphs) { d->paragraphs = paragraphs; + emit paragraphsChanged(); } diff --git a/src/acbf/AcbfTextarea.h b/src/acbf/AcbfTextarea.h index cddf847..a84dd3d 100644 --- a/src/acbf/AcbfTextarea.h +++ b/src/acbf/AcbfTextarea.h @@ -1,172 +1,225 @@ /* * Copyright (C) 2016 Dan Leinir Turthra Jensen * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * */ #ifndef ACBFTEXTAREA_H #define ACBFTEXTAREA_H #include #include "AcbfTextlayer.h" #include +#include /** * \brief Class to handle the text areas in ACBF * * ACBF text areas are a collection of points, some text * and some extra information on how to render that text if necessary. */ namespace AdvancedComicBookFormat { class ACBF_EXPORT Textarea : public QObject { Q_OBJECT + Q_PROPERTY(QString bgcolor READ bgcolor WRITE setBgcolor NOTIFY bgcolorChanged) + Q_PROPERTY(int pointCount READ pointCount NOTIFY pointCountChanged) + Q_PROPERTY(QRect bounds READ bounds NOTIFY boundsChanged) + Q_PROPERTY(int textRotation READ textRotation WRITE setTextRotation NOTIFY textRotationChanged) + Q_PROPERTY(QString type READ type WRITE setType NOTIFY typeChanged) + Q_PROPERTY(bool inverted READ inverted WRITE setInverted NOTIFY invertedChanged) + Q_PROPERTY(bool transparent READ transparent WRITE setTransparent NOTIFY transparentChanged) + Q_PROPERTY(QStringList paragraphs READ paragraphs WRITE setParagraphs NOTIFY paragraphsChanged) + public: explicit Textarea(Textlayer* parent = nullptr); ~Textarea() override; /** * @return the types that a text-area can be, like speech or commentary. */ static QStringList availableTypes(); /** * \brief Write the textarea into the xml writer. */ void toXml(QXmlStreamWriter* writer); /** * \brief load a textarea element into this object. * @return True if the xmlReader encountered no errors. */ bool fromXml(QXmlStreamReader *xmlReader); /** * @return a list of points that encompasses the textarea. */ QList points() const; /** * @param index - the index of the desired point. * @return a point for an index. */ - QPoint point(int index) const; + Q_INVOKABLE QPoint point(int index) const; /** * @param point - a point from the points list. * @return the index of the given point. */ - int pointIndex(const QPoint& point) const; + Q_INVOKABLE int pointIndex(const QPoint& point) const; /** * \brief add a point to the points list. * @param point - the point to add. Coordinates should be in pixels. * @param index - the index to add it at. If afterIndex is larger than zero, * the insertion will happen at that index */ - void addPoint(const QPoint& point, int index = -1); + Q_INVOKABLE void addPoint(const QPoint& point, int index = -1); /** * \brief remove a point from the list. * @param point - point to remove from the list. */ - void removePoint(const QPoint& point); + Q_INVOKABLE void removePoint(const QPoint& point); /** * \brief Swap two points in the list. * @param swapThis - the first points to swap. * @param withThis - the second points to swap. */ bool swapPoints(const QPoint& swapThis, const QPoint& withThis); + /** + * @brief set the points based on a top left and bottom right. + * @param topLeft the topleft corner of the rect. + * @param bottomRight the bottomright corner of the rect. + */ + Q_INVOKABLE void setPointsFromRect(const QPoint& topLeft, const QPoint& bottomRight); + int pointCount() const; + /** + * @brief fires when the point counts changes. + */ + Q_SIGNAL void pointCountChanged(); + /** + * @brief convenience function to get the ractangle of the points. + * @return the bounds of the frame. + */ + QRect bounds() const; + /** + * @brief fires when the bounds change, which happens when the point count changes. + */ + Q_SIGNAL void boundsChanged(); /** * @return the background color as a QString. * * It should be an 8bit per channel rgb hexcode. */ QString bgcolor() const; /** * \brief set the background color. * * @param newColor - a String with an 8bit per channel rgb hexcode (#ff00ff, or the like) */ void setBgcolor(const QString& newColor = QString()); + /** + * @brief fires when the background color changes. + */ + Q_SIGNAL void bgcolorChanged(); /** * @return the text rotation in degrees. */ int textRotation() const; /** * \brief set the text rotation. * @param rotation - the text rotation in degrees. */ void setTextRotation(int rotation = 0); + /** + * @brief fires when the text rotation is set. + */ + Q_SIGNAL void textRotationChanged(); /** * @return the type of the text area. This determines how it is styled. */ QString type() const; /** * \brief set the type of the text area. * @param type - this should be an entry from the availableTypes(), * it will change the way how the text is styled. The default is "speech". */ void setType(const QString& type = QStringLiteral("speech")); + /** + * @brief fires when the type is set. + */ + Q_SIGNAL void typeChanged(); /** * @return whether the text should use the 'inverted' colorscheme. */ bool inverted() const; /** * \brief set whether the text should use the inverted colorscheme. * @param inverted - whether to do so. */ void setInverted(bool inverted = false); - + /** + * @brief fires when inverted is set. + */ + Q_SIGNAL void invertedChanged(); /** * @return whether to use the background color when overlaying the text. */ bool transparent() const; /** * \brief set whether to use the background color when overlaying the text. * @param transparent - false means to use the background color, true means * to have the textare background transparent. */ void setTransparent(bool transparent = false); + /** + * @brief fires when transparent is set. + */ + Q_SIGNAL void transparentChanged(); /** * @returns a list of paragraphs. * * Contains allowed sub-elements: strong, emphasis, strikethrough * sub, sup, a (with mandatory href attribute only) * Can also contain deprecated sub-elements (superceded by...): code (type option code), * inverted (textarea option inverted) */ QStringList paragraphs() const; /** * \brief set the list of paragraphs for this textarea. * @param paragraphs - a list of paragraphs. Can contain sub-elements: * strong, emphasis, strikethrough, sub, sup, a (with mandatory href attribute only) */ void setParagraphs(const QStringList& paragraphs); + /** + * @brief fires when the paragraphs are set. + */ + Q_SIGNAL void paragraphsChanged(); private: class Private; std::unique_ptr d; }; } #endif//ACBFTEXTAREA_H diff --git a/src/acbf/AcbfTextlayer.cpp b/src/acbf/AcbfTextlayer.cpp index ab6061a..dbb87e6 100644 --- a/src/acbf/AcbfTextlayer.cpp +++ b/src/acbf/AcbfTextlayer.cpp @@ -1,151 +1,171 @@ /* * Copyright (C) 2016 Dan Leinir Turthra Jensen * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * */ #include "AcbfTextlayer.h" #include "AcbfTextarea.h" #include #include #include using namespace AdvancedComicBookFormat; class Textlayer::Private { public: Private() {} QString language; QString bgcolor; QList textareas; }; Textlayer::Textlayer(Page* parent) : QObject(parent) , d(new Private) { + qRegisterMetaType("Textlayer*"); } Textlayer::~Textlayer() = default; void Textlayer::toXml(QXmlStreamWriter* writer) { writer->writeStartElement(QStringLiteral("text-layer")); if(!d->language.isEmpty()) { writer->writeAttribute(QStringLiteral("lang"), d->language); } if(!d->bgcolor.isEmpty()) { writer->writeAttribute(QStringLiteral("bgcolor"), d->bgcolor); } Q_FOREACH(Textarea* area, d->textareas) { area->toXml(writer); } writer->writeEndElement(); } bool Textlayer::fromXml(QXmlStreamReader *xmlReader) { setBgcolor(xmlReader->attributes().value(QStringLiteral("bgcolor")).toString()); setLanguage(xmlReader->attributes().value(QStringLiteral("lang")).toString()); while(xmlReader->readNextStartElement()) { if(xmlReader->name() == QStringLiteral("text-area")) { Textarea* newArea = new Textarea(this); if(!newArea->fromXml(xmlReader)) { return false; } d->textareas.append(newArea); } else { qWarning() << Q_FUNC_INFO << "currently unsupported subsection:" << xmlReader->name(); xmlReader->skipCurrentElement(); } } if (xmlReader->hasError()) { qWarning() << Q_FUNC_INFO << "Failed to read ACBF XML document at token" << xmlReader->name() << "(" << xmlReader->lineNumber() << ":" << xmlReader->columnNumber() << ") The reported error was:" << xmlReader->errorString(); } qDebug() << Q_FUNC_INFO << "Created a text layer with" << d->textareas.count() << "text areas"; return !xmlReader->hasError(); } QString Textlayer::language() const { return d->language; } void Textlayer::setLanguage(const QString& language) { d->language = language; + emit languageChanged(); } QString Textlayer::bgcolor() const { return d->bgcolor; } void Textlayer::setBgcolor(const QString& newColor) { d->bgcolor = newColor; + emit bgcolorChanged(); } QList