diff --git a/src/acbf/AcbfPage.cpp b/src/acbf/AcbfPage.cpp index 5327350..13f9f17 100644 --- a/src/acbf/AcbfPage.cpp +++ b/src/acbf/AcbfPage.cpp @@ -1,440 +1,456 @@ /* * 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 "AcbfTextarea.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; emit bgcolorChanged(); } 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 { if (d->title.count()==0) { return ""; } if (!d->title.keys().contains(language)) { d->title.values().at(0); } QString title = d->title.value(language); if (title.isEmpty()) { title = d->title.values().at(0); } return title; } 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 { if (!d->textLayers.keys().contains("") && language == QString()) { return d->textLayers.values().at(0); } 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); textLayer->setLanguage(language); setTextLayer(textLayer, language); } void Page::removeTextLayer(const QString &language) { setTextLayer(nullptr, language); } void Page::duplicateTextLayer(const QString &languageFrom, const QString &languageTo) { Textlayer* to = new Textlayer(this); to->setLanguage(languageTo); if (d->textLayers[languageFrom]) { Textlayer* from = d->textLayers[languageFrom]; to->setBgcolor(from->bgcolor()); - for (int i=0; itextareaCount(); i++) { + for (int i=0; itextareaPointStrings().size(); i++) { to->addTextarea(i); to->textarea(i)->setBgcolor(from->textarea(i)->bgcolor()); to->textarea(i)->setInverted(from->textarea(i)->inverted()); to->textarea(i)->setTransparent(from->textarea(i)->transparent()); to->textarea(i)->setTextRotation(from->textarea(i)->textRotation()); to->textarea(i)->setType(from->textarea(i)->type()); to->textarea(i)->setParagraphs(from->textarea(i)->paragraphs()); for (int p=0; ptextarea(i)->pointCount(); p++) { to->textarea(i)->addPoint(from->textarea(i)->point(p)); } } } setTextLayer(to); } QStringList Page::textLayerLanguages() const { if (d->textLayers.isEmpty()) { return QStringList(); } 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(); + emit framePointStringsChanged(); } void Page::removeFrame(Frame* frame) { d->frames.removeAll(frame); - emit frameCountChanged(); + emit framePointStringsChanged(); } void Page::removeFrame(int index) { 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(); + emit framePointStringsChanged(); return true; } return false; } -int Page::frameCount() +QStringList Page::framePointStrings() { - return d->frames.size(); + QStringList frameList; + for (int i=0; iframes.size(); i++) { + QStringList framePoints; + for (int p=0; p< frame(i)->pointCount(); p++) { + framePoints.append(QString("%1,%2").arg(frame(i)->point(p).x()).arg(frame(i)->point(p).y())); + } + frameList.append(framePoints.join(" ")); + } + return frameList; } 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(); + emit jumpPointStringsChanged(); } 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(); + emit jumpPointStringsChanged(); } void Page::removeJump(int index) { removeJump(jump(index)); } bool Page::swapJumps(int swapThis, int withThis) { if(swapThis > -1 && withThis > -1) { d->jumps.swap(swapThis, withThis); - emit jumpCountChanged(); + emit jumpPointStringsChanged(); return true; } return false; } -int Page::jumpCount() +QStringList Page::jumpPointStrings() { - return d->jumps.size(); + QStringList jumpList; + for (int i=0; ijumps.size(); i++) { + QStringList points; + for (int p=0; p< jump(i)->pointCount(); p++) { + points.append(QString("%1,%2").arg(jump(i)->point(p).x()).arg(jump(i)->point(p).y())); + } + jumpList.append(points.join(" ")); + } + return jumpList; } 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 d9fcc0c..71ac3b3 100644 --- a/src/acbf/AcbfPage.h +++ b/src/acbf/AcbfPage.h @@ -1,324 +1,327 @@ /* * 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) + Q_PROPERTY(QStringList framePointStrings READ framePointStrings NOTIFY framePointStringsChanged) + Q_PROPERTY(QStringList jumpPointStrings READ jumpPointStrings NOTIFY jumpPointStringsChanged) 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. */ Q_INVOKABLE static QStringList availableTransitions(); /** * @return all titles for this page in all languages. */ 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. */ 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). */ 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. */ 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 = 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 duplicate a text layer to a different language, if languageFrom doesn't * exist this makes a new text layer. * @param languageFrom the language from which to duplicate. * @param languageTo the language to make the new text layer at. */ Q_INVOKABLE void duplicateTextLayer(const QString&languageFrom, const QString& languageTo = 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. */ 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 index to swap. * @param withThis - the second index to swap. */ Q_INVOKABLE bool swapFrames(int swapThis, int withThis); /** - * @brief frameCount - * @return the total amount of frames. + * @brief returns the amount of frames as a stringlist. + * This is a hack to ensure that qml repeaters may update properly. + * @return a string list containing strings with the point counts of said frames. */ - int frameCount(); + QStringList framePointStrings(); /** - * @brief fires when the frame count changes. + * @brief fires when the frame point strings change. */ - Q_SIGNAL void frameCountChanged(); + Q_SIGNAL void framePointStringsChanged(); /** * @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. */ 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 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. + * This is a hack to ensure QML updates properly when swapJumps is called. + * @returns a QStringlist with the jump's points. */ - int jumpCount(); + QStringList jumpPointStrings(); /** - * @brief changes when the jumpcount changes. + * @brief changes when the jump point strings change. */ - Q_SIGNAL void jumpCountChanged(); + Q_SIGNAL void jumpPointStringsChanged(); /** * @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/AcbfTextlayer.cpp b/src/acbf/AcbfTextlayer.cpp index dbb87e6..74c54f4 100644 --- a/src/acbf/AcbfTextlayer.cpp +++ b/src/acbf/AcbfTextlayer.cpp @@ -1,171 +1,179 @@ /* * 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