diff --git a/OpenBoard.pro b/OpenBoard.pro index b0d04b70..292aa2de 100644 --- a/OpenBoard.pro +++ b/OpenBoard.pro @@ -65,6 +65,11 @@ DEPENDPATH += src/pdf-merger INCLUDEPATH += src/pdf-merger include(src/pdf-merger/pdfMerger.pri) + +#plugins +include(plugins/plugins.pri) +INCLUDEPATH += plugins/cffadaptor/src + #ThirdParty DEPENDPATH += $$THIRD_PARTY_PATH/quazip/ INCLUDEPATH += $$THIRD_PARTY_PATH/quazip/ @@ -82,7 +87,8 @@ FORMS += resources/forms/mainWindow.ui \ resources/forms/trapFlash.ui \ resources/forms/youTubePublishingDialog.ui \ resources/forms/capturePublishing.ui \ - resources/forms/intranetPodcastPublishingDialog.ui + resources/forms/intranetPodcastPublishingDialog.ui \ + resources/forms/webPublishing.ui UB_ETC.files = resources/etc UB_I18N.files = resources/i18n/*.qm diff --git a/plugins/cffadaptor/UBCFFAdaptor.pro b/plugins/cffadaptor/UBCFFAdaptor.pro new file mode 100644 index 00000000..f8214d5d --- /dev/null +++ b/plugins/cffadaptor/UBCFFAdaptor.pro @@ -0,0 +1,69 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2012-02-14T13:30:14 +# +#------------------------------------------------- + +TARGET = CFF_Adaptor +TEMPLATE = lib + +win32: SUB_DIR = win32 +macx: SUB_DIR = macx +linux-g++: SUB_DIR = linux +linux-g++-32: SUB_DIR = linux +linux-g++-64: SUB_DIR = linux + +THIRD_PARTY_PATH = ../../../Sankore-ThirdParty +QUAZIP_DIR = "$$PWD/../../../Sankore-ThirdParty/quazip/quazip-0.3" + +BUILD_DIR = build/$$SUB_DIR +CONFIG(debug, debug|release):BUILD_DIR = $$BUILD_DIR/debug +CONFIG(release, debug|release) { + BUILD_DIR = $$BUILD_DIR/release + CONFIG += warn_off +} + +win32{ + CONFIG += dll +} + +OBJECTS_DIR = $$BUILD_DIR/objects +MOC_DIR = $$BUILD_DIR/moc +DESTDIR = $$BUILD_DIR/lib +RCC_DIR = $$BUILD_DIR/rcc +SANKORE_DIR = ../.. +win32:{ + QMAKE_POST_LINK += copy $$replace(DESTDIR,/,\\)\\CFF_Adaptor.dll" $$replace(SANKORE_DIR,/,\\)\\CFF_Adaptor.dll /y" +} +macx:{ + QMAKE_POST_LINK += bash postScript_mac.sh "$$DESTDIR" "$$SANKORE_DIR/$$BUILD_DIR/product/Open-Sankore.app/Contents/MacOS" +} + +LIBS += "-L$$THIRD_PARTY_PATH/quazip/lib/$$SUB_DIR" "-lquazip" + +QT += xml xmlpatterns core +QT += gui +QT += svg + + +DEFINES += UBCFFADAPTOR_LIBRARY +DEFINES += NO_THIRD_PARTY_WARNINGS + +INCLUDEPATH += src + +DEPENDPATH += $$THIRD_PARTY_PATH/quazip/ +INCLUDEPATH += $$THIRD_PARTY_PATH/quazip/ +include($$THIRD_PARTY_PATH/quazip/quazip.pri) + + +SOURCES += \ + src/UBCFFAdaptor.cpp + +HEADERS +=\ + $$PWD/../../src/globals/UBGlobals.h \ + src/UBCFFAdaptor.h \ + src/UBCFFAdaptor_global.h \ + src/UBCFFConstants.h + +RESOURCES += \ + resources/resources.qrc \ No newline at end of file diff --git a/plugins/cffadaptor/postScript_mac.sh b/plugins/cffadaptor/postScript_mac.sh new file mode 100644 index 00000000..dfe0fb63 --- /dev/null +++ b/plugins/cffadaptor/postScript_mac.sh @@ -0,0 +1,5 @@ +#!/bin/bash +SOURCE=$1 +DESTINATION=$2 +mkdir -p $DESTINATION +cp -R $SOURCE/ $DESTINATION \ No newline at end of file diff --git a/plugins/cffadaptor/resources/images/soundOn.svg b/plugins/cffadaptor/resources/images/soundOn.svg new file mode 100644 index 00000000..79d27706 --- /dev/null +++ b/plugins/cffadaptor/resources/images/soundOn.svg @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/plugins/cffadaptor/resources/resources.qrc b/plugins/cffadaptor/resources/resources.qrc new file mode 100644 index 00000000..0a4ec471 --- /dev/null +++ b/plugins/cffadaptor/resources/resources.qrc @@ -0,0 +1,5 @@ + + + images/soundOn.svg + + diff --git a/plugins/cffadaptor/src/UBCFFAdaptor.cpp b/plugins/cffadaptor/src/UBCFFAdaptor.cpp new file mode 100644 index 00000000..a7b25f2c --- /dev/null +++ b/plugins/cffadaptor/src/UBCFFAdaptor.cpp @@ -0,0 +1,2069 @@ +/* + * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA) + * + * This file is part of Open-Sankoré. + * + * Open-Sankoré is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License, + * with a specific linking exception for the OpenSSL project's + * "OpenSSL" library (or with modified versions of it that use the + * same license as the "OpenSSL" library). + * + * Open-Sankoré 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Open-Sankoré. If not, see . + */ + + + +#include "UBCFFAdaptor.h" + +#include +#include +#include +#include +#include +#include + +#include "UBGlobals.h" +#include "UBCFFConstants.h" + +THIRD_PARTY_WARNINGS_DISABLE +#include "quazip.h" +#include "quazipfile.h" +#include "quazipfileinfo.h" +THIRD_PARTY_WARNINGS_ENABLE + +UBCFFAdaptor::UBCFFAdaptor() +{} + +bool UBCFFAdaptor::convertUBZToIWB(const QString &from, const QString &to) +{ + qDebug() << "starting converion from" << from << "to" << to; + + QString source = QString(); + if (QFileInfo(from).isDir() && QFile::exists(from)) { + qDebug() << "File specified is dir, continuing convertion"; + source = from; + } else { + source = uncompressZip(from); + if (!source.isNull()) qDebug() << "File specified is zip file. Uncompressed to tmp dir, continuing convertion"; + } + if (source.isNull()) { + qDebug() << "File specified is not a dir or a zip file, stopping covretion"; + return false; + } + + QString tmpDestination = createNewTmpDir(); + if (tmpDestination.isNull()) { + qDebug() << "can't create temp destination folder. Stopping parsing..."; + return false; + } + + UBToCFFConverter tmpConvertrer(source, tmpDestination); + if (!tmpConvertrer) { + qDebug() << "The convertrer class is invalid, stopping conversion. Error message" << tmpConvertrer.lastErrStr(); + return false; + } + + bool bParceRes = tmpConvertrer.parse(); + + mConversionMessages << tmpConvertrer.getMessages(); + + if (!bParceRes) { + return false; + } + + if (!compressZip(tmpDestination, to)) + qDebug() << "error in compression"; + + //Cleanning tmp souces in filesystem + if (!QFileInfo(from).isDir()) + if (!freeDir(source)) + qDebug() << "can't delete tmp directory" << QDir(source).absolutePath() << "try to delete them manually"; + + if (!freeDir(tmpDestination)) + qDebug() << "can't delete tmp directory" << QDir(tmpDestination).absolutePath() << "try to delete them manually"; + + return true; +} + +QString UBCFFAdaptor::uncompressZip(const QString &zipFile) +{ + QuaZip zip(zipFile); + + if(!zip.open(QuaZip::mdUnzip)) { + qWarning() << "Import failed. Cause zip.open(): " << zip.getZipError(); + return QString(); + } + + zip.setFileNameCodec("UTF-8"); + QuaZipFileInfo info; + QuaZipFile file(&zip); + + //create unique cff document root fodler + QString documentRootFolder = createNewTmpDir(); + + if (documentRootFolder.isNull()) { + qDebug() << "can't create tmp directory for zip file" << zipFile; + return QString(); + } + + QDir rootDir(documentRootFolder); + QFile out; + char c; + bool allOk = true; + for(bool more = zip.goToFirstFile(); more; more=zip.goToNextFile()) { + if(!zip.getCurrentFileInfo(&info)) { + qWarning() << "Import failed. Cause: getCurrentFileInfo(): " << zip.getZipError(); + allOk = false; + break; + } + if(!file.open(QIODevice::ReadOnly)) { + allOk = false; + break; + } + if(file.getZipError()!= UNZ_OK) { + qWarning() << "Import failed. Cause: file.getFileName(): " << zip.getZipError(); + allOk = false; + break; + } + + QString newFileName = documentRootFolder + "/" + file.getActualFileName(); + + QFileInfo newFileInfo(newFileName); + rootDir.mkpath(newFileInfo.absolutePath()); + + out.setFileName(newFileName); + out.open(QIODevice::WriteOnly); + + while(file.getChar(&c)) + out.putChar(c); + + out.close(); + + if(file.getZipError()!=UNZ_OK) { + qWarning() << "Import failed. Cause: " << zip.getZipError(); + allOk = false; + break; + } + if(!file.atEnd()) { + qWarning() << "Import failed. Cause: read all but not EOF"; + allOk = false; + break; + } + file.close(); + + if(file.getZipError()!=UNZ_OK) { + qWarning() << "Import failed. Cause: file.close(): " << file.getZipError(); + allOk = false; + break; + } + } + + if (!allOk) { + out.close(); + file.close(); + zip.close(); + return QString(); + } + + if(zip.getZipError()!=UNZ_OK) { + qWarning() << "Import failed. Cause: zip.close(): " << zip.getZipError(); + return QString(); + } + + return documentRootFolder; +} + +bool UBCFFAdaptor::compressZip(const QString &source, const QString &destination) +{ + QDir toDir = QFileInfo(destination).dir(); + if (!toDir.exists()) + if (!QDir().mkpath(toDir.absolutePath())) { + qDebug() << "can't create destination folder to uncompress file"; + return false; + } + + QuaZip zip(destination); + zip.setFileNameCodec("UTF-8"); + if(!zip.open(QuaZip::mdCreate)) { + qDebug("Export failed. Cause: zip.open(): %d", zip.getZipError()); + return false; + } + + QuaZipFile outZip(&zip); + + QFileInfo sourceInfo(source); + if (sourceInfo.isDir()) { + if (!compressDir(QFileInfo(source).absoluteFilePath(), "", &outZip)) + return false; + } else if (sourceInfo.isFile()) { + if (!compressFile(QFileInfo(source).absoluteFilePath(), "", &outZip)) + return false; + } + + return true; +} + +bool UBCFFAdaptor::compressDir(const QString &dirName, const QString &parentDir, QuaZipFile *outZip) +{ + QFileInfoList dirFiles = QDir(dirName).entryInfoList(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot); + QListIterator iter(dirFiles); + while (iter.hasNext()) { + QFileInfo curFile = iter.next(); + + if (curFile.isDir()) { + if (!compressDir(curFile.absoluteFilePath(), parentDir + curFile.fileName() + "/", outZip)) { + qDebug() << "error at compressing dir" << curFile.absoluteFilePath(); + return false; + } + } else if (curFile.isFile()) { + if (!compressFile(curFile.absoluteFilePath(), parentDir, outZip)) { + return false; + } + } + } + + return true; +} + +bool UBCFFAdaptor::compressFile(const QString &fileName, const QString &parentDir, QuaZipFile *outZip) +{ + QFile sourceFile(fileName); + + if(!sourceFile.open(QIODevice::ReadOnly)) { + qDebug() << "Compression of file" << sourceFile.fileName() << " failed. Cause: inFile.open(): " << sourceFile.errorString(); + return false; + } + + if(!outZip->open(QIODevice::WriteOnly, QuaZipNewInfo(parentDir + QFileInfo(fileName).fileName(), sourceFile.fileName()))) { + qDebug() << "Compression of file" << sourceFile.fileName() << " failed. Cause: outFile.open(): " << outZip->getZipError(); + sourceFile.close(); + return false; + } + + outZip->write(sourceFile.readAll()); + if(outZip->getZipError() != UNZ_OK) { + qDebug() << "Compression of file" << sourceFile.fileName() << " failed. Cause: outFile.write(): " << outZip->getZipError(); + + sourceFile.close(); + outZip->close(); + return false; + } + + if(outZip->getZipError() != UNZ_OK) + { + qWarning() << "Compression of file" << sourceFile.fileName() << " failed. Cause: outFile.close(): " << outZip->getZipError(); + + sourceFile.close(); + outZip->close(); + return false; + } + + outZip->close(); + sourceFile.close(); + + return true; +} + +QString UBCFFAdaptor::createNewTmpDir() +{ + int tmpNumber = 0; + QDir systemTmp = QDir::temp(); + + while (true) { + QString dirName = QString("CFF_adaptor_filedata_store%1.%2") + .arg(QDateTime::currentDateTime().toString("dd_MM_yyyy_HH-mm")) + .arg(tmpNumber++); + if (!systemTmp.exists(dirName)) { + if (systemTmp.mkdir(dirName)) { + QString result = systemTmp.absolutePath() + "/" + dirName; + tmpDirs.append(result); + return result; + } else { + qDebug() << "Can't create temporary dir maybe due to permissions"; + return QString(); + } + } else if (tmpNumber == 10) { + qWarning() << "Import failed. Failed to create temporary file "; + return QString(); + } + tmpNumber++; + } + + return QString(); +} +bool UBCFFAdaptor::deleteDir(const QString& pDirPath) const +{ + if (pDirPath == "" || pDirPath == "." || pDirPath == "..") + return false; + + QDir dir(pDirPath); + + if (dir.exists()) + { + foreach(QFileInfo dirContent, dir.entryInfoList(QDir::Files | QDir::Dirs + | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System, QDir::Name)) + { + if (dirContent.isDir()) + { + deleteDir(dirContent.absoluteFilePath()); + } + else + { + if (!dirContent.dir().remove(dirContent.fileName())) + { + return false; + } + } + } + } + + return dir.rmdir(pDirPath); +} + +QList UBCFFAdaptor::getConversionMessages() +{ + return mConversionMessages; +} + +bool UBCFFAdaptor::freeDir(const QString &dir) +{ + bool result = true; + if (!deleteDir(dir)) + result = false; + + tmpDirs.removeAll(QDir(dir).absolutePath()); + + return result; +} +void UBCFFAdaptor::freeTmpDirs() +{ + foreach (QString dir, tmpDirs) + freeDir(dir); +} + +UBCFFAdaptor::~UBCFFAdaptor() +{ + freeTmpDirs(); +} + +UBCFFAdaptor::UBToCFFConverter::UBToCFFConverter(const QString &source, const QString &destination) +{ + sourcePath = source; + destinationPath = destination; + + errorStr = noErrorMsg; + mDataModel = new QDomDocument; + mDocumentToWrite = new QDomDocument; + mDocumentToWrite->setContent(QString("")); + + mIWBContentWriter = new QXmlStreamWriter; + mIWBContentWriter->setAutoFormatting(true); + + iwbSVGItemsAttributes.insert(tIWBImage, iwbSVGImageAttributes); + iwbSVGItemsAttributes.insert(tIWBVideo, iwbSVGVideoAttributes); + iwbSVGItemsAttributes.insert(tIWBText, iwbSVGTextAttributes); + iwbSVGItemsAttributes.insert(tIWBTextArea, iwbSVGTextAreaAttributes); + iwbSVGItemsAttributes.insert(tIWBPolyLine, iwbSVGPolyLineAttributes); + iwbSVGItemsAttributes.insert(tIWBPolygon, iwbSVGPolygonAttributes); + iwbSVGItemsAttributes.insert(tIWBRect, iwbSVGRectAttributes); + iwbSVGItemsAttributes.insert(tIWBLine, iwbSVGLineAttributes); + iwbSVGItemsAttributes.insert(tIWBTspan, iwbSVGTspanAttributes); +} + +bool UBCFFAdaptor::UBToCFFConverter::parse() +{ + if(!isValid()) { + qDebug() << "document metadata is not valid. Can't parse"; + return false; + } + + qDebug() << "begin parsing ubz"; + + QFile outFile(contentIWBFileName()); + if (!outFile.open(QIODevice::WriteOnly| QIODevice::Text)) { + qDebug() << "can't open output file for writing"; + errorStr = "createXMLOutputPatternError"; + return false; + } + + mIWBContentWriter->setDevice(&outFile); + + mIWBContentWriter->writeStartDocument(); + mIWBContentWriter->writeStartElement(tIWBRoot); + + fillNamespaces(); + + mIWBContentWriter->writeAttribute(aIWBVersion, avIWBVersionNo); + + if (!parseMetadata()) { + if (errorStr == noErrorMsg) + errorStr = "MetadataParsingError"; + + outFile.close(); + return false; + } + + if (!parseContent()) { + if (errorStr == noErrorMsg) + errorStr = "ContentParsingError"; + outFile.close(); + return false; + } + + mIWBContentWriter->writeEndElement(); + mIWBContentWriter->writeEndDocument(); + + outFile.close(); + + qDebug() << "finished with success"; + + return true; +} +bool UBCFFAdaptor::UBToCFFConverter::parseMetadata() +{ + int errorLine, errorColumn; + QFile metaDataFile(sourcePath + "/" + fMetadata); + + if (!metaDataFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + errorStr = "can't open" + QFileInfo(sourcePath + "/" + fMetadata).absoluteFilePath(); + qDebug() << errorStr; + return false; + + } else if (!mDataModel->setContent(metaDataFile.readAll(), true, &errorStr, &errorLine, &errorColumn)) { + qWarning() << "Error:Parseerroratline" << errorLine << "," + << "column" << errorColumn << ":" << errorStr; + return false; + } + + QDomElement nextInElement = mDataModel->documentElement(); + + nextInElement = nextInElement.firstChildElement(tDescription); + if (!nextInElement.isNull()) { + + mIWBContentWriter->writeStartElement(iwbNS, tIWBMeta); + mIWBContentWriter->writeAttribute(aIWBName, aCreator); + mIWBContentWriter->writeAttribute(aIWBContent, avCreator); + mIWBContentWriter->writeEndElement(); + + mIWBContentWriter->writeStartElement(iwbNS, tIWBMeta); + mIWBContentWriter->writeAttribute(aIWBName, aOwner); + mIWBContentWriter->writeAttribute(aIWBContent, avOwner); + mIWBContentWriter->writeEndElement(); + + mIWBContentWriter->writeStartElement(iwbNS, tIWBMeta); + mIWBContentWriter->writeAttribute(aIWBName, aDescription); + mIWBContentWriter->writeAttribute(aIWBContent, avDescription); + mIWBContentWriter->writeEndElement(); + + mIWBContentWriter->writeStartElement(iwbNS, tIWBMeta); + mIWBContentWriter->writeAttribute(aIWBName, aAbout); + mIWBContentWriter->writeAttribute(aIWBContent, nextInElement.attribute(aAbout)); + mIWBContentWriter->writeEndElement(); + + nextInElement = nextInElement.firstChildElement(); + while (!nextInElement.isNull()) { + + QString textContent = nextInElement.text(); + if (!textContent.trimmed().isEmpty()) { + if (nextInElement.tagName() == tUBZSize) { //taking main viewbox rect since for CFF specificaton we have static viewbox + QSize tmpSize = getSVGDimentions(nextInElement.text()); + if (!tmpSize.isNull()) { + mSVGSize = tmpSize; + } else { + qDebug() << "can't interpret svg section size"; + errorStr = "InterpretSvgSizeError"; + return false; + } + } else { + mIWBContentWriter->writeStartElement(iwbNS, tIWBMeta); + mIWBContentWriter->writeAttribute(aIWBName, nextInElement.tagName()); + mIWBContentWriter->writeAttribute(aIWBContent, textContent); + mIWBContentWriter->writeEndElement(); + } + + } + nextInElement = nextInElement.nextSiblingElement(); + } + } + + metaDataFile.close(); + return true; +} +bool UBCFFAdaptor::UBToCFFConverter::parseContent() { + + QDir sourceDir(sourcePath); + QStringList fileFilters; + fileFilters << QString(pageAlias + "???." + pageFileExtentionUBZ); + QStringList pageList = sourceDir.entryList(fileFilters, QDir::Files, QDir::Name | QDir::IgnoreCase); + + QDomElement svgDocumentSection = mDataModel->createElementNS(svgIWBNS, ":"+tSvg); + + if (!pageList.count()) { + qDebug() << "can't find any content file"; + errorStr = "ErrorContentFile"; + return false; + } else + { + QDomElement pageset = parsePageset(pageList); + if (pageset.isNull()) + return false; + else + svgDocumentSection.appendChild(pageset); + } + + + if (QRect() == mViewbox) + { + mViewbox.setRect(0,0, mSVGSize.width(), mSVGSize.height()); + } + + svgDocumentSection.setAttribute(aIWBViewBox, rectToIWBAttr(mViewbox)); + svgDocumentSection.setAttribute(aWidth, QString("%1").arg(mViewbox.width())); + svgDocumentSection.setAttribute(aHeight, QString("%1").arg(mViewbox.height())); + + + writeQDomElementToXML(svgDocumentSection); + + + if (!writeExtendedIwbSection()) { + if (errorStr == noErrorMsg) + errorStr = "writeExtendedIwbSectionError"; + return false; + } + + return true; +} + +QDomElement UBCFFAdaptor::UBToCFFConverter::parsePage(const QString &pageFileName) +{ + qDebug() << "begin parsing page" + pageFileName; + mSvgElements.clear(); //clean Svg elements map before parsing new page + + int errorLine, errorColumn; + + QFile pageFile(sourcePath + "/" + pageFileName); + if (!pageFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + qDebug() << "can't open file" << pageFileName << "for reading"; + return QDomElement(); + } else if (!mDataModel->setContent(pageFile.readAll(), true, &errorStr, &errorLine, &errorColumn)) { + qWarning() << "Error:Parseerroratline" << errorLine << "," + << "column" << errorColumn << ":" << errorStr; + pageFile.close(); + return QDomElement(); + } + + QDomElement page; + QDomElement group; + + QDomElement nextTopElement = mDataModel->firstChildElement(); + while (!nextTopElement.isNull()) { + QString tagname = nextTopElement.tagName(); + if (tagname == tSvg) { + page = parseSvgPageSection(nextTopElement); + if (page.isNull()) { + qDebug() << "The page is empty."; + pageFile.close(); + return QDomElement(); + } + } else if (tagname == tUBZGroups) { + group = parseGroupsPageSection(nextTopElement); + if (group.isNull()) { + qDebug() << "Page doesn't contains any groups."; + pageFile.close(); + return QDomElement(); + } + } + + nextTopElement = nextTopElement.nextSiblingElement(); + } + + pageFile.close(); + + return page.hasChildNodes() ? page : QDomElement(); +} + +QDomElement UBCFFAdaptor::UBToCFFConverter::parsePageset(const QStringList &pageFileNames) +{ + QMultiMap pageList; + int iPageNo = 1; + + QStringListIterator curPage(pageFileNames); + + while (curPage.hasNext()) { + + QString curPageFile = curPage.next(); + QDomElement iterElement = parsePage(curPageFile); + if (!iterElement.isNull()) + { + iterElement.setAttribute(tId, iPageNo); + addSVGElementToResultModel(iterElement, pageList, iPageNo); + iPageNo++; + } + else + return QDomElement(); + } + + + if (!pageList.count()) + return QDomElement(); + + + QDomElement svgPagesetElement = mDocumentToWrite->createElementNS(svgIWBNS,":"+ tIWBPageSet); + + QMapIterator nextSVGElement(pageList); + nextSVGElement.toFront(); + while (nextSVGElement.hasNext()) + svgPagesetElement.appendChild(nextSVGElement.next().value()); + + return svgPagesetElement.hasChildNodes() ? svgPagesetElement : QDomElement(); +} +QDomElement UBCFFAdaptor::UBToCFFConverter::parseSvgPageSection(const QDomElement &element) +{ + //we don't know about page number, so return QDomElement. + + //Parsing top level tag attributes + + //getting current page viewbox to be able to convert coordinates to global viewbox parameter + if (element.hasAttribute(aUBZViewBox)) { + setViewBox(getViewboxRect(element.attribute(aUBZViewBox))); + } + + QMultiMap svgElements; + + QDomElement svgElementPart = mDocumentToWrite->createElementNS(svgIWBNS,":"+ tIWBPage); + + if (element.hasAttribute(aDarkBackground)) { + createBackground(element, svgElements); + } + + //Parsing svg children attributes + // Elements can know about its layer, so it must add result QDomElements to ordrered list. + QDomElement nextElement = element.firstChildElement(); + while (!nextElement.isNull()) { + QString tagName = nextElement.tagName(); + if (tagName == tUBZG) parseSVGGGroup(nextElement, svgElements); + else if (tagName == tUBZImage) parseUBZImage(nextElement, svgElements); + else if (tagName == tUBZVideo) parseUBZVideo(nextElement, svgElements); + else if (tagName == tUBZAudio) parseUBZAudio(nextElement, svgElements); + else if (tagName == tUBZForeignObject) parseForeignObject(nextElement, svgElements); + else if (tagName == tUBZLine) parseUBZLine(nextElement, svgElements); + else if (tagName == tUBZPolygon) parseUBZPolygon(nextElement, svgElements); + else if (tagName == tUBZPolyline) parseUBZPolyline(nextElement, svgElements); + else if (tagName == tUBZGroups) parseGroupsPageSection(nextElement); + + nextElement = nextElement.nextSiblingElement(); + } + + if (0 == svgElements.count()) + return QDomElement(); + + // to do: + // there we must to sort elements (take elements from list and assign parent ordered like in parseSVGGGroup) + // we returns just element because we don't care about layer. + QMapIterator nextSVGElement(svgElements); + nextSVGElement.toFront(); + while (nextSVGElement.hasNext()) + svgElementPart.appendChild(nextSVGElement.next().value()); + + return svgElementPart.hasChildNodes() ? svgElementPart : QDomElement(); +} + +void UBCFFAdaptor::UBToCFFConverter::writeQDomElementToXML(const QDomNode &node) +{ + if (!node.isNull()) { + if (node.isText()) + mIWBContentWriter->writeCharacters(node.nodeValue()); + else { + mIWBContentWriter->writeStartElement(node.namespaceURI(), node.toElement().tagName()); + + for (int i = 0; i < node.toElement().attributes().count(); i++) { + QDomAttr attr = node.toElement().attributes().item(i).toAttr(); + mIWBContentWriter->writeAttribute(attr.name(), attr.value()); + } + QDomNode child = node.firstChild(); + while(!child.isNull()) { + writeQDomElementToXML(child); + child = child.nextSibling(); + } + + mIWBContentWriter->writeEndElement(); + } + } +} + +bool UBCFFAdaptor::UBToCFFConverter::writeExtendedIwbSection() +{ + if (!mExtendedElements.count()) { + qDebug() << "extended iwb content list is empty"; + errorStr = "EmptyExtendedIwbSectionContentError"; + return false; + } + QListIterator nextExtendedIwbElement(mExtendedElements); + while (nextExtendedIwbElement.hasNext()) { + writeQDomElementToXML(nextExtendedIwbElement.next()); + //TODO write iwb extended element to mIWBContentWriter + } + + return true; +} + +// extended element options +// editable, background, locked are supported for now + +QDomElement UBCFFAdaptor::UBToCFFConverter::parseGroupsPageSection(const QDomElement &groupRoot) +{ +// First sankore side implementation needed. TODO in Sankore 1.5 + if (!groupRoot.hasChildNodes()) { + qDebug() << "Group root is empty"; + return QDomElement(); + } + + QDomElement groupElement = groupRoot.firstChildElement(); + + while (!groupElement.isNull()) { + QDomElement extendedElement = mDataModel->createElementNS(iwbNS, groupElement.tagName()); + QDomElement groupChildElement = groupElement.firstChildElement(); + while (!groupChildElement.isNull()) { + QDomElement extSubElement = mDataModel->createElementNS(iwbNS, groupChildElement.tagName()); + extSubElement.setAttribute(aRef, groupChildElement.attribute(aID, QUuid().toString())); + extendedElement.appendChild(extSubElement); + + groupChildElement = groupChildElement.nextSiblingElement(); + } + + mExtendedElements.append(extendedElement); + + groupElement = groupElement.nextSiblingElement(); + } + + qDebug() << "parsing ubz group section"; + return groupRoot; +} + +QString UBCFFAdaptor::UBToCFFConverter::getDstContentFolderName(const QString &elementType) +{ + QString sRet; + QString sDstContentFolderName; + + // widgets must be saved as .png images. + if ((tIWBImage == elementType) || (tUBZForeignObject == elementType)) + sDstContentFolderName = cfImages; + else + if (tIWBVideo == elementType) + sDstContentFolderName = cfVideos; + else + if (tIWBAudio == elementType) + sDstContentFolderName = cfAudios; + + sRet = sDstContentFolderName; + + return sRet; +} + +QString UBCFFAdaptor::UBToCFFConverter::getSrcContentFolderName(QString href) +{ + QString sRet; + + QStringList ls = href.split("/"); + for (int i = 0; i < ls.count()-1; i++) + { + QString sPart = ls.at(i); + if (ubzContentFolders.contains(sPart)) + { + sRet = sPart; + } + } + +// if (0 < ls.count()) +// sRet = ls.at(ls.count()-1); +// +// sRet = href.remove(sRet); +// +// if (sRet.endsWith("/")) +// sRet.remove("/"); + + return sRet; +} + +QString UBCFFAdaptor::UBToCFFConverter::getFileNameFromPath(const QString sPath) +{ + QString sRet; + QStringList sl = sPath.split("/",QString::SkipEmptyParts); + + if (0 < sl.count()) + { + QString name = sl.at(sl.count()-1); + QString extention = getExtentionFromFileName(name); + + if (feWgt == extention) + { + name.remove("{"); + name.remove("}"); + } + + name.remove(name.length()-extention.length(), extention.length()); + name += convertExtention(extention); + + sRet = name; + } + return sRet; +} + +QString UBCFFAdaptor::UBToCFFConverter::getExtentionFromFileName(const QString &filename) +{ + QStringList sl = filename.split("/",QString::SkipEmptyParts); + + if (0 < sl.count()) + { + QString name = sl.at(sl.count()-1); + QStringList tl = name.split("."); + return tl.at(tl.count()-1); + } + return QString(); +} + +QString UBCFFAdaptor::UBToCFFConverter::convertExtention(const QString &ext) +{ + QString sRet; + + if (feSvg == ext) + sRet = fePng; + else + if (feWgt == ext) + sRet = fePng; + else + sRet = ext; + + return sRet; +} + +QString UBCFFAdaptor::UBToCFFConverter::getElementTypeFromUBZ(const QDomElement &element) +{ + QString sRet; + if (tUBZForeignObject == element.tagName()) + { + QString sPath; + if (element.hasAttribute(aUBZType)) + { + if (avUBZText == element.attribute(aUBZType)) + sRet = tIWBTextArea; + else + sRet = element.attribute(aUBZType); + } + else + { + if (element.hasAttribute(aSrc)) + sPath = element.attribute(aSrc); + else + if (element.hasAttribute(aUBZHref)) + sPath = element.attribute(aUBZHref); + + QStringList tsl = sPath.split(".", QString::SkipEmptyParts); + if (0 < tsl.count()) + { + QString elementType = tsl.at(tsl.count()-1); + if (iwbElementImage.contains(elementType)) + sRet = tIWBImage; + else + if (iwbElementAudio.contains(elementType)) + sRet = tIWBAudio; + else + if (iwbElementVideo.contains(elementType)) + sRet = tIWBVideo; + } + } + } + else + sRet = element.tagName(); + + return sRet; +} + +int UBCFFAdaptor::UBToCFFConverter::getElementLayer(const QDomElement &element) +{ + int iRetLayer = 0; + if (element.hasAttribute(aZLayer)) + iRetLayer = (int)element.attribute(aZLayer).toDouble(); + else + iRetLayer = DEFAULT_LAYER; + + return iRetLayer; +} + +bool UBCFFAdaptor::UBToCFFConverter::itIsSupportedFormat(const QString &format) const +{ + bool bRet; + + QStringList tsl = format.split(".", QString::SkipEmptyParts); + if (0 < tsl.count()) + bRet = cffSupportedFileFormats.contains(tsl.at(tsl.count()-1).toLower()); + else + bRet = false; + + return bRet; +} + +bool UBCFFAdaptor::UBToCFFConverter::itIsFormatToConvert(const QString &format) const +{ + foreach (QString f, ubzFormatsToConvert.split(",")) + { + if (format == f) + return true; + } + return false; +} + +bool UBCFFAdaptor::UBToCFFConverter::itIsSVGElementAttribute(const QString ItemType, const QString &AttrName) +{ + QString allowedElementAttributes = iwbSVGItemsAttributes[ItemType]; + + allowedElementAttributes.remove("/t"); + allowedElementAttributes.remove(" "); + foreach(QString attr, allowedElementAttributes.split(",")) + { + if (AttrName == attr.trimmed()) + return true; + } + return false; +} + + +bool UBCFFAdaptor::UBToCFFConverter::itIsIWBAttribute(const QString &attribute) const +{ + foreach (QString attr, iwbElementAttributes.split(",")) + { + if (attribute == attr.trimmed()) + return true; + } + return false; +} + +bool UBCFFAdaptor::UBToCFFConverter::itIsUBZAttributeToConvert(const QString &attribute) const +{ + foreach (QString attr, ubzElementAttributesToConvert.split(",")) + { + if (attribute == attr.trimmed()) + return true; + } + return false; +} + +bool UBCFFAdaptor::UBToCFFConverter::ibwAddLine(int x1, int y1, int x2, int y2, QString color, int width, bool isBackground) +{ + bool bRet = true; + + QDomDocument doc; + + QDomElement svgBackgroundCrossPart = doc.createElementNS(svgIWBNS,svgIWBNSPrefix + ":line"); + QDomElement iwbBackgroundCrossPart = doc.createElementNS(iwbNS,iwbNsPrefix + ":" + tElement); + + QString sUUID = QUuid::createUuid().toString(); + + svgBackgroundCrossPart.setTagName(tIWBLine); + + svgBackgroundCrossPart.setAttribute(aX+"1", x1); + svgBackgroundCrossPart.setAttribute(aY+"1", y1); + svgBackgroundCrossPart.setAttribute(aX+"2", x2); + svgBackgroundCrossPart.setAttribute(aY+"2", y2); + + svgBackgroundCrossPart.setAttribute(aStroke, color); + svgBackgroundCrossPart.setAttribute(aStrokeWidth, width); + + svgBackgroundCrossPart.setAttribute(aID, sUUID); + + if (isBackground) + { + iwbBackgroundCrossPart.setAttribute(aRef, sUUID); + iwbBackgroundCrossPart.setAttribute(aLocked, avTrue); + + addIWBElementToResultModel(iwbBackgroundCrossPart); + } + + addSVGElementToResultModel(svgBackgroundCrossPart, mSvgElements, DEFAULT_BACKGROUND_CROSS_LAYER); + + if (!bRet) + { + qDebug() << "|error at creating crosses on background"; + errorStr = "CreatingCrossedBackgroundParsingError."; + } + + return bRet; +} + +QTransform UBCFFAdaptor::UBToCFFConverter::getTransformFromUBZ(const QDomElement &ubzElement) +{ + QTransform trRet; + + QStringList transformParameters; + + QString ubzTransform = ubzElement.attribute(aTransform); + ubzTransform.remove("matrix"); + ubzTransform.remove("("); + ubzTransform.remove(")"); + + transformParameters = ubzTransform.split(",", QString::SkipEmptyParts); + + if (6 <= transformParameters.count()) + { + QTransform *tr = NULL; + tr = new QTransform(transformParameters.at(0).toDouble(), + transformParameters.at(1).toDouble(), + transformParameters.at(2).toDouble(), + transformParameters.at(3).toDouble(), + transformParameters.at(4).toDouble(), + transformParameters.at(5).toDouble()); + + trRet = *tr; + + delete tr; + } + + if (6 <= transformParameters.count()) + { + QTransform *tr = NULL; + tr = new QTransform(transformParameters.at(0).toDouble(), + transformParameters.at(1).toDouble(), + transformParameters.at(2).toDouble(), + transformParameters.at(3).toDouble(), + transformParameters.at(4).toDouble(), + transformParameters.at(5).toDouble()); + + trRet = *tr; + + delete tr; + } + return trRet; +} + +qreal UBCFFAdaptor::UBToCFFConverter::getAngleFromTransform(const QTransform &tr) +{ + qreal angle = -(atan(tr.m21()/tr.m11())*180/PI); + if (tr.m21() > 0 && tr.m11() < 0) + angle += 180; + else + if (tr.m21() < 0 && tr.m11() < 0) + angle += 180; + return angle; +} + +void UBCFFAdaptor::UBToCFFConverter::setGeometryFromUBZ(const QDomElement &ubzElement, QDomElement &iwbElement) +{ + setCoordinatesFromUBZ(ubzElement,iwbElement); + + + +} + +void UBCFFAdaptor::UBToCFFConverter::setCoordinatesFromUBZ(const QDomElement &ubzElement, QDomElement &iwbElement) +{ + QTransform tr; + + if (QString() != ubzElement.attribute(aTransform)) + tr = getTransformFromUBZ(ubzElement); + + qreal x = ubzElement.attribute(aX).toDouble(); + qreal y = ubzElement.attribute(aY).toDouble(); + qreal height = ubzElement.attribute(aHeight).toDouble(); + qreal width = ubzElement.attribute(aWidth).toDouble(); + + qreal alpha = getAngleFromTransform(tr); + + QRectF itemRect; + QGraphicsRectItem item; + + item.setRect(0,0, width, height); + item.setTransform(tr); + item.setRotation(-alpha); + QMatrix sceneMatrix = item.sceneMatrix(); + + iwbElement.setAttribute(aX, x); + iwbElement.setAttribute(aY, y); + iwbElement.setAttribute(aHeight, height*sceneMatrix.m22()); + iwbElement.setAttribute(aWidth, width*sceneMatrix.m11()); + iwbElement.setAttribute(aTransform, QString("rotate(%1) translate(%2,%3)").arg(alpha) + .arg(sceneMatrix.dx()) + .arg(sceneMatrix.dy())); +} + +bool UBCFFAdaptor::UBToCFFConverter::setContentFromUBZ(const QDomElement &ubzElement, QDomElement &svgElement) +{ + bool bRet = true; + + QString srcPath; + if (tUBZForeignObject != ubzElement.tagName()) + srcPath = ubzElement.attribute(aUBZHref); + else + srcPath = ubzElement.attribute(aSrc); + + QString sSrcContentFolder = getSrcContentFolderName(srcPath); + QString sSrcFileName = sourcePath + "/" + srcPath ; + QString fileExtention = getExtentionFromFileName(sSrcFileName); + QString sDstContentFolder = getDstContentFolderName(ubzElement.tagName()); + QString sDstFileName(QString(QUuid::createUuid().toString()+"."+convertExtention(fileExtention))); + + + if (itIsSupportedFormat(fileExtention)) // format is supported and we can copy src. files without changing. + { + sSrcFileName = sourcePath + "/" + sSrcContentFolder + "/" + getFileNameFromPath(srcPath); // some elements must be exported as images, so we take hes existing thumbnails. + + QFile srcFile; + srcFile.setFileName(sSrcFileName); + + QDir dstDocFolder(destinationPath); + + if (!dstDocFolder.exists(sDstContentFolder)) + bRet &= dstDocFolder.mkdir(sDstContentFolder); + + if (bRet) + { + QString dstFilePath = destinationPath+"/"+sDstContentFolder+"/"+sDstFileName; + bRet &= srcFile.copy(dstFilePath); + } + + if (bRet) + { + svgElement.setAttribute(aSVGHref, sDstContentFolder+"/"+sDstFileName); + // NOT by standard! Enable it later! + // validator http://validator.imsglobal.org/iwb/index.jsp?validate=package + //svgElement.setAttribute(aSVGRequiredExtension, svgRequiredExtensionPrefix+convertExtention(fileExtention)); + } + } + else + if (itIsFormatToConvert(fileExtention)) // we cannot copy that source files. We need to create dst. file from src. file without copy. + { + if (feSvg == fileExtention) + { + QDir dstDocFolder(destinationPath); + + if (!dstDocFolder.exists(sDstContentFolder)) + bRet &= dstDocFolder.mkdir(sDstContentFolder); + + if (bRet) + { + if (feSvg == fileExtention) // svg images must be converted to PNG. + { + QString dstFilePath = destinationPath+"/"+sDstContentFolder+"/"+sDstFileName; + bRet &= createPngFromSvg(sSrcFileName, dstFilePath, getTransformFromUBZ(ubzElement)); + } + else + bRet = false; + } + + if (bRet) + { + svgElement.setAttribute(aSVGHref, sDstContentFolder+"/"+sDstFileName); + // NOT by standard! Enable it later! + // validator http://validator.imsglobal.org/iwb/index.jsp?validate=package + //svgElement.setAttribute(aSVGRequiredExtension, svgRequiredExtensionPrefix+fePng); + } + } + }else + { + addLastExportError(QObject::tr("Element ID = ") + QString("%1 \r\n").arg(ubzElement.attribute(aUBZUuid)) + + QString("Source file = ") + QString("%1 \r\n").arg(ubzElement.attribute(aUBZSource)) + + QObject::tr("Content is not supported in destination format.")); + bRet = false; + } + + if (!bRet) + { + qDebug() << "format is not supported by CFF"; + } + + return bRet; +} + +void UBCFFAdaptor::UBToCFFConverter::setCFFTextFromHTMLTextNode(const QDomElement htmlTextNode, QDomElement &iwbElement) +{ + + QDomDocument textDoc; + + QDomElement textParentElement = iwbElement; + + QString textString; + QDomNode htmlPNode = htmlTextNode.firstChild(); + bool bTbreak = false; + + // reads HTML text strings - each string placed in separate

section + while(!htmlPNode.isNull()) + { + // add for split strings + if (bTbreak) + { + bTbreak = false; + + QDomElement tbreakNode = textDoc.createElementNS(svgIWBNS, svgIWBNSPrefix+":"+tIWBTbreak); + textParentElement.appendChild(tbreakNode.cloneNode(true)); + } + + QDomNode spanNode = htmlPNode.firstChild(); + + while (!spanNode.isNull()) + { + if (spanNode.isText()) + { + QDomText nodeText = textDoc.createTextNode(spanNode.nodeValue()); + textParentElement.appendChild(nodeText.cloneNode(true)); + } + else + if (spanNode.isElement()) + { + QDomElement pElementIwb; + QDomElement spanElement = textDoc.createElementNS(svgIWBNS,svgIWBNSPrefix + ":" + tIWBTspan); + setCommonAttributesFromUBZ(htmlPNode.toElement(), pElementIwb, spanElement); + + if (spanNode.hasAttributes()) + { + int attrCount = spanNode.attributes().count(); + if (0 < attrCount) + { + for (int i = 0; i < attrCount; i++) + { + // html attributes like: style="font-size:40pt; color:"red";". + QStringList cffAttributes = spanNode.attributes().item(i).nodeValue().split(";", QString::SkipEmptyParts); + { + for (int i = 0; i < cffAttributes.count(); i++) + { + QString attr = cffAttributes.at(i).trimmed(); + QStringList AttrVal = attr.split(":", QString::SkipEmptyParts); + if(1 < AttrVal.count()) + { + QString sAttr = ubzAttrNameToCFFAttrName(AttrVal.at(0)); + if (itIsSVGElementAttribute(spanElement.tagName(), sAttr)) + spanElement.setAttribute(sAttr, ubzAttrValueToCFFAttrName(AttrVal.at(1))); + } + } + } + } + } + } + QDomText nodeText = textDoc.createTextNode(spanNode.firstChild().nodeValue()); + spanElement.appendChild(nodeText); + textParentElement.appendChild(spanElement.cloneNode(true)); + } + spanNode = spanNode.nextSibling(); + } + + bTbreak = true; + htmlPNode = htmlPNode.nextSibling(); + } +} + +QString UBCFFAdaptor::UBToCFFConverter::ubzAttrNameToCFFAttrName(QString cffAttrName) +{ + QString sRet = cffAttrName; + if (QString("color") == cffAttrName) + sRet = QString("fill"); + if (QString("align") == cffAttrName) + sRet = QString("text-align"); + + return sRet; +} +QString UBCFFAdaptor::UBToCFFConverter::ubzAttrValueToCFFAttrName(QString cffValue) +{ + QString sRet = cffValue; + if (QString("text") == cffValue) + sRet = QString("normal"); + + return sRet; +} + +bool UBCFFAdaptor::UBToCFFConverter::setCFFAttribute(const QString &attributeName, const QString &attributeValue, const QDomElement &ubzElement, QDomElement &iwbElement, QDomElement &svgElement) +{ + bool bRet = true; + bool bNeedsIWBSection = false; + + if (itIsIWBAttribute(attributeName)) + { + if (!((aBackground == attributeName) && (avFalse == attributeValue))) + { + iwbElement.setAttribute(attributeName, attributeValue); + bNeedsIWBSection = true; + } + } + else + if (itIsUBZAttributeToConvert(attributeName)) + { + if (aTransform == attributeName) + { + setGeometryFromUBZ(ubzElement, svgElement); + } + else + if (attributeName.contains(aUBZUuid)) + { + + QString parentId = ubzElement.attribute(aUBZParent); + QString id; + if (!parentId.isEmpty()) + id = "{" + parentId + "}" + "{" + ubzElement.attribute(aUBZUuid)+"}"; + else + id = "{" + ubzElement.attribute(aUBZUuid)+"}"; + + svgElement.setAttribute(aID, id); + } + else + if (attributeName.contains(aUBZHref)||attributeName.contains(aSrc)) + { + bRet &= setContentFromUBZ(ubzElement, svgElement); + bNeedsIWBSection = bRet||bNeedsIWBSection; + } + } + else + if (itIsSVGElementAttribute(svgElement.tagName(),attributeName)) + { + svgElement.setAttribute(attributeName, attributeValue); + } + + if (bNeedsIWBSection) + { + if (0 < iwbElement.attributes().count()) + { + + QStringList tl = ubzElement.attribute(aSVGHref).split("/"); + QString id = tl.at(tl.count()-1); + // if element already have an ID, we use it. Else we create new id for element. + if (QString() == id) + id = QUuid::createUuid().toString(); + + svgElement.setAttribute(aID, id); + iwbElement.setAttribute(aRef, id); + } + } + + return bRet; +} + +bool UBCFFAdaptor::UBToCFFConverter::setCommonAttributesFromUBZ(const QDomElement &ubzElement, QDomElement &iwbElement, QDomElement &svgElement) +{ + bool bRet = true; + + for (int i = 0; i < ubzElement.attributes().count(); i++) + { + QDomNode attribute = ubzElement.attributes().item(i); + QString attributeName = ubzAttrNameToCFFAttrName(attribute.nodeName().remove("ub:")); + + bRet &= setCFFAttribute(attributeName, ubzAttrValueToCFFAttrName(attribute.nodeValue()), ubzElement, iwbElement, svgElement); + if (!bRet) break; + } + return bRet; +} + +void UBCFFAdaptor::UBToCFFConverter::setViewBox(QRect viewbox) +{ + mViewbox |= viewbox; +} + +QDomNode UBCFFAdaptor::UBToCFFConverter::findTextNode(const QDomNode &node) +{ + QDomNode iterNode = node; + + while (!iterNode.isNull()) + { + if (iterNode.isText()) + { + if (!iterNode.isNull()) + return iterNode; + } + else + { + if (!iterNode.firstChild().isNull()) + { + QDomNode foundNode = findTextNode(iterNode.firstChild()); + if (!foundNode.isNull()) + if (foundNode.isText()) + return foundNode; + } + } + if (!iterNode.nextSibling().isNull()) + iterNode = iterNode.nextSibling(); + else + break; + } + return iterNode; +} + +QDomNode UBCFFAdaptor::UBToCFFConverter::findNodeByTagName(const QDomNode &node, QString tagName) +{ + QDomNode iterNode = node; + + while (!iterNode.isNull()) + { + QString t = iterNode.toElement().tagName(); + if (tagName == t) + return iterNode; + else + { + if (!iterNode.firstChildElement().isNull()) + { + QDomNode foundNode = findNodeByTagName(iterNode.firstChildElement(), tagName); + if (!foundNode.isNull()){ + if (foundNode.isElement()) + { + if (tagName == foundNode.toElement().tagName()) + return foundNode; + } + else + break; + } + } + } + + if (!iterNode.nextSibling().isNull()) + iterNode = iterNode.nextSibling(); + else + break; + } + return QDomNode(); + +} + +bool UBCFFAdaptor::UBToCFFConverter::createBackground(const QDomElement &element, QMultiMap &dstSvgList) +{ + qDebug() << "|creating element background"; + + + QDomDocument doc; + + //QDomElement svgBackgroundElementPart = doc.createElementNS(svgIWBNS,svgIWBNSPrefix + ":" + tUBZImage); + QDomElement svgBackgroundElementPart = doc.createElementNS(svgIWBNS,svgIWBNSPrefix + ":" + tIWBRect); + QDomElement iwbBackgroundElementPart = doc.createElementNS(iwbNS,iwbNsPrefix + ":" + tElement); + + + QRect bckRect(mViewbox); + + if (0 <= mViewbox.topLeft().x()) + bckRect.topLeft().setX(0); + + if (0 <= mViewbox.topLeft().y()) + bckRect.topLeft().setY(0); + + if (QRect() != bckRect) + { + QString sElementID = QUuid::createUuid().toString(); + + bool darkBackground = (avTrue == element.attribute(aDarkBackground)); + svgBackgroundElementPart.setAttribute(aFill, darkBackground ? "black" : "white"); + svgBackgroundElementPart.setAttribute(aID, sElementID); + svgBackgroundElementPart.setAttribute(aX, bckRect.x()); + svgBackgroundElementPart.setAttribute(aY, bckRect.y()); + svgBackgroundElementPart.setAttribute(aHeight, bckRect.height()); + svgBackgroundElementPart.setAttribute(aWidth, bckRect.width()); + + //svgBackgroundElementPart.setAttribute(aSVGHref, backgroundImagePath); + + iwbBackgroundElementPart.setAttribute(aRef, sElementID); + iwbBackgroundElementPart.setAttribute(aBackground, avTrue); + //iwbBackgroundElementPart.setAttribute(aLocked, avTrue); + + addSVGElementToResultModel(svgBackgroundElementPart, dstSvgList, DEFAULT_BACKGROUND_LAYER); + addIWBElementToResultModel(iwbBackgroundElementPart); + return true; + } + else + { + qDebug() << "|error at creating element background"; + errorStr = "CreatingElementBackgroundParsingError."; + return false; + } +} + +QString UBCFFAdaptor::UBToCFFConverter::createBackgroundImage(const QDomElement &element, QSize size) +{ + QString sRet; + + QString sDstFileName(fIWBBackground); + + bool bDirExists = true; + QDir dstDocFolder(destinationPath); + + if (!dstDocFolder.exists(cfImages)) + bDirExists &= dstDocFolder.mkdir(cfImages); + + QString dstFilePath; + if (bDirExists) + dstFilePath = destinationPath+"/"+cfImages+"/"+sDstFileName; + + if (!QFile().exists(dstFilePath)) + { + QRect rect(0,0, size.width(), size.height()); + + QImage *bckImage = new QImage(size, QImage::Format_RGB888); + + QPainter *painter = new QPainter(bckImage); + + bool darkBackground = (avTrue == element.attribute(aDarkBackground)); + + QColor bCrossColor; + + bCrossColor = darkBackground?QColor(Qt::white):QColor(Qt::blue); + int penAlpha = (int)(255/2); // default Sankore value for transform.m11 < 1 + bCrossColor.setAlpha(penAlpha); + painter->setPen(bCrossColor); + painter->setBrush(darkBackground?QColor(Qt::black):QColor(Qt::white)); + + painter->drawRect(rect); + + if (avTrue == element.attribute(aCrossedBackground)) + { + qreal firstY = ((int) (rect.y () / iCrossSize)) * iCrossSize; + + for (qreal yPos = firstY; yPos <= rect.y () + rect.height (); yPos += iCrossSize) + { + painter->drawLine (rect.x (), yPos, rect.x () + rect.width (), yPos); + } + + qreal firstX = ((int) (rect.x () / iCrossSize)) * iCrossSize; + + for (qreal xPos = firstX; xPos <= rect.x () + rect.width (); xPos += iCrossSize) + { + painter->drawLine (xPos, rect.y (), xPos, rect.y () + rect.height ()); + } + } + + painter->end(); + painter->save(); + + if (QString() != dstFilePath) + if (bckImage->save(dstFilePath)) + sRet = cfImages+"/"+sDstFileName; + + delete bckImage; + delete painter; + } + else + sRet = cfImages+"/"+sDstFileName; + + return sRet; +} + +bool UBCFFAdaptor::UBToCFFConverter::createPngFromSvg(QString &svgPath, QString &dstPath, QTransform transformation, QSize size) +{ + if (QFile().exists(svgPath)) + { + QImage i(svgPath); + + QSize iSize = (QSize() == size)?QSize(i.size().width()*transformation.m11(), i.size().height()*transformation.m22()):size; + + QImage image(iSize, QImage::Format_ARGB32_Premultiplied); + image.fill(0); + QPainter imagePainter(&image); + QSvgRenderer renderer(svgPath); + renderer.render(&imagePainter); + + return image.save(dstPath); + + } + else + return false; +} + + +bool UBCFFAdaptor::UBToCFFConverter::parseSVGGGroup(const QDomElement &element, QMultiMap &dstSvgList) +{ + qDebug() << "|parsing g section"; + QDomElement nextElement = element.firstChildElement(); + if (nextElement.isNull()) { + qDebug() << "Empty g element"; + errorStr = "EmptyGSection"; + return false; + } + + QMultiMap svgElements; + + QDomDocument doc; + QDomElement svgElementPart = doc.createElementNS(svgIWBNS,svgIWBNSPrefix + ":" + tIWBG); + QDomElement iwbElementPart = doc.createElementNS(iwbNS,iwbNsPrefix + ":" + tElement); + + // Elements can know about its layer, so it must add result QDomElements to ordrered list. + while (!nextElement.isNull()) { + QString tagName = nextElement.tagName(); + if (tagName == tUBZLine) parseUBZLine(nextElement, svgElements); + else if (tagName == tUBZPolygon) parseUBZPolygon(nextElement, svgElements); + else if (tagName == tUBZPolyline) parseUBZPolyline(nextElement, svgElements); + + nextElement = nextElement.nextSiblingElement(); + } + + QList layers; + QMapIterator nextSVGElement(svgElements); + while (nextSVGElement.hasNext()) + layers << nextSVGElement.next().key(); + + qSort(layers); + int layer = layers.at(0); + + nextSVGElement.toFront(); + while (nextSVGElement.hasNext()) + svgElementPart.appendChild(nextSVGElement.next().value()); + + addSVGElementToResultModel(svgElementPart, dstSvgList, layer); + + return true; +} +bool UBCFFAdaptor::UBToCFFConverter::parseUBZImage(const QDomElement &element, QMultiMap &dstSvgList) +{ + qDebug() << "|parsing image"; + + QDomDocument doc; + + QDomElement svgElementPart = doc.createElementNS(svgIWBNS,svgIWBNSPrefix + ":" + getElementTypeFromUBZ(element)); + QDomElement iwbElementPart = doc.createElementNS(iwbNS,iwbNsPrefix + ":" + tElement); + + if (setCommonAttributesFromUBZ(element, iwbElementPart, svgElementPart)) + { + addSVGElementToResultModel(svgElementPart, dstSvgList, getElementLayer(element)); + + if (0 < iwbElementPart.attributes().count()) + addIWBElementToResultModel(iwbElementPart); + return true; + } + else + { + qDebug() << "|error at image parsing"; + errorStr = "ImageParsingError"; + return false; + + } +} + +bool UBCFFAdaptor::UBToCFFConverter::parseUBZVideo(const QDomElement &element, QMultiMap &dstSvgList) +{ + qDebug() << "|parsing video"; + + QDomDocument doc; + + QDomElement svgElementPart = doc.createElementNS(svgIWBNS,svgIWBNSPrefix + ":" + getElementTypeFromUBZ(element)); + QDomElement iwbElementPart = doc.createElementNS(iwbNS,iwbNsPrefix + ":" + tElement); + + if (setCommonAttributesFromUBZ(element, iwbElementPart, svgElementPart)) + { + QDomElement svgSwitchSection = doc.createElementNS(svgIWBNS,svgIWBNSPrefix + ":" + tIWBSwitch); + svgSwitchSection.appendChild(svgElementPart); + + // if viewer cannot open that content - it must use that: + QDomElement svgText = doc.createElementNS(svgIWBNS,svgIWBNSPrefix + ":" + tIWBTextArea); + svgText.setAttribute(aX, svgElementPart.attribute(aX)); + svgText.setAttribute(aY, svgElementPart.attribute(aY)); + svgText.setAttribute(aWidth, svgElementPart.attribute(aWidth)); + svgText.setAttribute(aHeight, svgElementPart.attribute(aHeight)); + svgText.setAttribute(aTransform, svgElementPart.attribute(aTransform)); + + QDomText text = doc.createTextNode("Cannot Open Content"); + svgText.appendChild(text); + + svgSwitchSection.appendChild(svgText); + + addSVGElementToResultModel(svgSwitchSection, dstSvgList, getElementLayer(element)); + + if (0 < iwbElementPart.attributes().count()) + addIWBElementToResultModel(iwbElementPart); + return true; + } + else + { + qDebug() << "|error at video parsing"; + errorStr = "VideoParsingError"; + return false; + } +} + +bool UBCFFAdaptor::UBToCFFConverter::parseUBZAudio(const QDomElement &element, QMultiMap &dstSvgList) +{ + qDebug() << "|parsing audio"; + + // audio file must be linked to cff item excluding video. + // to do: + // 1 add image for audio element. + // 2 set id for this element + // 3 add section with xlink:href to audio file + // 4 add shild to a section with id of the image + + QDomDocument doc; + + QDomElement svgElementPart = doc.createElementNS(svgIWBNS,svgIWBNSPrefix + ":" + getElementTypeFromUBZ(element)); + QDomElement iwbElementPart = doc.createElementNS(iwbNS,iwbNsPrefix + ":" + tElement); + + if (setCommonAttributesFromUBZ(element, iwbElementPart, svgElementPart)) + { + //we must create image-containers for audio files + int audioImageDimention = qMin(svgElementPart.attribute(aWidth).toInt(), svgElementPart.attribute(aHeight).toInt()); + QString srcAudioImageFile(sAudioElementImage); + QString elementId = QString(QUuid::createUuid().toString()); + QString sDstAudioImageFileName = elementId+"."+fePng; + QString dstAudioImageFilePath = destinationPath+"/"+cfImages+"/"+sDstAudioImageFileName; + QString dstAudioImageRelativePath = cfImages+"/"+sDstAudioImageFileName; + + QFile srcFile(srcAudioImageFile); + + //creating folder for audioImage + QDir dstDocFolder(destinationPath); + bool bRes = true; + if (!dstDocFolder.exists(cfImages)) + bRes &= dstDocFolder.mkdir(cfImages); + + // CFF cannot show SVG images, so we need to convert it to png. + if (bRes && createPngFromSvg(srcAudioImageFile, dstAudioImageFilePath, getTransformFromUBZ(element), QSize(audioImageDimention, audioImageDimention))) + { + // switch section disabled because of imcompatibility with validator http://validator.imsglobal.org/iwb/index.jsp?validate=package + // QDomElement svgSwitchSection = doc.createElementNS(svgIWBNS,svgIWBNSPrefix + ":" + tIWBSwitch); + + // first we place content + QDomElement svgASection = doc.createElementNS(svgIWBNS,svgIWBNSPrefix + ":" + tIWBA); + svgASection.setAttribute(aSVGHref, svgElementPart.attribute(aSVGHref)); + + svgElementPart.setTagName(tIWBImage); + svgElementPart.setAttribute(aSVGHref, dstAudioImageRelativePath); + svgElementPart.setAttribute(aHeight, audioImageDimention); + svgElementPart.setAttribute(aWidth, audioImageDimention); + + svgASection.appendChild(svgElementPart); + // switch section disabled because of imcompatibility with validator http://validator.imsglobal.org/iwb/index.jsp?validate=package + // svgSwitchSection.appendChild(svgASection); + + // if viewer cannot open that content - it must use that: + QDomElement svgText = doc.createElementNS(svgIWBNS,svgIWBNSPrefix + ":" + tIWBTextArea); + svgText.setAttribute(aX, svgElementPart.attribute(aX)); + svgText.setAttribute(aY, svgElementPart.attribute(aY)); + svgText.setAttribute(aWidth, svgElementPart.attribute(aWidth)); + svgText.setAttribute(aHeight, svgElementPart.attribute(aHeight)); + svgText.setAttribute(aTransform, svgElementPart.attribute(aTransform)); + + QDomText text = doc.createTextNode("Cannot Open Content"); + svgText.appendChild(text); + + // switch section disabled because of imcompatibility with validator http://validator.imsglobal.org/iwb/index.jsp?validate=package + // svgSwitchSection.appendChild(svgText); + + // switch section disabled because of imcompatibility with validator http://validator.imsglobal.org/iwb/index.jsp?validate=package + addSVGElementToResultModel(svgASection/*svgSwitchSection*/, dstSvgList, getElementLayer(element)); + + if (0 < iwbElementPart.attributes().count()) + addIWBElementToResultModel(iwbElementPart); + return true; + } + return false; + } + else + { + qDebug() << "|error at audio parsing"; + errorStr = "AudioParsingError"; + return false; + } +} + +bool UBCFFAdaptor::UBToCFFConverter::parseForeignObject(const QDomElement &element, QMultiMap &dstSvgList) +{ + + if (element.attribute(aUBZType) == avUBZText) { + return parseUBZText(element, dstSvgList); + } + + qDebug() << "|parsing foreign object"; + + QDomDocument doc; + + QDomElement svgElementPart = doc.createElementNS(svgIWBNS,svgIWBNSPrefix + ":" + getElementTypeFromUBZ(element)); + QDomElement iwbElementPart = doc.createElementNS(iwbNS,iwbNsPrefix + ":" + tElement); + + if (setCommonAttributesFromUBZ(element, iwbElementPart, svgElementPart)) + { + addSVGElementToResultModel(svgElementPart, dstSvgList, getElementLayer(element)); + if (0 < iwbElementPart.attributes().count()) + addIWBElementToResultModel(iwbElementPart); + return true; + } + else + { + qDebug() << "|error at parsing foreign object"; + errorStr = "ForeignObjectParsingError"; + return false; + } +} + +bool UBCFFAdaptor::UBToCFFConverter::parseUBZText(const QDomElement &element, QMultiMap &dstSvgList) +{ + qDebug() << "|parsing text"; + + QDomDocument doc; + + QDomElement svgElementPart = doc.createElementNS(svgIWBNS,svgIWBNSPrefix + ":" + getElementTypeFromUBZ(element)); + QDomElement iwbElementPart = doc.createElementNS(iwbNS,iwbNsPrefix + ":" + tElement); + + if (element.hasChildNodes()) + { + QDomDocument htmlDoc; + htmlDoc.setContent(findTextNode(element).nodeValue()); + QDomNode bodyNode = findNodeByTagName(htmlDoc.firstChildElement(), "body"); + + setCFFTextFromHTMLTextNode(bodyNode.toElement(), svgElementPart); + + if (setCommonAttributesFromUBZ(element, iwbElementPart, svgElementPart)) + { + QString commonParams; + for (int i = 0; i < bodyNode.attributes().count(); i++) + { + commonParams += " " + bodyNode.attributes().item(i).nodeValue(); + } + commonParams.remove(" "); + commonParams.remove("'"); + + QStringList commonAttributes = commonParams.split(";", QString::SkipEmptyParts); + for (int i = 0; i < commonAttributes.count(); i++) + { + QStringList AttrVal = commonAttributes.at(i).split(":", QString::SkipEmptyParts); + if (1 < AttrVal.count()) + { + QString sAttr = ubzAttrNameToCFFAttrName(AttrVal.at(0)); + QString sVal = ubzAttrValueToCFFAttrName(AttrVal.at(1)); + + setCFFAttribute(sAttr, sVal, element, iwbElementPart, svgElementPart); + } + } + addSVGElementToResultModel(svgElementPart, dstSvgList, getElementLayer(element)); + if (0 < iwbElementPart.attributes().count()) + addIWBElementToResultModel(iwbElementPart); + return true; + } + return false; + } + else + { + qDebug() << "|error at text parsing"; + errorStr = "TextParsingError"; + return false; + } +} + +bool UBCFFAdaptor::UBToCFFConverter::parseUBZPolygon(const QDomElement &element, QMultiMap &dstSvgList) +{ + qDebug() << "||parsing polygon"; + + QDomDocument doc; + + QDomElement svgElementPart = doc.createElementNS(svgIWBNS,svgIWBNSPrefix + ":" + getElementTypeFromUBZ(element)); + QDomElement iwbElementPart = doc.createElementNS(iwbNS,iwbNsPrefix + ":" + tElement); + + if (setCommonAttributesFromUBZ(element, iwbElementPart, svgElementPart)) + { + svgElementPart.setAttribute(aStroke, svgElementPart.attribute(aFill)); + addSVGElementToResultModel(svgElementPart, dstSvgList, getElementLayer(element)); + + if (0 < iwbElementPart.attributes().count()) + { + QString id = svgElementPart.attribute(aUBZUuid); + if (id.isEmpty()) + id = QUuid::createUuid().toString(); + + svgElementPart.setAttribute(aID, id); + iwbElementPart.setAttribute(aRef, id); + + addIWBElementToResultModel(iwbElementPart); + } + return true; + } + else + { + qDebug() << "||error at parsing polygon"; + errorStr = "PolygonParsingError"; + return false; + } + +} + +bool UBCFFAdaptor::UBToCFFConverter::parseUBZPolyline(const QDomElement &element, QMultiMap &dstSvgList) +{ + qDebug() << "||parsing polyline"; + QDomElement resElement; + + QDomDocument doc; + + QDomElement svgElementPart = doc.createElementNS(svgIWBNS,svgIWBNSPrefix + ":" + getElementTypeFromUBZ(element)); + QDomElement iwbElementPart = doc.createElementNS(iwbNS,iwbNsPrefix + ":" + tElement); + + if (setCommonAttributesFromUBZ(element, iwbElementPart, svgElementPart)) + { + svgElementPart.setAttribute(aStroke, svgElementPart.attribute(aFill)); + addSVGElementToResultModel(svgElementPart, dstSvgList, getElementLayer(element)); + + if (0 < iwbElementPart.attributes().count()) + { + QString id = QUuid::createUuid().toString(); + svgElementPart.setAttribute(aID, id); + iwbElementPart.setAttribute(aRef, id); + + addIWBElementToResultModel(iwbElementPart); + } + return true; + } + else + { + qDebug() << "||error at parsing polygon"; + errorStr = "PolylineParsingError"; + return false; + } + +} + +bool UBCFFAdaptor::UBToCFFConverter::parseUBZLine(const QDomElement &element, QMultiMap &dstSvgList) +{ + qDebug() << "||parsing line"; + QDomElement resElement; + QDomDocument doc; + + QDomElement svgElementPart = doc.createElementNS(svgIWBNS,svgIWBNSPrefix + ":" + getElementTypeFromUBZ(element)); + QDomElement iwbElementPart = doc.createElementNS(iwbNS,iwbNsPrefix + ":" + tElement); + + if (setCommonAttributesFromUBZ(element, iwbElementPart, svgElementPart)) + { + svgElementPart.setAttribute(aStroke, svgElementPart.attribute(aFill)); + addSVGElementToResultModel(svgElementPart, dstSvgList, getElementLayer(element)); + + if (0 < iwbElementPart.attributes().count()) + { + QString id = QUuid::createUuid().toString(); + svgElementPart.setAttribute(aID, id); + iwbElementPart.setAttribute(aRef, id); + + addIWBElementToResultModel(iwbElementPart); + } + } + else + { + qDebug() << "||error at parsing polygon"; + errorStr = "LineParsingError"; + return false; + } + return true; +} + +void UBCFFAdaptor::UBToCFFConverter::addSVGElementToResultModel(const QDomElement &element, QMultiMap &dstList, int layer) +{ + int elementLayer = (DEFAULT_LAYER == layer) ? DEFAULT_LAYER : layer; + + QDomElement rootElement = element.cloneNode(true).toElement(); + mDocumentToWrite->firstChildElement().appendChild(rootElement); + dstList.insert(elementLayer, rootElement); +} + +void UBCFFAdaptor::UBToCFFConverter::addIWBElementToResultModel(const QDomElement &element) +{ + QDomElement rootElement = element.cloneNode(true).toElement(); + mDocumentToWrite->firstChildElement().appendChild(rootElement); + mExtendedElements.append(rootElement); +} + +UBCFFAdaptor::UBToCFFConverter::~UBToCFFConverter() +{ + if (mDataModel) + delete mDataModel; + if (mIWBContentWriter) + delete mIWBContentWriter; + if (mDocumentToWrite) + delete mDocumentToWrite; +} +bool UBCFFAdaptor::UBToCFFConverter::isValid() const +{ + bool result = QFileInfo(sourcePath).exists() + && QFileInfo(sourcePath).isDir() + && errorStr == noErrorMsg; + + if (!result) { + qDebug() << "specified data is not valid"; + errorStr = "ValidateDataError"; + } + + return result; +} + +void UBCFFAdaptor::UBToCFFConverter::fillNamespaces() +{ + mIWBContentWriter->writeDefaultNamespace(svgUBZNS); + mIWBContentWriter->writeNamespace(iwbNS, iwbNsPrefix); + mIWBContentWriter->writeNamespace(svgIWBNS, svgIWBNSPrefix); + mIWBContentWriter->writeNamespace(xlinkNS, xlinkNSPrefix); +} + +QString UBCFFAdaptor::UBToCFFConverter::digitFileFormat(int digit) const +{ + return QString("%1").arg(digit, 3, 10, QLatin1Char('0')); +} +QString UBCFFAdaptor::UBToCFFConverter::contentIWBFileName() const +{ + return destinationPath + "/" + fIWBContent; +} + +//setting SVG dimenitons +QSize UBCFFAdaptor::UBToCFFConverter::getSVGDimentions(const QString &element) +{ + + QStringList dimList; + + dimList = element.split(dimensionsDelimiter1, QString::KeepEmptyParts); + if (dimList.count() != 2) // row unlike 0x0 + return QSize(); + + bool ok; + + int width = dimList.takeFirst().toInt(&ok); + if (!ok || !width) + return QSize(); + + int height = dimList.takeFirst().toInt(&ok); + if (!ok || !height) + return QSize(); + + return QSize(width, height); +} + +//Setting viewbox rectangle +QRect UBCFFAdaptor::UBToCFFConverter::getViewboxRect(const QString &element) const +{ + QStringList dimList; + + dimList = element.split(dimensionsDelimiter2, QString::KeepEmptyParts); + if (dimList.count() != 4) // row unlike 0 0 0 0 + return QRect(); + + bool ok = false; + + int x = dimList.takeFirst().toInt(&ok); + if (!ok || !x) + return QRect(); + + int y = dimList.takeFirst().toInt(&ok); + if (!ok || !y) + return QRect(); + + int width = dimList.takeFirst().toInt(&ok); + if (!ok || !width) + return QRect(); + + int height = dimList.takeFirst().toInt(&ok); + if (!ok || !height) + return QRect(); + + return QRect(x, y, width, height); +} + +QString UBCFFAdaptor::UBToCFFConverter::rectToIWBAttr(const QRect &rect) const +{ + if (rect.isNull()) return QString(); + + return QString("%1 %2 %3 %4").arg(rect.topLeft().x()) + .arg(rect.topLeft().y()) + .arg(rect.width()) + .arg(rect.height()); +} + +UBCFFAdaptor::UBToUBZConverter::UBToUBZConverter() +{ + +} diff --git a/plugins/cffadaptor/src/UBCFFAdaptor.h b/plugins/cffadaptor/src/UBCFFAdaptor.h new file mode 100644 index 00000000..99f57396 --- /dev/null +++ b/plugins/cffadaptor/src/UBCFFAdaptor.h @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA) + * + * This file is part of Open-Sankoré. + * + * Open-Sankoré is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License, + * with a specific linking exception for the OpenSSL project's + * "OpenSSL" library (or with modified versions of it that use the + * same license as the "OpenSSL" library). + * + * Open-Sankoré 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Open-Sankoré. If not, see . + */ + + +#ifndef UBCFFADAPTOR_H +#define UBCFFADAPTOR_H + +#include "UBCFFAdaptor_global.h" + +#include + +class QTransform; +class QDomDocument; +class QDomElement; +class QDomNode; +class QuaZipFile; + +class UBCFFADAPTORSHARED_EXPORT UBCFFAdaptor { + class UBToCFFConverter; + +public: + UBCFFAdaptor(); + ~UBCFFAdaptor(); + + bool convertUBZToIWB(const QString &from, const QString &to); + bool deleteDir(const QString& pDirPath) const; + QList getConversionMessages(); + +private: + QString uncompressZip(const QString &zipFile); + bool compressZip(const QString &source, const QString &destination); + bool compressDir(const QString &dirName, const QString &parentDir, QuaZipFile *outZip); + bool compressFile(const QString &fileName, const QString &parentDir, QuaZipFile *outZip); + + QString createNewTmpDir(); + bool freeDir(const QString &dir); + void freeTmpDirs(); + +private: + QStringList tmpDirs; + QList mConversionMessages; + +private: + + class UBToCFFConverter { + + static const int DEFAULT_LAYER = -100000; + + public: + UBToCFFConverter(const QString &source, const QString &destination); + ~UBToCFFConverter(); + bool isValid() const; + QString lastErrStr() const {return errorStr;} + bool parse(); + QList getMessages() {return mExportErrorList;} + + private: + + void addLastExportError(QString error) {mExportErrorList.append(error);} + + void fillNamespaces(); + + bool parseMetadata(); + bool parseContent(); + QDomElement parsePageset(const QStringList &pageFileNames); + QDomElement parsePage(const QString &pageFileName); + QDomElement parseSvgPageSection(const QDomElement &element); + void writeQDomElementToXML(const QDomNode &node); + bool writeExtendedIwbSection(); + QDomElement parseGroupsPageSection(const QDomElement &groupRoot); + + bool createBackground(const QDomElement &element, QMultiMap &dstSvgList); + QString createBackgroundImage(const QDomElement &element, QSize size); + bool createPngFromSvg(QString &svgPath, QString &dstPath, QTransform transformation, QSize size = QSize()); + + bool parseSVGGGroup(const QDomElement &element, QMultiMap &dstSvgList); + bool parseUBZImage(const QDomElement &element, QMultiMap &dstSvgList); + bool parseUBZVideo(const QDomElement &element, QMultiMap &dstSvgList); + bool parseUBZAudio(const QDomElement &element, QMultiMap &dstSvgList); + bool parseForeignObject(const QDomElement &element, QMultiMap &dstSvgList); + bool parseUBZText(const QDomElement &element, QMultiMap &dstSvgList); + + bool parseUBZPolygon(const QDomElement &element, QMultiMap &dstSvgList); + bool parseUBZPolyline(const QDomElement &element, QMultiMap &dstSvgList); + bool parseUBZLine(const QDomElement &element, QMultiMap &dstSvgList); + void addSVGElementToResultModel(const QDomElement &element, QMultiMap &dstList, int layer = DEFAULT_LAYER); + void addIWBElementToResultModel(const QDomElement &element); + + qreal getAngleFromTransform(const QTransform &tr); + QString getDstContentFolderName(const QString &elementType); + QString getSrcContentFolderName(QString href); + QString getFileNameFromPath(QString sPath); + QString getExtentionFromFileName(const QString &filename); + QString convertExtention(const QString &ext); + QString getElementTypeFromUBZ(const QDomElement &element); + + int getElementLayer(const QDomElement &element); + + bool itIsSupportedFormat(const QString &format) const; + bool itIsFormatToConvert(const QString &format) const; + bool itIsSVGElementAttribute(const QString ItemType, const QString &AttrName); + bool itIsIWBAttribute(const QString &attribute) const; + bool itIsUBZAttributeToConvert(const QString &attribute) const; + + bool ibwAddLine(int x1, int y1, int x2, int y2, QString color=QString(), int width=1, bool isBackground=false); + + QTransform getTransformFromUBZ(const QDomElement &ubzElement); + void setGeometryFromUBZ(const QDomElement &ubzElement, QDomElement &iwbElement); + void setCoordinatesFromUBZ(const QDomElement &ubzElement, QDomElement &iwbElement); + bool setContentFromUBZ(const QDomElement &ubzElement, QDomElement &svgElement); + void setCFFTextFromUBZ(const QDomElement &ubzElement, QDomElement &iwbElement, QDomElement &svgElement); + void setCFFTextFromHTMLTextNode(const QDomElement htmlTextNode, QDomElement &iwbElement); + QString ubzAttrNameToCFFAttrName(QString cffAttrName); + QString ubzAttrValueToCFFAttrName(QString cffAttrValue); + + bool setCFFAttribute(const QString &attributeName, const QString &attributeValue, const QDomElement &ubzElement, QDomElement &iwbElement, QDomElement &svgElement); + bool setCommonAttributesFromUBZ(const QDomElement &ubzElement, QDomElement &iwbElement, QDomElement &svgElement); + void setViewBox(QRect viewbox); + + QDomNode findTextNode(const QDomNode &node); + QDomNode findNodeByTagName(const QDomNode &node, QString tagName); + + QSize getSVGDimentions(const QString &element); + + inline QRect getViewboxRect(const QString &element) const; + inline QString rectToIWBAttr(const QRect &rect) const; + inline QString digitFileFormat(int num) const; + inline bool strToBool(const QString &in) const {return in == "true";} + QString contentIWBFileName() const; + + private: + QList mExportErrorList; + QMap iwbSVGItemsAttributes; + QDomDocument *mDataModel; //model for reading indata + QXmlStreamWriter *mIWBContentWriter; //stream to write outdata + QSize mSVGSize; //svg page size + QRect mViewbox; //Main viewbox parameter for CFF + QString sourcePath; // dir with unpacked source data (ubz) + QString destinationPath; //dir with unpacked destination data (iwb) + QDomDocument *mDocumentToWrite; //document for saved QDomElements from mSvgElements and mExtendedElements + QMultiMap mSvgElements; //Saving svg elements to have a sorted by z order list of elements to write; + QList mExtendedElements; //Saving extended options of elements to be able to add them to the end of result iwb document; + mutable QString errorStr; // last error string message + + public: + operator bool() const {return isValid();} + }; + + class UBToUBZConverter { + public: + UBToUBZConverter(); + }; + + +}; + +#endif // UBCFFADAPTOR_H diff --git a/plugins/cffadaptor/src/UBCFFAdaptor_global.h b/plugins/cffadaptor/src/UBCFFAdaptor_global.h new file mode 100644 index 00000000..3f060b96 --- /dev/null +++ b/plugins/cffadaptor/src/UBCFFAdaptor_global.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA) + * + * This file is part of Open-Sankoré. + * + * Open-Sankoré is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License, + * with a specific linking exception for the OpenSSL project's + * "OpenSSL" library (or with modified versions of it that use the + * same license as the "OpenSSL" library). + * + * Open-Sankoré 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Open-Sankoré. If not, see . + */ + + +#ifndef UBCFFADAPTOR_GLOBAL_H +#define UBCFFADAPTOR_GLOBAL_H + +#include + +#if defined(UBCFFADAPTOR_LIBRARY) +# define UBCFFADAPTORSHARED_EXPORT Q_DECL_EXPORT +#else +# define UBCFFADAPTORSHARED_EXPORT Q_DECL_IMPORT +#endif + +#endif // UBCFFADAPTOR_GLOBAL_H diff --git a/plugins/cffadaptor/src/UBCFFConstants.h b/plugins/cffadaptor/src/UBCFFConstants.h new file mode 100644 index 00000000..588ea699 --- /dev/null +++ b/plugins/cffadaptor/src/UBCFFConstants.h @@ -0,0 +1,405 @@ +/* + * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA) + * + * This file is part of Open-Sankoré. + * + * Open-Sankoré is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License, + * with a specific linking exception for the OpenSSL project's + * "OpenSSL" library (or with modified versions of it that use the + * same license as the "OpenSSL" library). + * + * Open-Sankoré 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Open-Sankoré. If not, see . + */ + + +#ifndef UBCFFCONSTANTS_H +#define UBCFFCONSTANTS_H + +#define PI 3.1415926535 + +const int DEFAULT_BACKGROUND_LAYER = -20000002; +const int DEFAULT_BACKGROUND_CROSS_LAYER = -20000001; + +// Constant names. Use only them instead const char* in each function + +// Constant fileNames; +const QString fMetadata = "metadata.rdf"; +const QString fIWBContent = "content.xml"; +const QString fIWBBackground = "background.png"; +const QString sAudioElementImage = ":images/soundOn.svg"; + +// Constant messages; +const QString noErrorMsg = "NoError"; + +// Tag names +const QString tDescription = "Description"; +const QString tIWBRoot = "iwb"; +const QString tIWBMeta = "meta"; +const QString tUBZSize = "size"; +const QString tSvg = "svg"; +const QString tIWBPage = "page"; +const QString tIWBPageSet = "pageset"; +const QString tId = "id"; +const QString tElement = "element"; +const QString tUBZGroup = "group"; +const QString tUBZGroups = "groups"; +const QString tUBZG = "g"; +const QString tUBZPolygon = "polygon"; +const QString tUBZPolyline = "polyline"; +const QString tUBZLine = "line"; +const QString tUBZAudio = "audio"; +const QString tUBZVideo = "video"; +const QString tUBZImage = "image"; +const QString tUBZForeignObject = "foreignObject"; +const QString tUBZTextContent = "itemTextContent"; + +const QString tIWBA = "a"; +const QString tIWBG = "g"; +const QString tIWBSwitch = "switch"; +const QString tIWBImage = "image"; +const QString tIWBVideo = "video"; +const QString tIWBAudio = "audio"; +const QString tIWBText = "text"; +const QString tIWBTextArea = "textarea"; +const QString tIWBPolyLine = "polyline"; +const QString tIWBPolygon = "polygon"; +const QString tIWBFlash = "video"; +const QString tIWBRect = "rect"; +const QString tIWBLine = "line"; +const QString tIWBTbreak = "tbreak"; +const QString tIWBTspan = "tspan"; + +// Attributes names +const QString aIWBVersion = "version"; +const QString aOwner = "owner"; +const QString aDescription = "description"; +const QString aCreator = "creator"; +const QString aAbout = "about"; +const QString aIWBViewBox = "viewbox"; +const QString aUBZViewBox = "viewBox"; +const QString aDarkBackground = "dark-background"; +const QString aBackground = "background"; +const QString aCrossedBackground = "crossed-background"; +const QString aUBZType = "type"; +const QString aUBZUuid = "uuid"; +const QString aUBZParent = "parent"; +const QString aFill = "fill"; // IWB attribute contans color to fill + +const QString aID = "id"; // ID of any svg element can be placed in to iwb section +const QString aRef = "ref"; // as reference for applying additional attributes +const QString aSVGHref = "xlink:href"; // reference to file +const QString aIWBHref = "ref"; // reference to element ID +const QString aUBZHref = "href"; +const QString aUBZSource = "source"; +const QString aSrc = "src"; +const QString aSVGRequiredExtension = "requiredExtensions"; + +const QString aX = "x"; +const QString aY = "y"; +const QString aWidth = "width"; +const QString aHeight = "height"; +const QString aStroke = "stroke"; +const QString aStrokeWidth = "stroke-width"; +const QString aPoints = "points"; +const QString aZLayer = "z-value"; +const QString aLayer = "layer"; +const QString aTransform = "transform"; +const QString aLocked = "locked"; +const QString aIWBName = "name"; +const QString aIWBContent = "content"; + + +// Attribute values +const QString avIWBVersionNo = "1.0"; +const QString avUBZText = "text"; +const QString avFalse = "false"; +const QString avTrue = "true"; + +// Namespaces and prefixes +const QString svgRequiredExtensionPrefix = "http://www.imsglobal.org/iwb/"; +const QString dcNS = "http://purl.org/dc/elements/1.1/"; +const QString ubNS = "http://uniboard.mnemis.com/document"; +const QString svgUBZNS = "http://www.imsglobal.org/xsd/iwb_v1p0"; +const QString svgIWBNS = "http://www.w3.org/2000/svg"; +const QString xlinkNS = "http://www.w3.org/1999/xlink"; +const QString iwbNS = "http://www.imsglobal.org/xsd/iwb_v1p0"; +const QString xsiNS = "http://www.w3.org/2001/XMLSchema-instance"; +const QString xsiShemaLocation = "\ +http://www.imsglobal.org/xsd/iwb_v1p0 \ +http://www.imsglobal.org/profile/iwb/iwbv1p0_v1p0.xsd \ +http://www.w3.org/2000/svg http://www.imsglobal.org/profile/iwb/svgsubsetv1p0_v1p0.xsd \ +http://www.w3.org/1999/xlink http://www.imsglobal.org/xsd/w3/1999/xlink.xsd"; +const QString dcNSPrefix = "dc"; +const QString ubNSPrefix = "ub"; +const QString svgIWBNSPrefix = "svg"; +const QString xlinkNSPrefix = "xlink"; +const QString iwbNsPrefix = "iwb"; +const QString xsiPrefix = "xsi"; +const QString xsiSchemaLocationPrefix = "schemaLocation"; + +const QString avOwner = ""; +const QString avCreator = ""; +const QString avDescription = ""; + +//constant symbols and words etc +const QString dimensionsDelimiter1 = "x"; +const QString dimensionsDelimiter2 = " "; +const QString pageAlias = "page"; +const QString pageFileExtentionUBZ = "svg"; + +//content folder names +const QString cfImages = "images"; +const QString cfVideos = "video"; +const QString cfAudios = "audio"; +const QString cfFlash = "flash"; + +//known file extentions +const QString feSvg = "svg"; +const QString feWgt = "wgt"; +const QString fePng = "png"; + +const int iCrossSize = 32; +const int iCrossWidth = 1; + +// Image formats supported by CFF exclude wgt. Wgt is Sankore widget, which is considered as a .png preview. +const QString iwbElementImage(" \ +wgt, \ +jpeg, \ +jpg, \ +bmp, \ +gif, \ +wmf, \ +emf, \ +png, \ +tif, \ +tiff \ +"); + +// Video formats supported by CFF +const QString iwbElementVideo(" \ +mpg, \ +mpeg, \ +swf, \ +"); + +// Audio formats supported by CFF +const QString iwbElementAudio(" \ +mp3, \ +wav \ +"); + +const QString cffSupportedFileFormats(iwbElementImage + iwbElementVideo + iwbElementAudio); +const QString ubzFormatsToConvert("svg"); + + +const QString iwbSVGImageAttributes(" \ +id, \ +xlink:href, \ +x, \ +y, \ +height, \ +width, \ +fill-opacity, \ +requiredExtentions, \ +transform \ +"); + + +const QString iwbSVGAudioAttributes(" \ +id, \ +xlink:href, \ +x, \ +y, \ +height, \ +width, \ +fill-opacity, \ +requiredExtentions, \ +transform \ +"); + +const QString iwbSVGVideoAttributes(" \ +id, \ +xlink:href, \ +x, \ +y, \ +height, \ +width, \ +fill-opacity, \ +requiredExtentions, \ +transform \ +"); + +const QString iwbSVGRectAttributes(" \ +id, \ +x, \ +y, \ +height, \ +width, \ +fill, \ +fill-opacity, \ +stroke, \ +stroke-dasharray, \ +stroke-linecap, \ +stroke-linejoin, \ +stroke-opacity, \ +stroke-width, \ +transform \ +"); + + + +const QString iwbSVGTextAttributes(" \ +id, \ +x, \ +y, \ +fill, \ +font-family, \ +font-size, \ +font-style, \ +font-weight, \ +font-stretch, \ +transform \ +"); + +const QString iwbSVGTextAreaAttributes(" \ +id, \ +x, \ +y, \ +height, \ +width, \ +fill, \ +font-family, \ +font-size, \ +font-style, \ +font-weight, \ +font-stretch, \ +text-align, \ +transform \ +"); + +const QString iwbSVGTspanAttributes(" \ +id, \ +fill, \ +font-family, \ +font-size, \ +font-style, \ +font-weight, \ +font-stretch, \ +text-align, \ +"); + +const QString iwbSVGLineAttributes(" \ +id, \ +x1, \ +y1, \ +x2, \ +y2, \ +stroke, \ +stroke-dasharray, \ +stroke-width, \ +stroke-opacity, \ +stroke-linecap, \ +transform \ +"); + +const QString iwbSVGPolyLineAttributes(" \ +id, \ +points, \ +stroke, \ +stroke-width, \ +stroke-dasharray, \ +stroke-opacity, \ +stroke-linecap, \ +transform \ +"); + +const QString iwbSVGPolygonAttributes(" \ +id, \ +points, \ +fill, \ +fill-opacity, \ +stroke, \ +stroke-dasharray, \ +stroke-width, \ +stroke-linecap, \ +stroke-linejoin, \ +stroke-opacity, \ +stroke-width, \ +transform \ +"); + +// 1 to 1 copy to SVG section +const QString iwbElementAttributes(" \ +background, \ +background-fill, \ +background-posture, \ +flip, \ +freehand, \ +highlight, \ +highlight-fill, \ +list-style-type, \ +list-style-type-fill, \ +locked, \ +replicate, \ +revealer, \ +stroke-lineshape-start, \ +stroke-lineshape-end \ +"); + +// cannot be copied 1 to 1 to SVG section +const QString ubzElementAttributesToConvert(" \ +xlink:href, \ +src, \ +transform, \ +uuid \ +" +); + +// additional attributes. Have references in SVG section. +const QString svgElementAttributes(" \ +points, \ +fill, \ +fill-opacity, \ +stroke, \ +stroke-dasharray, \ +stroke-linecap, \ +stroke-opacity, \ +stroke-width, \ +stroke_linejoin, \ +requiredExtensions, \ +viewbox, \ +x, \ +y, \ +x1, \ +y1, \ +x2, \ +y2, \ +height, \ +width, \ +font-family, \ +font-size, \ +font-style, \ +font-weight, \ +font-stretch, \ +text-align \ +"); + +const QString ubzContentFolders("audios,videos,images,widgets"); + +struct UBItemLayerType +{ + enum Enum + { + FixedBackground = -2000, Object = -1000, Graphic = 0, Tool = 1000, Control = 2000 + }; +}; + +#endif // UBCFFCONSTANTS_H diff --git a/plugins/cffadaptor/src/UBGlobals.h b/plugins/cffadaptor/src/UBGlobals.h new file mode 100644 index 00000000..1c2864d3 --- /dev/null +++ b/plugins/cffadaptor/src/UBGlobals.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA) + * + * This file is part of Open-Sankoré. + * + * Open-Sankoré is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License, + * with a specific linking exception for the OpenSSL project's + * "OpenSSL" library (or with modified versions of it that use the + * same license as the "OpenSSL" library). + * + * Open-Sankoré 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Open-Sankoré. If not, see . + */ + + +#ifndef UBGLOBALS_H +#define UBGLOBALS_H + +#define DELETEPTR(ptr) if(NULL != ptr){ \ + delete ptr; \ + ptr = NULL; \ + } + +#ifdef Q_WS_WIN + +#define WARNINGS_DISABLE __pragma(warning(push, 0)); +#define WARNINGS_ENABLE __pragma(warning(pop)); + +#ifdef NO_THIRD_PARTY_WARNINGS +// disabling warning level to 0 and save old state +#define THIRD_PARTY_WARNINGS_DISABLE WARNINGS_DISABLE +#else +// just save old state (needs for not empty define) +#define THIRD_PARTY_WARNINGS_DISABLE __pragma(warning(push)); +#endif //#ifdef NO_THIRD_PARTY_WARNINGS +// anyway on WIN +#define THIRD_PARTY_WARNINGS_ENABLE WARNINGS_ENABLE + +#else //#ifdef Q_WS_WIN + +#define WARNINGS_DISABLE _Pragma("GCC diagnostic push"); \ +_Pragma("GCC diagnostic ignored \"-Wunused-parameter\""); \ +_Pragma("GCC diagnostic ignored \"-Wunused-variable\""); \ +_Pragma("GCC diagnostic ignored \"-Wsign-compare\""); + +#define WARNINGS_ENABLE _Pragma("GCC diagnostic pop"); + +#ifdef NO_THIRD_PARTY_WARNINGS +//disabling some warnings +#define THIRD_PARTY_WARNINGS_DISABLE WARNINGS_DISABLE + +#define THIRD_PARTY_WARNINGS_ENABLE WARNINGS_ENABLE +#else +// just save old state (needs for not empty define) +#define THIRD_PARTY_WARNINGS_ENABLE WARNINGS_ENABLE + +#endif //#ifdef NO_THIRD_PARTY_WARNINGS + +#endif //#ifdef Q_WS_WIN + +#endif // UBGLOBALS_H + diff --git a/plugins/plugins.pri b/plugins/plugins.pri new file mode 100644 index 00000000..ddf3113a --- /dev/null +++ b/plugins/plugins.pri @@ -0,0 +1,6 @@ +HEADERS += plugins/cffadaptor/src/UBCFFAdaptor_global.h \ + plugins/cffadaptor/src/UBCFFAdaptor.h \ + plugins/cffadaptor/src/UBCFFConstants.h \ + plugins/cffadaptor/src/UBGlobals.h + +SOURCES += plugins/cffadaptor/src/UBCFFAdaptor.cpp diff --git a/resources/OpenBoard.qrc b/resources/OpenBoard.qrc index 18c33750..d67ff3d5 100644 --- a/resources/OpenBoard.qrc +++ b/resources/OpenBoard.qrc @@ -360,5 +360,7 @@ images/ungroupItems.svg images/setAsBackground.svg images/backgroundPalette/resetDefaultGridSize.svg + images/collapse-all.png + images/expand-all.png diff --git a/resources/forms/documents.ui b/resources/forms/documents.ui index af48a648..90dc4476 100644 --- a/resources/forms/documents.ui +++ b/resources/forms/documents.ui @@ -28,50 +28,142 @@ - - - - - QFrame::NoFrame - - - QFrame::Raised - - - 0 - + + + - + + + Qt::Horizontal + + + true + + + 6 + + 0 0 - - QFrame::Panel - - - QFrame::Plain - - - 0 - - - 0 - - - - - 0 - 0 - + + + + + + 0 + 0 + + + + Collapse All + + + + + + + :/images/collapse-all.png:/images/collapse-all.png + + + true + + + + + + + + 0 + 0 + + + + Expand All + + + + + + + :/images/expand-all.png:/images/expand-all.png + + + true + + + + + + + + 4 + 0 + + + + + Select a sort + + + + + Creation date + + + + + Update date + + + + + Alphabetical order + + + + + + + + + 2 + 0 + + + + + Ascending order + + + + + Descending order + + + + + + + + + + + 400 + 0 + - - + + + 16777215 + 16777215 + QFrame::NoFrame @@ -79,14 +171,23 @@ QFrame::Plain - - 1 + + true + + + 65 - - QAbstractItemView::ScrollPerPixel + + QAbstractItemView::ExtendedSelection - - 2 + + QAbstractItemView::SelectRows + + + QAbstractItemView::ScrollPerItem + + + true true @@ -94,25 +195,15 @@ false - - - 1 - - - - - + - - 0 - @@ -147,7 +238,16 @@ 0 - + + 0 + + + 0 + + + 0 + + 0 @@ -224,6 +324,7 @@ + @@ -238,9 +339,9 @@

gui/UBDocumentThumbnailWidget.h
- UBDocumentTreeWidget - QTreeWidget -
gui/UBDocumentTreeWidget.h
+ UBDocumentTreeView + QTreeView +
document/UBDocumentController.h
diff --git a/resources/forms/webPublishing.ui b/resources/forms/webPublishing.ui new file mode 100644 index 00000000..d71282e5 --- /dev/null +++ b/resources/forms/webPublishing.ui @@ -0,0 +1,132 @@ + + + documentPublishingDialog + + + Qt::WindowModal + + + + 0 + 0 + 607 + 405 + + + + Dialog + + + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + Title + + + + + + + 60 + + + + + + + E-mail + + + + + + + + + + Author + + + + + + + + + + Description + + + + + + + + 0 + 0 + + + + + 0 + 168 + + + + + + + + Qt::Vertical + + + + 20 + 10 + + + + + + + + Attach Downloadable PDF Version + + + + + + + Attach Downloadable Uniboard File (UBZ) + + + + + + + Warning: This documents contains video, which will not be displayed properly on the Web + + + true + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + diff --git a/resources/images/collapse-all.png b/resources/images/collapse-all.png new file mode 100755 index 00000000..9d4df9c9 Binary files /dev/null and b/resources/images/collapse-all.png differ diff --git a/resources/images/expand-all.png b/resources/images/expand-all.png new file mode 100755 index 00000000..b4045892 Binary files /dev/null and b/resources/images/expand-all.png differ diff --git a/src/adaptors/UBCFFSubsetAdaptor.cpp b/src/adaptors/UBCFFSubsetAdaptor.cpp new file mode 100644 index 00000000..474e4cc7 --- /dev/null +++ b/src/adaptors/UBCFFSubsetAdaptor.cpp @@ -0,0 +1,1549 @@ +/* + * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA) + * + * This file is part of Open-Sankoré. + * + * Open-Sankoré is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License, + * with a specific linking exception for the OpenSSL project's + * "OpenSSL" library (or with modified versions of it that use the + * same license as the "OpenSSL" library). + * + * Open-Sankoré 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Open-Sankoré. If not, see . + */ + + + +#include +#include +#include +#include +#include + +#include "core/UBPersistenceManager.h" + +#include "document/UBDocumentProxy.h" + +#include "domain/UBItem.h" +#include "domain/UBGraphicsPolygonItem.h" +#include "domain/UBGraphicsStroke.h" +#include "domain/UBGraphicsTextItem.h" +#include "domain/UBGraphicsSvgItem.h" +#include "domain/UBGraphicsPixmapItem.h" +#include "domain/UBGraphicsMediaItem.h" +#include "domain/UBGraphicsWidgetItem.h" +#include "domain/UBGraphicsTextItem.h" +#include "domain/UBGraphicsTextItemDelegate.h" +#include "domain/UBGraphicsWidgetItem.h" +#include "domain/UBGraphicsGroupContainerItem.h" + +#include "frameworks/UBFileSystemUtils.h" + +#include "UBCFFSubsetAdaptor.h" +#include "UBMetadataDcSubsetAdaptor.h" +#include "UBThumbnailAdaptor.h" +#include "UBSvgSubsetAdaptor.h" + +#include "core/UBApplication.h" +#include "QFile" + +#include "core/memcheck.h" +//#include "qtlogger.h" + +//tag names definition. Use them everiwhere! +static QString tElement = "element"; +static QString tGroup = "group"; +static QString tEllipse = "ellipse"; +static QString tIwb = "iwb"; +static QString tMeta = "meta"; +static QString tPage = "page"; +static QString tPageset = "pageset"; +static QString tG = "g"; +static QString tSwitch = "switch"; +static QString tPolygon = "polygon"; +static QString tPolyline = "polyline"; +static QString tRect = "rect"; +static QString tSvg = "svg"; +static QString tText = "text"; +static QString tTextarea = "textarea"; +static QString tTspan = "tspan"; +static QString tBreak = "tbreak"; +static QString tImage = "image"; +static QString tFlash = "flash"; +static QString tAudio = "a"; +static QString tVideo = "video"; + +//attribute names definition +static QString aFill = "fill"; +static QString aFillopacity = "fill-opacity"; +static QString aX = "x"; +static QString aY = "y"; +static QString aWidth = "width"; +static QString aHeight = "height"; +static QString aStroke = "stroke"; +static QString aStrokewidth = "stroke-width"; +static QString aCx = "cx"; +static QString aCy = "cy"; +static QString aRx = "rx"; +static QString aRy = "ry"; +static QString aTransform = "transform"; +static QString aViewbox = "viewbox"; +static QString aFontSize = "font-size"; +static QString aFontfamily = "font-family"; +static QString aFontstretch = "font-stretch"; +static QString aFontstyle = "font-style"; +static QString aFontweight = "font-weight"; +static QString aTextalign = "text-align"; +static QString aPoints = "points"; +static QString svgNS = "http://www.w3.org/2000/svg"; +static QString iwbNS = "http://www.imsglobal.org/xsd/iwb_v1p0"; +static QString aId = "id"; +static QString aRef = "ref"; +static QString aHref = "href"; +static QString aBackground = "background"; +static QString aLocked = "locked"; +static QString aEditable = "editable"; + +//attributes part names +static QString apRotate = "rotate"; +static QString apTranslate = "translate"; + + +UBCFFSubsetAdaptor::UBCFFSubsetAdaptor() +{} + +bool UBCFFSubsetAdaptor::ConvertCFFFileToUbz(QString &cffSourceFile, UBDocumentProxy* pDocument) +{ + //TODO + // fill document proxy metadata + // create persistance manager to save data using proxy + // create UBCFFSubsetReader and make it parse cffSourceFolder + QFile file(cffSourceFile); + + if (!file.open(QIODevice::ReadOnly)) + { + qWarning() << "Cannot open file " << cffSourceFile << " for reading ..."; + return false; + } + + UBCFFSubsetReader cffReader(pDocument, &file); + bool result = cffReader.parse(); + file.close(); + + return result; +} +UBCFFSubsetAdaptor::UBCFFSubsetReader::UBCFFSubsetReader(UBDocumentProxy *proxy, QFile *content) + : mProxy(proxy) + , mGSectionContainer(NULL) +{ + int errorLine, errorColumn; + QString errorStr; + if(!mDOMdoc.setContent(content, true, &errorStr, &errorLine, &errorColumn)){ + qWarning() << "Error:Parseerroratline" << errorLine << "," + << "column" << errorColumn << ":" << errorStr; + } else { + qDebug() << "well parsed to DOM"; + pwdContent = QFileInfo(content->fileName()).dir().absolutePath(); + } + qDebug() << "tmp path is" << pwdContent; +} +bool UBCFFSubsetAdaptor::UBCFFSubsetReader::parse() +{ + UBMetadataDcSubsetAdaptor::persist(mProxy); + + mIndent = ""; + if (!getTempFileName() || !createTempFlashPath()) + return false; + + if (mDOMdoc.isNull()) + return false; + + bool result = parseDoc(); + if (result) + result = mProxy->pageCount() != 0; + + if (QFile::exists(mTempFilePath)) + QFile::remove(mTempFilePath); + +// if (mTmpFlashDir.exists()) +// UBFileSystemUtils::deleteDir(mTmpFlashDir.path()); + + return result; +} + +bool UBCFFSubsetAdaptor::UBCFFSubsetReader::parseGSection(const QDomElement &element) +{ + mGSectionContainer = new UBGraphicsGroupContainerItem(); + + QDomElement currentSvgElement = element.firstChildElement(); + while (!currentSvgElement.isNull()) { + parseSvgElement(currentSvgElement); + currentSvgElement = currentSvgElement.nextSiblingElement(); + } + + if (mGSectionContainer->childItems().count()) + { + mCurrentScene->addGroup(mGSectionContainer); + } + else + { + delete mGSectionContainer; + } + mGSectionContainer = NULL; + + return true; +} +bool UBCFFSubsetAdaptor::UBCFFSubsetReader::parseSvgSwitchSection(const QDomElement &element) +{ + + QDomElement currentSvgElement = element.firstChildElement(); + while (!currentSvgElement.isNull()) { + if (parseSvgElement(currentSvgElement)) + return true; + } + + return false; +} + +bool UBCFFSubsetAdaptor::UBCFFSubsetReader::parseSvgRect(const QDomElement &element) +{ + qreal x1 = element.attribute(aX).toDouble(); + qreal y1 = element.attribute(aY).toDouble(); + //rect dimensions + qreal width = element.attribute(aWidth).toDouble(); + qreal height = element.attribute(aHeight).toDouble(); + + QString textFillColor = element.attribute(aFill); + QString textStrokeColor = element.attribute(aStroke); + QString textStrokeWidth = element.attribute(aStrokewidth); + + QColor fillColor = !textFillColor.isNull() ? colorFromString(textFillColor) : QColor(); + QColor strokeColor = !textStrokeColor.isNull() ? colorFromString(textStrokeColor) : QColor(); + int strokeWidth = textStrokeWidth.toInt(); + + x1 -= strokeWidth/2; + y1 -= strokeWidth/2; + width += strokeWidth; + height += strokeWidth; + + //init svg generator with temp file + QSvgGenerator *generator = createSvgGenerator(width, height); + + //init painter to paint to svg + QPainter painter; + + painter.begin(generator); + + //fill rect + if (fillColor.isValid()) { + painter.setBrush(QBrush(fillColor)); + painter.fillRect(0, 0, width, height, fillColor); + } + QPen pen; + if (strokeColor.isValid()) { + pen.setColor(strokeColor); + } + if (strokeWidth) + pen.setWidth(strokeWidth); + painter.setPen(pen); + painter.drawRect(0, 0, width, height); + + painter.end(); + + UBGraphicsSvgItem *svgItem = mCurrentScene->addSvg(QUrl::fromLocalFile(generator->fileName())); + + QString uuid = QUuid::createUuid().toString(); + mRefToUuidMap.insert(element.attribute(aId), uuid); + svgItem->setUuid(QUuid(uuid)); + + QTransform transform; + QString textTransform = element.attribute(aTransform); + + svgItem->resetTransform(); + if (!textTransform.isNull()) { + transform = transformFromString(textTransform, svgItem); + } + + repositionSvgItem(svgItem, width, height, x1, y1, transform); + hashSceneItem(element, svgItem); + + if (mGSectionContainer) + { + addItemToGSection(svgItem); + } + + delete generator; + + return true; +} +bool UBCFFSubsetAdaptor::UBCFFSubsetReader::parseSvgEllipse(const QDomElement &element) +{ + //ellipse horisontal and vertical radius + qreal rx = element.attribute(aRx).toDouble(); + qreal ry = element.attribute(aRy).toDouble(); + QSvgGenerator *generator = createSvgGenerator(rx * 2, ry * 2); + + //fill and stroke color + QColor fillColor = colorFromString(element.attribute(aFill)); + QColor strokeColor = colorFromString(element.attribute(aStroke)); + int strokeWidth = element.attribute(aStrokewidth).toInt(); + + //ellipse center coordinates + qreal cx = element.attribute(aCx).toDouble(); + qreal cy = element.attribute(aCy).toDouble(); + + //init painter to paint to svg + QPainter painter; + painter.begin(generator); + + QPen pen(strokeColor); + pen.setWidth(strokeWidth); + painter.setPen(pen); + painter.setBrush(QBrush(fillColor)); + + painter.drawEllipse(0, 0, rx * 2, ry * 2); + + painter.end(); + + UBGraphicsSvgItem *svgItem = mCurrentScene->addSvg(QUrl::fromLocalFile(generator->fileName())); + + QString uuid = QUuid::createUuid().toString(); + mRefToUuidMap.insert(element.attribute(aId), uuid); + svgItem->setUuid(QUuid(uuid)); + + QTransform transform; + QString textTransform = element.attribute(aTransform); + + svgItem->resetTransform(); + if (!textTransform.isNull()) { + transform = transformFromString(textTransform, svgItem); + } + + + repositionSvgItem(svgItem, rx * 2, ry * 2, cx - 2*rx, cy+ry, transform); + hashSceneItem(element, svgItem); + + if (mGSectionContainer) + { + addItemToGSection(svgItem); + } + + delete generator; + + return true; +} +bool UBCFFSubsetAdaptor::UBCFFSubsetReader::parseSvgPolygon(const QDomElement &element) +{ + QString svgPoints = element.attribute(aPoints); + QPolygonF polygon; + + if (!svgPoints.isNull()) { + QStringList ts = svgPoints.split(QLatin1Char(' '), QString::SkipEmptyParts); + + foreach(const QString sPoint, ts) { + QStringList sCoord = sPoint.split(QLatin1Char(','), QString::SkipEmptyParts); + if (sCoord.size() == 2) { + QPointF point; + point.setX(sCoord.at(0).toFloat()); + point.setY(sCoord.at(1).toFloat()); + polygon << point; + } + else if (sCoord.size() == 4){ + //This is the case on system were the "," is used to seperate decimal + QPointF point; + QString x = sCoord.at(0) + "." + sCoord.at(1); + QString y = sCoord.at(2) + "." + sCoord.at(3); + point.setX(x.toFloat()); + point.setY(y.toFloat()); + polygon << point; + } + else { + qWarning() << "cannot make sense of a 'point' value" << sCoord; + } + } + } + + //bounding rect lef top corner coordinates + qreal x1 = polygon.boundingRect().topLeft().x(); + qreal y1 = polygon.boundingRect().topLeft().y(); + //bounding rect dimensions + qreal width = polygon.boundingRect().width(); + qreal height = polygon.boundingRect().height(); + + QString strokeColorText = element.attribute(aStroke); + QString fillColorText = element.attribute(aFill); + QString strokeWidthText = element.attribute(aStrokewidth); + + QColor strokeColor = !strokeColorText.isEmpty() ? colorFromString(strokeColorText) : QColor(); + QColor fillColor = !fillColorText.isEmpty() ? colorFromString(fillColorText) : QColor(); + int strokeWidth = strokeWidthText.toDouble(); + + QPen pen; + pen.setColor(strokeColor); + pen.setWidth(strokeWidth); + + QBrush brush; + brush.setColor(fillColor); + brush.setStyle(Qt::SolidPattern); + + + QUuid itemUuid(element.attribute(aId).right(QUuid().toString().length())); + QUuid itemGroupUuid(element.attribute(aId).left(QUuid().toString().length()-1)); + if (!itemUuid.isNull() && (itemGroupUuid!=itemUuid)) // reimported from UBZ + { + UBGraphicsPolygonItem *graphicsPolygon = mCurrentScene->polygonToPolygonItem(polygon); + + graphicsPolygon->setBrush(brush); + + QTransform transform; + QString textTransform = element.attribute(aTransform); + + graphicsPolygon->resetTransform(); + if (!textTransform.isNull()) { + transform = transformFromString(textTransform, graphicsPolygon); + } + mCurrentScene->addItem(graphicsPolygon); + + graphicsPolygon->setUuid(itemUuid); + mRefToUuidMap.insert(element.attribute(aId), itemUuid.toString()); + + } + else // single CFF + { + QSvgGenerator *generator = createSvgGenerator(width + pen.width(), height + pen.width()); + QPainter painter; + + painter.begin(generator); //drawing to svg tmp file + + painter.translate(pen.widthF() / 2 - x1, pen.widthF() / 2 - y1); + painter.setBrush(brush); + painter.setPen(pen); + painter.drawPolygon(polygon); + + painter.end(); + + //add resulting svg file to scene + UBGraphicsSvgItem *svgItem = mCurrentScene->addSvg(QUrl::fromLocalFile(generator->fileName())); + QTransform transform; + QString textTransform = element.attribute(aTransform); + + QUuid uuid = QUuid::createUuid().toString(); + mRefToUuidMap.insert(element.attribute(aId), uuid.toString()); + svgItem->setUuid(uuid); + + svgItem->resetTransform(); + if (!textTransform.isNull()) { + transform = transformFromString(textTransform, svgItem); + } + repositionSvgItem(svgItem, width +strokeWidth, height + strokeWidth, x1 - strokeWidth/2 + transform.m31(), y1 + strokeWidth/2 + transform.m32(), transform); + hashSceneItem(element, svgItem); + + if (mGSectionContainer) + { + addItemToGSection(svgItem); + } + + delete generator; + } + return true; +} +bool UBCFFSubsetAdaptor::UBCFFSubsetReader::parseSvgPolyline(const QDomElement &element) +{ + QString svgPoints = element.attribute(aPoints); + QPolygonF polygon; + + if (!svgPoints.isNull()) { + QStringList ts = svgPoints.split(QLatin1Char(' '), + QString::SkipEmptyParts); + + foreach(const QString sPoint, ts) { + QStringList sCoord = sPoint.split(QLatin1Char(','), QString::SkipEmptyParts); + if (sCoord.size() == 2) { + QPointF point; + point.setX(sCoord.at(0).toFloat()); + point.setY(sCoord.at(1).toFloat()); + polygon << point; + } + else if (sCoord.size() == 4){ + //This is the case on system were the "," is used to seperate decimal + QPointF point; + QString x = sCoord.at(0) + "." + sCoord.at(1); + QString y = sCoord.at(2) + "." + sCoord.at(3); + point.setX(x.toFloat()); + point.setY(y.toFloat()); + polygon << point; + } + else { + qWarning() << "cannot make sense of a 'point' value" << sCoord; + } + } + } + + //bounding rect lef top corner coordinates + qreal x1 = polygon.boundingRect().topLeft().x(); + qreal y1 = polygon.boundingRect().topLeft().y(); + + //bounding rect dimensions + qreal width = polygon.boundingRect().width(); + qreal height = polygon.boundingRect().height(); + + QString strokeColorText = element.attribute(aStroke); + QString strokeWidthText = element.attribute(aStrokewidth); + + QColor strokeColor = !strokeColorText.isEmpty() ? colorFromString(strokeColorText) : QColor(); + int strokeWidth = strokeWidthText.toDouble(); + + width += strokeWidth; + height += strokeWidth; + + QPen pen; + pen.setColor(strokeColor); + pen.setWidth(strokeWidth); + + QBrush brush; + brush.setColor(strokeColor); + brush.setStyle(Qt::SolidPattern); + + QUuid itemUuid(element.attribute(aId).right(QUuid().toString().length())); + QUuid itemGroupUuid(element.attribute(aId).left(QUuid().toString().length()-1)); + if (!itemUuid.isNull() && (itemGroupUuid!=itemUuid)) // reimported from UBZ + { + UBGraphicsPolygonItem *graphicsPolygon = new UBGraphicsPolygonItem(polygon); + + UBGraphicsStroke *stroke = new UBGraphicsStroke(); + graphicsPolygon->setStroke(stroke); + + graphicsPolygon->setBrush(brush); + QTransform transform; + QString textTransform = element.attribute(aTransform); + + graphicsPolygon->resetTransform(); + if (!textTransform.isNull()) { + transform = transformFromString(textTransform, graphicsPolygon); + } + mCurrentScene->addItem(graphicsPolygon); + + graphicsPolygon->setUuid(itemUuid); + mRefToUuidMap.insert(element.attribute(aId), itemUuid.toString()); + + } + else // simple CFF + { + QSvgGenerator *generator = createSvgGenerator(width + pen.width(), height + pen.width()); + QPainter painter; + + painter.begin(generator); //drawing to svg tmp file + + painter.translate(pen.widthF() / 2 - x1, pen.widthF() / 2 - y1); + painter.setBrush(brush); + painter.setPen(pen); + painter.drawPolygon(polygon); + + painter.end(); + + //add resulting svg file to scene + UBGraphicsSvgItem *svgItem = mCurrentScene->addSvg(QUrl::fromLocalFile(generator->fileName())); + + QString uuid = QUuid::createUuid().toString(); + mRefToUuidMap.insert(element.attribute(aId), uuid); + svgItem->setUuid(QUuid(uuid)); + + QTransform transform; + QString textTransform = element.attribute(aTransform); + + svgItem->resetTransform(); + if (!textTransform.isNull()) { + transform = transformFromString(textTransform, svgItem); + } + repositionSvgItem(svgItem, width +strokeWidth, height + strokeWidth, x1 - strokeWidth/2 + transform.m31(), y1 + strokeWidth/2 + transform.m32(), transform); + hashSceneItem(element, svgItem); + + if (mGSectionContainer) + { + addItemToGSection(svgItem); + } + + delete generator; + } + + + return true; +} +void UBCFFSubsetAdaptor::UBCFFSubsetReader::parseTextAttributes(const QDomElement &element, + qreal &fontSize, QColor &fontColor, QString &fontFamily, + QString &fontStretch, bool &italic, int &fontWeight, + int &textAlign, QTransform &fontTransform) +{ + //consider inch has 72 liens + //since svg font size is given in pixels, divide it by pixels per line + QString fontSz = element.attribute(aFontSize); + if (!fontSz.isNull()) fontSize = fontSz.toDouble() * 72 / QApplication::desktop()->physicalDpiY(); + + QString fontColorText = element.attribute(aFill); + if (!fontColorText.isNull()) fontColor = colorFromString(fontColorText); + + QString fontFamilyText = element.attribute(aFontfamily); + if (!fontFamilyText.isNull()) fontFamily = fontFamilyText; + + QString fontStretchText = element.attribute(aFontstretch); + if (!fontStretchText.isNull()) fontStretch = fontStretchText; + + if (!element.attribute(aFontstyle).isNull()) + italic = (element.attribute(aFontstyle) == "italic"); + + QString weight = element.attribute(aFontweight); + if (!weight.isNull()) { + if (weight == "normal") fontWeight = QFont::Normal; + else if (weight == "light") fontWeight = QFont::Light; + else if (weight == "demibold") fontWeight = QFont::DemiBold; + else if (weight == "bold") fontWeight = QFont::Bold; + else if (weight == "black") fontWeight = QFont::Black; + } + QString align = element.attribute(aTextalign); + if (!align.isNull()) { + if (align == "middle" || align == "center") textAlign = Qt::AlignHCenter; + else if (align == "start") textAlign = Qt::AlignLeft; + else if (align == "end") textAlign = Qt::AlignRight; + } + + if (!element.attribute(aTransform).isNull()) + fontTransform = transformFromString(element.attribute(aTransform)); +} +void UBCFFSubsetAdaptor::UBCFFSubsetReader::readTextBlockAttr(const QDomElement &element, QTextBlockFormat &format) +{ + QString fontStretchText = element.attribute(aFontstretch); + if (!fontStretchText.isNull()) format.setAlignment(Qt::AlignJustify); + + QString align = element.attribute(aTextalign); + if (!align.isNull()) { + if (align == "middle" || align == "center") format.setAlignment(Qt::AlignHCenter); + else if (align == "start") format.setAlignment(Qt::AlignLeft); + else if (align == "end") format.setAlignment(Qt::AlignRight); + else if (align == "justify") format.setAlignment(Qt::AlignJustify); + } +} +void UBCFFSubsetAdaptor::UBCFFSubsetReader::readTextCharAttr(const QDomElement &element, QTextCharFormat &format) +{ + QString fontSz = element.attribute(aFontSize); + if (!fontSz.isNull()) { + qreal fontSize = fontSz.remove("pt").toDouble(); + format.setFontPointSize(fontSize); + } + QString fontColorText = element.attribute(aFill); + if (!fontColorText.isNull()) { + QColor fontColor = colorFromString(fontColorText); + if (fontColor.isValid()) format.setForeground(fontColor); + } + QString fontFamilyText = element.attribute(aFontfamily); + if (!fontFamilyText.isNull()) { + format.setFontFamily(fontFamilyText); + } + if (!element.attribute(aFontstyle).isNull()) { + bool italic = (element.attribute(aFontstyle) == "italic"); + format.setFontItalic(italic); + } + QString weight = element.attribute(aFontweight); + if (!weight.isNull()) { + if (weight == "normal") format.setFontWeight(QFont::Normal); + else if (weight == "light") format.setFontWeight(QFont::Light); + else if (weight == "demibold") format.setFontWeight(QFont::DemiBold); + else if (weight == "bold") format.setFontWeight(QFont::Bold); + else if (weight == "black") format.setFontWeight(QFont::Black); + } +} + +bool UBCFFSubsetAdaptor::UBCFFSubsetReader::parseSvgText(const QDomElement &element) +{ + qreal x = element.attribute(aX).toDouble(); + qreal y = element.attribute(aY).toDouble(); + qreal width = element.attribute(aWidth).toDouble(); + qreal height = element.attribute(aHeight).toDouble(); + + + qreal fontSize = 12; + QColor fontColor(qApp->palette().foreground().color()); + QString fontFamily = "Arial"; + QString fontStretch = "normal"; + bool italic = false; + int fontWeight = QFont::Normal; + int textAlign = Qt::AlignLeft; + QTransform fontTransform; + parseTextAttributes(element, fontSize, fontColor, fontFamily, fontStretch, italic, fontWeight, textAlign, fontTransform); + + QFont startFont(fontFamily, fontSize, fontWeight, italic); + height = QFontMetrics(startFont).height(); + width = QFontMetrics(startFont).width(element.text()) + 5; + + QSvgGenerator *generator = createSvgGenerator(width, height); + QPainter painter; + painter.begin(generator); + painter.setFont(startFont); + + qreal curY = 0.0; + qreal curX = 0.0; + qreal linespacing = QFontMetricsF(painter.font()).leading(); + +// remember if text area has transform +// QString transformString; + QTransform transform = fontTransform; + + QRectF lastDrawnTextBoundingRect; + //parse text area tags + + //recursive call any tspan in text svg element + parseTSpan(element, painter + , curX, curY, width, height, linespacing, lastDrawnTextBoundingRect + , fontSize, fontColor, fontFamily, fontStretch, italic, fontWeight, textAlign, fontTransform); + + painter.end(); + + //add resulting svg file to scene + UBGraphicsSvgItem *svgItem = mCurrentScene->addSvg(QUrl::fromLocalFile(generator->fileName())); + + QString uuid = QUuid::createUuid().toString(); + mRefToUuidMap.insert(element.attribute(aId), uuid); + svgItem->setUuid(QUuid(uuid)); + + svgItem->resetTransform(); + repositionSvgItem(svgItem, width, height, x + transform.m31(), y + transform.m32(), transform); + hashSceneItem(element, svgItem); + + if (mGSectionContainer) + { + addItemToGSection(svgItem); + } + + delete generator; + return true; +} + +void UBCFFSubsetAdaptor::UBCFFSubsetReader::parseTSpan(const QDomElement &parent, QPainter &painter + , qreal &curX, qreal &curY, qreal &width, qreal &height, qreal &linespacing, QRectF &lastDrawnTextBoundingRect + , qreal &fontSize, QColor &fontColor, QString &fontFamily, QString &fontStretch, bool &italic + , int &fontWeight, int &textAlign, QTransform &fontTransform) +{ + QDomNode curNode = parent.firstChild(); + while (!curNode.isNull()) { + if (curNode.toElement().tagName() == tTspan) { + QDomElement curTSpan = curNode.toElement(); + parseTextAttributes(curTSpan, fontSize, fontColor, fontFamily, fontStretch, italic + , fontWeight, textAlign, fontTransform); + painter.setFont(QFont(fontFamily, fontSize, fontWeight, italic)); + painter.setPen(fontColor); + linespacing = QFontMetricsF(painter.font()).leading(); + parseTSpan(curTSpan, painter + , curX, curY, width, height, linespacing, lastDrawnTextBoundingRect + , fontSize, fontColor, fontFamily, fontStretch, italic, fontWeight, textAlign, fontTransform); + } else if (curNode.nodeType() == QDomNode::CharacterDataNode + || curNode.nodeType() == QDomNode::CDATASectionNode + || curNode.nodeType() == QDomNode::TextNode) { + + QDomCharacterData textData = curNode.toCharacterData(); + QString text = textData.data().trimmed(); +// width = painter.fontMetrics().width(text); + //get bounding rect to obtain desired text height + lastDrawnTextBoundingRect = painter.boundingRect(QRectF(curX, curY, width, height - curY), textAlign|Qt::TextWordWrap, text); + painter.drawText(curX, curY, width, lastDrawnTextBoundingRect.height(), textAlign|Qt::TextWordWrap, text); + curX += lastDrawnTextBoundingRect.x() + lastDrawnTextBoundingRect.width(); + } else if (curNode.nodeType() == QDomNode::ElementNode + && curNode.toElement().tagName() == tBreak) { + + curY += lastDrawnTextBoundingRect.height() + linespacing; + curX = 0.0; + lastDrawnTextBoundingRect = QRectF(0,0,0,0); + } + curNode = curNode.nextSibling(); + } +} +void UBCFFSubsetAdaptor::UBCFFSubsetReader::parseTSpan(const QDomElement &element, QTextCursor &cursor + , QTextBlockFormat &blockFormat, QTextCharFormat &charFormat) +{ + QDomNode curNode = element.firstChild(); + while (!curNode.isNull()) { + if (curNode.toElement().tagName() == tTspan) { + QDomElement curTspan = curNode.toElement(); + readTextBlockAttr(curTspan, blockFormat); + readTextCharAttr(curTspan, charFormat); + cursor.setBlockFormat(blockFormat); + cursor.setCharFormat(charFormat); + parseTSpan(curTspan, cursor, blockFormat, charFormat); + + } else if (curNode.nodeType() == QDomNode::CharacterDataNode + || curNode.nodeType() == QDomNode::CDATASectionNode + || curNode.nodeType() == QDomNode::TextNode) { + + QDomCharacterData textData = curNode.toCharacterData(); + QString text = textData.data().trimmed(); + cursor.insertText(text, charFormat); + + } else if (curNode.nodeType() == QDomNode::ElementNode + && curNode.toElement().tagName() == tBreak) { + cursor.insertBlock(); + } + curNode = curNode.nextSibling(); + } +} + +bool UBCFFSubsetAdaptor::UBCFFSubsetReader::parseSvgTextarea(const QDomElement &element) +{ + qreal x = element.attribute(aX).toDouble(); + qreal y = element.attribute(aY).toDouble(); + qreal width = element.attribute(aWidth).toDouble(); + qreal height = element.attribute(aHeight).toDouble(); + + QTextBlockFormat blockFormat; + blockFormat.setAlignment(Qt::AlignLeft); + + QTextCharFormat textFormat; + // default values + textFormat.setFontPointSize(12); + textFormat.setForeground(qApp->palette().foreground().color()); + textFormat.setFontFamily("Arial"); + textFormat.setFontItalic(false); + textFormat.setFontWeight(QFont::Normal); + + // readed values + readTextBlockAttr(element, blockFormat); + readTextCharAttr(element, textFormat); + + QTextDocument doc; + doc.setPlainText(""); + QTextCursor tCursor(&doc); + tCursor.setBlockFormat(blockFormat); + tCursor.setCharFormat(textFormat); + + parseTSpan(element, tCursor, blockFormat, textFormat); + + UBGraphicsTextItem *svgItem = mCurrentScene->addTextHtml(doc.toHtml()); + svgItem->resize(width, height); + + QString uuid = QUuid::createUuid().toString(); + mRefToUuidMap.insert(element.attribute(aId), uuid); + svgItem->setUuid(QUuid(uuid)); + + QTransform transform; + QString textTransform = element.attribute(aTransform); + + svgItem->resetTransform(); + if (!textTransform.isNull()) { + transform = transformFromString(textTransform, svgItem); + } + + //by default all the textAreas are not editable + UBGraphicsTextItemDelegate *curDelegate = dynamic_cast(svgItem->Delegate()); + if (curDelegate) { + curDelegate->setEditable(false); + } + + repositionSvgItem(svgItem, width, height, x + transform.m31(), y + transform.m32(), transform); + hashSceneItem(element, svgItem); + + if (mGSectionContainer) + { + addItemToGSection(svgItem); + } + + return true; +} +bool UBCFFSubsetAdaptor::UBCFFSubsetReader::parseSvgImage(const QDomElement &element) +{ + qreal x = element.attribute(aX).toDouble(); + qreal y = element.attribute(aY).toDouble(); + qreal width = element.attribute(aWidth).toDouble(); + qreal height = element.attribute(aHeight).toDouble(); + + QString itemRefPath = element.attribute(aHref); + + QPixmap pix; + if (!itemRefPath.isNull()) { + QString imagePath = pwdContent + "/" + itemRefPath; + if (!QFile::exists(imagePath)) { + qDebug() << "can't load file" << pwdContent + "/" + itemRefPath << "maybe file corrupted"; + return false; + } else { +// qDebug() << "size of file" << itemRefPath << QFileInfo(itemRefPath).size(); + } + pix.load(imagePath); + if (pix.isNull()) { + qDebug() << "can't create pixmap for file" << pwdContent + "/" + itemRefPath << "maybe format does not supported"; + } + } + + UBGraphicsPixmapItem *pixItem = mCurrentScene->addPixmap(pix, NULL); + + QString uuid = QUuid::createUuid().toString(); + mRefToUuidMap.insert(element.attribute(aId), uuid); + pixItem->setUuid(QUuid(uuid)); + + QTransform transform; + QString textTransform = element.attribute(aTransform); + + pixItem->resetTransform(); + if (!textTransform.isNull()) { + transform = transformFromString(textTransform, pixItem); + } + repositionSvgItem(pixItem, width, height, x + transform.m31(), y + transform.m32(), transform); + hashSceneItem(element, pixItem); + + if (mGSectionContainer) + { + addItemToGSection(pixItem); + } + + return true; +} +bool UBCFFSubsetAdaptor::UBCFFSubsetReader::parseSvgFlash(const QDomElement &element) +{ + QString itemRefPath = element.attribute(aHref); + + qreal x = element.attribute(aX).toDouble(); + qreal y = element.attribute(aY).toDouble(); + qreal width = element.attribute(aWidth).toDouble(); + qreal height = element.attribute(aHeight).toDouble(); + + QUrl urlPath; + QString flashPath; + if (!itemRefPath.isNull()) { + flashPath = pwdContent + "/" + itemRefPath; + if (!QFile::exists(flashPath)) { + qDebug() << "can't load file" << pwdContent + "/" + itemRefPath << "maybe file corrupted"; + return false; + } + urlPath = QUrl::fromLocalFile(flashPath); + } + QDir tmpFlashDir(mTmpFlashDir); + if (!tmpFlashDir.exists()) { + qDebug() << "Can't create temporary directory to put flash"; + return false; + } + + QString flashUrl = UBGraphicsW3CWidgetItem::createNPAPIWrapperInDir(flashPath, tmpFlashDir, "application/x-shockwave-flash" + ,QSize(mCurrentSceneRect.width(), mCurrentSceneRect.height())); + UBGraphicsWidgetItem *flashItem = mCurrentScene->addW3CWidget(QUrl::fromLocalFile(flashUrl)); + flashItem->setSourceUrl(urlPath); + + QString uuid = QUuid::createUuid().toString(); + mRefToUuidMap.insert(element.attribute(aId), uuid); + flashItem->setUuid(QUuid(uuid)); + + QTransform transform; + QString textTransform = element.attribute(aTransform); + + flashItem->resetTransform(); + if (!textTransform.isNull()) { + transform = transformFromString(textTransform, flashItem); + } + repositionSvgItem(flashItem, width, height, x + transform.m31(), y + transform.m32(), transform); + hashSceneItem(element, flashItem); + + if (mGSectionContainer) + { + addItemToGSection(flashItem); + } + + return true; +} + +bool UBCFFSubsetAdaptor::UBCFFSubsetReader::parseSvgAudio(const QDomElement &element) +{ + QDomElement parentOfAudio = element.firstChild().toElement(); + + qreal x = parentOfAudio.attribute(aX).toDouble(); + qreal y = parentOfAudio.attribute(aY).toDouble(); + + QString itemRefPath = element.attribute(aHref); + + QUrl concreteUrl; + if (!itemRefPath.isNull()) { + QString audioPath = pwdContent + "/" + itemRefPath; + if (!QFile::exists(audioPath)) { + qDebug() << "can't load file" << pwdContent + "/" + itemRefPath << "maybe file corrupted"; + return false; + } + concreteUrl = QUrl::fromLocalFile(audioPath); + } + + QString uuid = QUuid::createUuid().toString(); + mRefToUuidMap.insert(element.attribute(aId), uuid); + + QString destFile; + bool b = UBPersistenceManager::persistenceManager()->addFileToDocument( + mCurrentScene->document(), + concreteUrl.toLocalFile(), + UBPersistenceManager::audioDirectory, + QUuid(uuid), + destFile); + if (!b) + { + return false; + } + concreteUrl = QUrl::fromLocalFile(destFile); + + UBGraphicsMediaItem *audioItem = mCurrentScene->addAudio(concreteUrl, false); + + QTransform transform; + QString textTransform = parentOfAudio.attribute(aTransform); + + audioItem->resetTransform(); + if (!textTransform.isNull()) { + transform = transformFromString(textTransform, audioItem); + } + repositionSvgItem(audioItem, audioItem->boundingRect().width(), audioItem->boundingRect().height(), x + transform.m31(), y + transform.m32(), transform); + hashSceneItem(element, audioItem); + + if (mGSectionContainer) + { + addItemToGSection(audioItem); + } + + return true; +} +bool UBCFFSubsetAdaptor::UBCFFSubsetReader::parseSvgVideo(const QDomElement &element) +{ + QString itemRefPath = element.attribute(aHref); + if (itemRefPath.startsWith(tFlash + "/") && itemRefPath.endsWith(".swf")) { + if (parseSvgFlash(element)) return true; + else return false; + } + qreal x = element.attribute(aX).toDouble(); + qreal y = element.attribute(aY).toDouble(); + + QUrl concreteUrl; + if (!itemRefPath.isNull()) { + QString videoPath = pwdContent + "/" + itemRefPath; + if (!QFile::exists(videoPath)) { + qDebug() << "can't load file" << pwdContent + "/" + itemRefPath << "maybe file corrupted"; + return false; + } + concreteUrl = QUrl::fromLocalFile(videoPath); + } + + QString uuid = QUuid::createUuid().toString(); + mRefToUuidMap.insert(element.attribute(aId), uuid); + + QString destFile; + bool b = UBPersistenceManager::persistenceManager()->addFileToDocument( + mCurrentScene->document(), + concreteUrl.toLocalFile(), + UBPersistenceManager::videoDirectory, + QUuid(uuid), + destFile); + if (!b) + { + return false; + } + concreteUrl = QUrl::fromLocalFile(destFile); + + UBGraphicsMediaItem *videoItem = mCurrentScene->addVideo(concreteUrl, false); + + QTransform transform; + QString textTransform = element.attribute(aTransform); + + videoItem->resetTransform(); + if (!textTransform.isNull()) { + transform = transformFromString(textTransform, videoItem); + } + repositionSvgItem(videoItem, videoItem->boundingRect().width(), videoItem->boundingRect().height(), x + transform.m31(), y + transform.m32(), transform); + hashSceneItem(element, videoItem); + + if (mGSectionContainer) + { + addItemToGSection(videoItem); + } + + return true; +} + +void UBCFFSubsetAdaptor::UBCFFSubsetReader::parseSvgSectionAttr(const QDomElement &svgSection) +{ + getViewBoxDimenstions(svgSection.attribute(aViewbox)); + mSize = QSize(svgSection.attribute(aWidth).toInt(), + svgSection.attribute(aHeight).toInt()); +} + +void UBCFFSubsetAdaptor::UBCFFSubsetReader::addItemToGSection(QGraphicsItem *item) +{ + mGSectionContainer->addToGroup(item); +} + +void UBCFFSubsetAdaptor::UBCFFSubsetReader::hashSceneItem(const QDomElement &element, UBGraphicsItem *item) +{ +// adding element pointer to hash to refer if needed + QString key = element.attribute(aId); + if (!key.isNull()) { + persistedItems.insert(key, item); + } +} + +bool UBCFFSubsetAdaptor::UBCFFSubsetReader::parseSvgElement(const QDomElement &parent) +{ + QString tagName = parent.tagName(); + if (parent.namespaceURI() != svgNS) { + qWarning() << "Incorrect namespace, error at content file, line number" << parent.lineNumber(); + //return false; + } + + if (tagName == tG && !parseGSection(parent)) return false; + else if (tagName == tSwitch && !parseSvgSwitchSection(parent)) return false; + else if (tagName == tRect && !parseSvgRect(parent)) return false; + else if (tagName == tEllipse && !parseSvgEllipse(parent)) return false; + else if (tagName == tPolygon && !parseSvgPolygon(parent)) return false; + else if (tagName == tPolyline && !parseSvgPolyline(parent)) return false; + else if (tagName == tText && !parseSvgText(parent)) return false; + else if (tagName == tTextarea && !parseSvgTextarea(parent)) return false; + else if (tagName == tImage && !parseSvgImage(parent)) return false; + else if (tagName == tFlash && !parseSvgFlash(parent)) return false; + else if (tagName == tAudio && !parseSvgAudio(parent)) return false; + else if (tagName == tVideo && !parseSvgVideo(parent)) return false; + + return true; +} + +bool UBCFFSubsetAdaptor::UBCFFSubsetReader::parseSvgPage(const QDomElement &parent) +{ + createNewScene(); + QDomElement currentSvgElement = parent.firstChildElement(); + while (!currentSvgElement.isNull()) { + if (!parseSvgElement(currentSvgElement)) + return false; + + currentSvgElement = currentSvgElement.nextSiblingElement(); + } + + return true; +} +bool UBCFFSubsetAdaptor::UBCFFSubsetReader::parseSvgPageset(const QDomElement &parent) +{ + QDomElement currentPage = parent.firstChildElement(tPage); + while (!currentPage.isNull()) { + if (!parseSvgPage(currentPage)) + return false; + currentPage = currentPage.nextSiblingElement(tPage); + } + return true; +} + +bool UBCFFSubsetAdaptor::UBCFFSubsetReader::parseIwbMeta(const QDomElement &element) +{ + if (element.namespaceURI() != iwbNS) { + qWarning() << "incorrect meta namespace, incorrect document"; + //return false; + } + + return true; +} +bool UBCFFSubsetAdaptor::UBCFFSubsetReader::parseSvg(const QDomElement &svgSection) +{ + if (svgSection.namespaceURI() != svgNS) { + qWarning() << "incorrect svg namespace, incorrect document"; + // return false; + } + + parseSvgSectionAttr(svgSection); + QDomElement currentSvg = svgSection.firstChildElement(); + + if (currentSvg.tagName() != tPageset) { + parseSvgPage(svgSection); + } else if (currentSvg.tagName() == tPageset){ + parseSvgPageset(currentSvg); + } + + return true; +} + +UBGraphicsGroupContainerItem *UBCFFSubsetAdaptor::UBCFFSubsetReader::parseIwbGroup(QDomElement &parent) +{ + //TODO. Create groups from elements parsed by parseIwbElement() function + if (parent.namespaceURI() != iwbNS) { + qWarning() << "incorrect iwb group namespace, incorrect document"; + // return false; + } + + UBGraphicsGroupContainerItem *group = new UBGraphicsGroupContainerItem(); + QMultiMap strokesGroupsContainer; + QList groupContainer; + QString currentStrokeIdentifier; + + QDomElement currentStrokeElement = parent.firstChildElement(); + while (!currentStrokeElement.isNull()) + { + if (tGroup == currentStrokeElement.tagName()) + group->addToGroup(parseIwbGroup(currentStrokeElement)); + else + { + + QString ref = currentStrokeElement.attribute(aRef); + QString uuid = mRefToUuidMap[ref]; + if (!uuid.isEmpty()) + { + if (ref.size() > QUuid().toString().length()) // create stroke group + { + currentStrokeIdentifier = ref.left(QUuid().toString().length()-1); + UBGraphicsPolygonItem *strokeByUuid = qgraphicsitem_cast(mCurrentScene->itemForUuid(QUuid(ref.right(QUuid().toString().length())))); + + if (strokeByUuid) + strokesGroupsContainer.insert(currentStrokeIdentifier, strokeByUuid); + } + else // single elements in group + groupContainer.append(mCurrentScene->itemForUuid(QUuid(uuid))); + } + } + currentStrokeElement = currentStrokeElement.nextSiblingElement(); + } + + + + foreach (QString key, strokesGroupsContainer.keys().toSet()) + { + UBGraphicsStrokesGroup* pStrokesGroup = new UBGraphicsStrokesGroup(); + UBGraphicsStroke *currentStroke = new UBGraphicsStroke(); + foreach(UBGraphicsPolygonItem* poly, strokesGroupsContainer.values(key)) + { + if (poly) + { + mCurrentScene->removeItem(poly); + mCurrentScene->removeItemFromDeletion(poly); + poly->setStrokesGroup(pStrokesGroup); + poly->setStroke(currentStroke); + pStrokesGroup->addToGroup(poly); + } + } + if (currentStroke->polygons().empty()) + delete currentStroke; + + if (pStrokesGroup->childItems().count()) + mCurrentScene->addItem(pStrokesGroup); + else + delete pStrokesGroup; + + if (pStrokesGroup) + { + QGraphicsItem *strokeGroup = qgraphicsitem_cast(pStrokesGroup); + groupContainer.append(strokeGroup); + } + } + + foreach(QGraphicsItem* item, groupContainer) + group->addToGroup(item); + + if (group->childItems().count()) + { + mCurrentScene->addItem(group); + + if (1 == group->childItems().count()) + { + group->destroy(false); + } + } + + return group; +} + +bool UBCFFSubsetAdaptor::UBCFFSubsetReader::strToBool(QString str) +{ + return str == "true"; +} + +bool UBCFFSubsetAdaptor::UBCFFSubsetReader::parseIwbElement(QDomElement &element) +{ + if (element.namespaceURI() != iwbNS) { + qWarning() << "incorrect iwb element namespace, incorrect document"; + // return false; + } + + bool locked = false; + bool isEditableItem = false; + bool isEditable = false; //Text items to convert to UBGraphicsTextItem only + + QString IDRef = element.attribute(aRef); + if (!IDRef.isNull()) { + element.hasAttribute(aBackground) ? strToBool(element.attribute(aBackground)) : false; + locked = element.hasAttribute(aBackground) ? strToBool(element.attribute(aBackground)) : false; + isEditableItem = element.hasAttribute(aEditable); + if (isEditableItem) + isEditable = strToBool(element.attribute(aEditable)); + + UBGraphicsItem *referedItem(0); + QHash::iterator iReferedItem; + iReferedItem = persistedItems.find(IDRef); + if (iReferedItem != persistedItems.end()) { + referedItem = *iReferedItem; + referedItem->Delegate()->lock(locked); + } + if (isEditableItem) { + UBGraphicsTextItemDelegate *textDelegate = dynamic_cast(referedItem->Delegate()); + if (textDelegate) { + textDelegate->setEditable(isEditable); + } + } + } + + return true; +} +bool UBCFFSubsetAdaptor::UBCFFSubsetReader::parseDoc() +{ + QDomElement currentTopElement = mDOMdoc.documentElement().firstChildElement(); + while (!currentTopElement.isNull()) { + QString tagName = currentTopElement.tagName(); + if (tagName == tMeta && !parseIwbMeta(currentTopElement)) return false; + else if (tagName == tSvg && !parseSvg(currentTopElement)) return false; + else if (tagName == tGroup && !parseIwbGroup(currentTopElement)) return false; + else if (tagName == tElement && !parseIwbElement(currentTopElement)) return false; + + currentTopElement = currentTopElement.nextSiblingElement(); + } + if (!persistScenes()) return false; + + return true; +} + +void UBCFFSubsetAdaptor::UBCFFSubsetReader::repositionSvgItem(QGraphicsItem *item, qreal width, qreal height, + qreal x, qreal y, + QTransform &transform) +{ + //First using viebox coordinates, then translate them to scene coordinates + + QRectF itemBounds = item->boundingRect(); + + qreal xScale = width / itemBounds.width(); + qreal yScale = height / itemBounds.height(); + + qreal fullScaleX = mVBTransFactor * xScale; + qreal fullScaleY = mVBTransFactor * yScale; + + QPointF oldVector((x - transform.dx()), (y - transform.dy())); + QTransform rTransform; + QPointF newVector = rTransform.map(oldVector); + + QTransform tr = item->sceneTransform(); + item->setTransform(rTransform.scale(fullScaleX, fullScaleY), true); + tr = item->sceneTransform(); + QPoint pos; + if (UBGraphicsTextItem::Type == item->type()) + pos = QPoint((int)((x + mShiftVector.x() + (newVector - oldVector).x())), (int)((y +mShiftVector.y() + (newVector - oldVector).y()) * mVBTransFactor)); + else + pos = QPoint((int)((x + mShiftVector.x() + (newVector - oldVector).x()) * mVBTransFactor), (int)((y +mShiftVector.y() + (newVector - oldVector).y()) * mVBTransFactor)); + + + item->setPos(pos); +} + +bool UBCFFSubsetAdaptor::UBCFFSubsetReader::createNewScene() +{ + mCurrentScene = UBPersistenceManager::persistenceManager()->createDocumentSceneAt(mProxy, mProxy->pageCount(), false); + mCurrentScene->setSceneRect(mViewBox); + if ((mCurrentScene->sceneRect().topLeft().x() >= 0) || (mCurrentScene->sceneRect().topLeft().y() >= 0)) { + mShiftVector = -mViewBox.center(); + } + mCurrentSceneRect = mViewBox; + mVBTransFactor = qMin(mCurrentScene->normalizedSceneRect().width() / mViewPort.width(), + mCurrentScene->normalizedSceneRect().height() / mViewPort.height()); + return true; +} + +bool UBCFFSubsetAdaptor::UBCFFSubsetReader::persistCurrentScene() +{ + if (mCurrentScene != 0 && mCurrentScene->isModified()) + { + UBThumbnailAdaptor::persistScene(mProxy, mCurrentScene, mProxy->pageCount() - 1); + UBSvgSubsetAdaptor::persistScene(mProxy, mCurrentScene, mProxy->pageCount() - 1); + + mCurrentScene->setModified(false); + mCurrentScene = 0; + } + return true; +} +bool UBCFFSubsetAdaptor::UBCFFSubsetReader::persistScenes() +{ + if (!mProxy->pageCount()) { + qDebug() << "No pages created"; + return false; + } + for (int i = 0; i < mProxy->pageCount(); i++) { + mCurrentScene = UBPersistenceManager::persistenceManager()->getDocumentScene(mProxy, i); + if (!mCurrentScene) { + qDebug() << "can't allocate scene, loading failed"; + return false; + } + + UBSvgSubsetAdaptor::persistScene(mProxy, mCurrentScene, i); + UBGraphicsScene *tmpScene = UBSvgSubsetAdaptor::loadScene(mProxy, i); + tmpScene->setModified(true); + UBThumbnailAdaptor::persistScene(mProxy, tmpScene, i); + mCurrentScene->setModified(false); + } + + return true; +} + +QColor UBCFFSubsetAdaptor::UBCFFSubsetReader::colorFromString(const QString& clrString) +{ + //init regexp with pattern + //pattern corresponds to strings like 'rgb(1,2,3) or rgb(10%,20%,30%)' + QRegExp regexp("rgb\\(([0-9]+%{0,1}),([0-9]+%{0,1}),([0-9]+%{0,1})\\)"); + if (regexp.exactMatch(clrString)) + { + if (regexp.capturedTexts().count() == 4 && regexp.capturedTexts().at(0).length() == clrString.length()) + { + int r = regexp.capturedTexts().at(1).toInt(); + if (regexp.capturedTexts().at(1).indexOf("%") != -1) + r = r * 255 / 100; + int g = regexp.capturedTexts().at(2).toInt(); + if (regexp.capturedTexts().at(2).indexOf("%") != -1) + g = g * 255 / 100; + int b = regexp.capturedTexts().at(3).toInt(); + if (regexp.capturedTexts().at(3).indexOf("%") != -1) + b = b * 255 / 100; + return QColor(r, g, b); + } + else + return QColor(); + } + else + return QColor(clrString); +} + +QTransform UBCFFSubsetAdaptor::UBCFFSubsetReader::transformFromString(const QString trString, QGraphicsItem *item) +{ + qreal dxr = 0.0; + qreal dyr = 0.0; + qreal dx = 0.0; + qreal dy = 0.0; + qreal angle = 0.0; + QTransform tr; + + foreach(QString trStr, trString.split(" ", QString::SkipEmptyParts)) + { + //check pattern for strings like 'rotate(10)' + QRegExp regexp("rotate\\( *([-+]{0,1}[0-9]*\\.{0,1}[0-9]*) *\\)"); + if (regexp.exactMatch(trStr)) { + angle = regexp.capturedTexts().at(1).toDouble(); + if (item) + { + item->setTransformOriginPoint(QPointF(0, 0)); + item->setRotation(angle); + } + continue; + }; + + //check pattern for strings like 'rotate(10,20,20)' or 'rotate(10.1,10.2,34.2)' + regexp.setPattern("rotate\\( *([-+]{0,1}[0-9]*\\.{0,1}[0-9]*) *, *([-+]{0,1}[0-9]*\\.{0,1}[0-9]*) *, *([-+]{0,1}[0-9]*\\.{0,1}[0-9]*) *\\)"); + if (regexp.exactMatch(trStr)) { + angle = regexp.capturedTexts().at(1).toDouble(); + dxr = regexp.capturedTexts().at(2).toDouble(); + dyr = regexp.capturedTexts().at(3).toDouble(); + if (item) + { + item->setTransformOriginPoint(QPointF(dxr, dyr)-item->pos()); + item->setRotation(angle); + } + continue; + } + + //check pattern for strings like 'translate(11.0, 12.34)' + regexp.setPattern("translate\\( *([-+]{0,1}[0-9]*\\.{0,1}[0-9]*) *,*([-+]{0,1}[0-9]*\\.{0,1}[0-9]*)*\\)"); + if (regexp.exactMatch(trStr)) { + dx = regexp.capturedTexts().at(1).toDouble(); + dy = regexp.capturedTexts().at(2).toDouble(); + tr.translate(dx,dy); + continue; + } + } + return tr; +} + +bool UBCFFSubsetAdaptor::UBCFFSubsetReader::getViewBoxDimenstions(const QString& viewBox) +{ + QStringList capturedTexts = viewBox.split(" ", QString::SkipEmptyParts); + if (capturedTexts.count()) + { + if (4 == capturedTexts.count()) + { + mViewBox = QRectF(capturedTexts.at(0).toDouble(), capturedTexts.at(1).toDouble(), capturedTexts.at(2).toDouble(), capturedTexts.at(3).toDouble()); + mViewPort = mViewBox; + mViewPort.translate(- mViewPort.center()); + mViewBoxCenter.setX(mViewBox.width() / 2); + mViewBoxCenter.setY(mViewBox.height() / 2); + + return true; + } + } + + mViewBox = QRectF(0, 0, 1000, 1000); + mViewBoxCenter = QPointF(500, 500); + return false; +} + +QSvgGenerator* UBCFFSubsetAdaptor::UBCFFSubsetReader::createSvgGenerator(qreal width, qreal height) +{ + QSvgGenerator* generator = new QSvgGenerator(); +// qWarning() << QString("Making generator with file %1, size (%2, %3) and viewbox (%4 %5 %6 %7)").arg(mTempFilePath) +// .arg(width).arg(height).arg(0.0).arg(0.0).arg(width).arg(width); + generator->setResolution(QApplication::desktop()->physicalDpiY()); + generator->setFileName(mTempFilePath); + generator->setSize(QSize(width, height)); + generator->setViewBox(QRectF(0, 0, width, height)); + + return generator; +} + +bool UBCFFSubsetAdaptor::UBCFFSubsetReader::getTempFileName() +{ + int tmpNumber = 0; + QDir rootDir; + while (true) + { + mTempFilePath = QString("%1/sanksvg%2.%3") + .arg(rootDir.tempPath()) + .arg(QDateTime::currentDateTime().toString("dd_MM_yyyy_HH-mm")) + .arg(tmpNumber); + if (!QFile::exists(mTempFilePath)) + return true; + tmpNumber++; + if (tmpNumber == 100000) + { + qWarning() << "Import failed. Failed to create temporary file for svg objects"; + return false; + } + } +} +bool UBCFFSubsetAdaptor::UBCFFSubsetReader::createTempFlashPath() +{ + int tmpNumber = 0; + QDir systemTmp = QDir::temp(); + + while (true) { + QString dirName = QString("SankTmpFlash%1.%2") + .arg(QDateTime::currentDateTime().toString("dd_MM_yyyy_HH-mm")) + .arg(tmpNumber++); + if (!systemTmp.exists(dirName)) { + if (systemTmp.mkdir(dirName)) { + mTmpFlashDir = QDir(systemTmp.absolutePath() + "/" + dirName); + return true; + } else { + qDebug() << "Can't create temporary dir maybe due to permissions"; + return false; + } + } else if (tmpNumber == 1000) { + qWarning() << "Import failed. Failed to create temporary file for svg objects"; + return false; + } + } + + return true; +} +UBCFFSubsetAdaptor::UBCFFSubsetReader::~UBCFFSubsetReader() +{ +// QList pages; +// for (int i = 0; i < mProxy->pageCount(); i++) { +// pages << i; +// } +// UBPersistenceManager::persistenceManager()->deleteDocumentScenes(mProxy, pages); +} diff --git a/src/adaptors/UBCFFSubsetAdaptor.h b/src/adaptors/UBCFFSubsetAdaptor.h new file mode 100644 index 00000000..68f741e9 --- /dev/null +++ b/src/adaptors/UBCFFSubsetAdaptor.h @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA) + * + * This file is part of Open-Sankoré. + * + * Open-Sankoré is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License, + * with a specific linking exception for the OpenSSL project's + * "OpenSSL" library (or with modified versions of it that use the + * same license as the "OpenSSL" library). + * + * Open-Sankoré 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Open-Sankoré. If not, see . + */ + + + +#ifndef UBCFFSUBSETADAPTOR_H +#define UBCFFSUBSETADAPTOR_H + +#include +#include +#include +#include +#include + +class UBDocumentProxy; +class UBGraphicsScene; +class QSvgGenerator; +class UBGraphicsSvgItem; +class UBGraphicsPixmapItem; +class UBGraphicsItemDelegate; +class QTransform; +class QPainter; +class UBGraphicsItem; +class QGraphicsItem; +class QTextBlockFormat; +class QTextCharFormat; +class QTextCursor; +class UBGraphicsStrokesGroup; + + +class UBCFFSubsetAdaptor +{ +public: + UBCFFSubsetAdaptor(); + static bool ConvertCFFFileToUbz(QString &cffSourceFile, UBDocumentProxy* pDocument); + +private: + class UBCFFSubsetReader + { + public: + UBCFFSubsetReader(UBDocumentProxy *proxy, QFile *content); + ~UBCFFSubsetReader(); + + UBDocumentProxy *mProxy; + QString pwdContent; + + bool parse(); + + private: + QString mTempFilePath; + UBGraphicsScene *mCurrentScene; + QRectF mCurrentSceneRect; + QString mIndent; + QRectF mViewBox; + QRectF mViewPort; + qreal mVBTransFactor; + QPointF mViewBoxCenter; + QSize mSize; + QPointF mShiftVector; + bool mSvgGSectionIsOpened; + UBGraphicsGroupContainerItem *mGSectionContainer; + + private: + QDomDocument mDOMdoc; + QDomNode mCurrentDOMElement; + QHash persistedItems; + QMap mRefToUuidMap; + QDir mTmpFlashDir; + + void addItemToGSection(QGraphicsItem *item); + bool hashElements(); + void addExtentionsToHash(QDomElement *parent, QDomElement *topGroup); + + void hashSvg(QDomNode *parent, QString prefix = ""); + void hashSiblingIwbElements(QDomElement *parent, QDomElement *topGroup = 0); + + inline void parseSvgSectionAttr(const QDomElement &); + bool parseSvgPage(const QDomElement &parent); + bool parseSvgPageset(const QDomElement &parent); + bool parseSvgElement(const QDomElement &parent); + bool parseIwbMeta(const QDomElement &element); + bool parseSvg(const QDomElement &svgSection); + + inline bool parseGSection(const QDomElement &element); + inline bool parseSvgSwitchSection(const QDomElement &element); + inline bool parseSvgRect(const QDomElement &element); + inline bool parseSvgEllipse(const QDomElement &element); + inline bool parseSvgPolygon(const QDomElement &element); + inline bool parseSvgPolyline(const QDomElement &element); + inline bool parseSvgText(const QDomElement &element); + inline bool parseSvgTextarea(const QDomElement &element); + inline bool parseSvgImage(const QDomElement &element); + inline bool parseSvgFlash(const QDomElement &element); + inline bool parseSvgAudio(const QDomElement &element); + inline bool parseSvgVideo(const QDomElement &element); + inline UBGraphicsGroupContainerItem *parseIwbGroup(QDomElement &parent); + inline bool parseIwbElement(QDomElement &element); + inline void parseTSpan(const QDomElement &parent, QPainter &painter + , qreal &curX, qreal &curY, qreal &width, qreal &height, qreal &linespacing, QRectF &lastDrawnTextBoundingRect + , qreal &fontSize, QColor &fontColor, QString &fontFamily, QString &fontStretch, bool &italic + , int &fontWeight, int &textAlign, QTransform &fontTransform); + inline void parseTSpan(const QDomElement &element, QTextCursor &cursor + , QTextBlockFormat &blockFormat, QTextCharFormat &charFormat); + inline void hashSceneItem(const QDomElement &element, UBGraphicsItem *item); + + // to kill + inline void parseTextAttributes(const QDomElement &element, qreal &fontSize, QColor &fontColor, + QString &fontFamily, QString &fontStretch, bool &italic, + int &fontWeight, int &textAlign, QTransform &fontTransform); + inline void parseTextAttributes(const QDomElement &element, QFont &font, QColor); + inline void readTextBlockAttr(const QDomElement &element, QTextBlockFormat &format); + inline void readTextCharAttr(const QDomElement &element, QTextCharFormat &format); + + //elements parsing methods + bool parseDoc(); + + bool createNewScene(); + bool persistCurrentScene(); + bool persistScenes(); + +// helper methods + void repositionSvgItem(QGraphicsItem *item, qreal width, qreal height, + qreal x, qreal y, + QTransform &transform); + QColor colorFromString(const QString& clrString); + QTransform transformFromString(const QString trString, QGraphicsItem *item = 0); + bool getViewBoxDimenstions(const QString& viewBox); + QSvgGenerator* createSvgGenerator(qreal width, qreal height); + bool getTempFileName(); + inline bool strToBool(QString); + bool createTempFlashPath(); + }; +}; + +#endif // UBCFFSUBSETADAPTOR_H diff --git a/src/adaptors/UBExportAdaptor.h b/src/adaptors/UBExportAdaptor.h index d80f34f9..a7e7ff6a 100644 --- a/src/adaptors/UBExportAdaptor.h +++ b/src/adaptors/UBExportAdaptor.h @@ -36,7 +36,7 @@ class UBDocumentProxy; class UBExportAdaptor : public QObject { - Q_OBJECT; + Q_OBJECT public: UBExportAdaptor(QObject *parent = 0); @@ -46,6 +46,9 @@ class UBExportAdaptor : public QObject virtual QString exportExtention() { return "";} virtual void persist(UBDocumentProxy* pDocument) = 0; virtual bool persistsDocument(UBDocumentProxy* pDocument, const QString& filename); + virtual bool associatedActionactionAvailableFor(const QModelIndex &selectedIndex) {Q_UNUSED(selectedIndex); return false;} + QAction *associatedAction() {return mAssociatedAction;} + void setAssociatedAction(QAction *pAssociatedAction) {mAssociatedAction = pAssociatedAction;} virtual void setVerbose(bool verbose) { @@ -66,6 +69,7 @@ class UBExportAdaptor : public QObject void showErrorsList(QList errorsList); bool mIsVerbose; + QAction* mAssociatedAction; }; diff --git a/src/adaptors/UBExportCFF.cpp b/src/adaptors/UBExportCFF.cpp new file mode 100644 index 00000000..b036b2a1 --- /dev/null +++ b/src/adaptors/UBExportCFF.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA) + * + * This file is part of Open-Sankoré. + * + * Open-Sankoré is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License, + * with a specific linking exception for the OpenSSL project's + * "OpenSSL" library (or with modified versions of it that use the + * same license as the "OpenSSL" library). + * + * Open-Sankoré 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Open-Sankoré. If not, see . + */ + + + +#include "UBExportCFF.h" +#include "UBCFFAdaptor.h" +#include "document/UBDocumentProxy.h" +#include "core/UBDocumentManager.h" +#include "core/UBApplication.h" +#include "core/memcheck.h" +#include "document/UBDocumentController.h" + +#include +#include + + +UBExportCFF::UBExportCFF(QObject *parent) +: UBExportAdaptor(parent) +{ + +} + +UBExportCFF::~UBExportCFF() +{ + +} +QString UBExportCFF::exportName() +{ + return tr("Export to IWB"); +} + +QString UBExportCFF::exportExtention() +{ + return QString(".iwb"); +} + +void UBExportCFF::persist(UBDocumentProxy* pDocument) +{ + QString src = pDocument->persistencePath(); + + if (!pDocument) + return; + + QString filename = askForFileName(pDocument, tr("Export as IWB File")); + + if (filename.length() > 0) + { + QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + + if (mIsVerbose) + UBApplication::showMessage(tr("Exporting document...")); + + UBCFFAdaptor toIWBExporter; + if (toIWBExporter.convertUBZToIWB(src, filename)) + { + if (mIsVerbose) + UBApplication::showMessage(tr("Export successful.")); + } + else + if (mIsVerbose) + UBApplication::showMessage(tr("Export failed.")); + + showErrorsList(toIWBExporter.getConversionMessages()); + + QApplication::restoreOverrideCursor(); + + } + + +} + +bool UBExportCFF::associatedActionactionAvailableFor(const QModelIndex &selectedIndex) +{ + const UBDocumentTreeModel *docModel = qobject_cast(selectedIndex.model()); + if (!selectedIndex.isValid() || docModel->isCatalog(selectedIndex)) { + return false; + } + + return true; +} diff --git a/src/adaptors/UBExportCFF.h b/src/adaptors/UBExportCFF.h new file mode 100644 index 00000000..ce103e28 --- /dev/null +++ b/src/adaptors/UBExportCFF.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA) + * + * This file is part of Open-Sankoré. + * + * Open-Sankoré is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License, + * with a specific linking exception for the OpenSSL project's + * "OpenSSL" library (or with modified versions of it that use the + * same license as the "OpenSSL" library). + * + * Open-Sankoré 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Open-Sankoré. If not, see . + */ + + + +#ifndef UBExportCFF_H_ +#define UBExportCFF_H_ + +#include + +#include "UBExportAdaptor.h" + +#include "frameworks/UBFileSystemUtils.h" + +class UBDocumentProxy; + +class UBExportCFF : public UBExportAdaptor +{ + Q_OBJECT + +public: + UBExportCFF(QObject *parent = 0); + virtual ~UBExportCFF(); + + virtual QString exportName(); + virtual QString exportExtention(); + virtual void persist(UBDocumentProxy* pDocument); + virtual bool associatedActionactionAvailableFor(const QModelIndex &selectedIndex); +}; + +#endif /* UBExportCFF_H_ */ diff --git a/src/adaptors/UBExportDocumentSetAdaptor.cpp b/src/adaptors/UBExportDocumentSetAdaptor.cpp new file mode 100644 index 00000000..87738391 --- /dev/null +++ b/src/adaptors/UBExportDocumentSetAdaptor.cpp @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA) + * + * This file is part of Open-Sankoré. + * + * Open-Sankoré is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License, + * with a specific linking exception for the OpenSSL project's + * "OpenSSL" library (or with modified versions of it that use the + * same license as the "OpenSSL" library). + * + * Open-Sankoré 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Open-Sankoré. If not, see . + */ + + +#include "UBExportDocumentSetAdaptor.h" +#include "UBExportDocument.h" + +#include "frameworks/UBPlatformUtils.h" + +#include "core/UBDocumentManager.h" +#include "core/UBApplication.h" + +#include "document/UBDocumentProxy.h" +#include "document/UBDocumentController.h" + +#include "globals/UBGlobals.h" +#include "core/UBPersistenceManager.h" +#include "core/UBForeignObjectsHandler.h" + +THIRD_PARTY_WARNINGS_DISABLE +#include "quazip.h" +#include "quazipfile.h" +THIRD_PARTY_WARNINGS_ENABLE + +#include "core/memcheck.h" + +UBExportDocumentSetAdaptor::UBExportDocumentSetAdaptor(QObject *parent) + : UBExportAdaptor(parent) +{ + +} + +UBExportDocumentSetAdaptor::~UBExportDocumentSetAdaptor() +{ + // NOOP +} + +void UBExportDocumentSetAdaptor::persist(UBDocumentProxy* pDocumentProxy) +{ + QModelIndex treeViewParentIndex; + UBPersistenceManager *persistenceManager = UBPersistenceManager::persistenceManager(); + UBDocumentTreeModel *treeModel = persistenceManager->mDocumentTreeStructureModel; + QString filename; + + if (pDocumentProxy) { + treeViewParentIndex = treeModel->indexForProxy(pDocumentProxy); + if (!treeViewParentIndex.isValid()) { + qDebug() << "failed to export"; + UBApplication::showMessage(tr("Failed to export...")); + return; + } + filename = askForFileName(pDocumentProxy, tr("Export as UBX File")); + + } else { + treeViewParentIndex = UBApplication::documentController->firstSelectedTreeIndex(); + if (!treeViewParentIndex.isValid()) { + qDebug() << "failed to export"; + UBApplication::showMessage(tr("Failed to export...")); + return; + } + + UBDocumentTreeNode* node = treeModel->nodeFromIndex(treeViewParentIndex); + UBDocumentProxy proxy; + proxy.setMetaData(UBSettings::documentName,node->displayName()); + filename = askForFileName(&proxy, tr("Export as UBX File")); + } + + if (filename.length() > 0) + { + QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + + if (mIsVerbose) + UBApplication::showMessage(tr("Exporting document...")); + + if (persistData(treeViewParentIndex, filename)) { + if (mIsVerbose) { + UBApplication::showMessage(tr("Export successful.")); + } + } else { + if (mIsVerbose) { + UBApplication::showMessage(tr("Export failed.")); + } + } + + QApplication::restoreOverrideCursor(); + } +} + +bool UBExportDocumentSetAdaptor::persistData(const QModelIndex &pRootIndex, QString filename) +{ + UBPersistenceManager *persistenceManager = UBPersistenceManager::persistenceManager(); + UBDocumentTreeModel *treeModel = persistenceManager->mDocumentTreeStructureModel; + + QModelIndex index = pRootIndex; + + if (!index.isValid()) { + return false; + } + + QuaZip zip(filename); + zip.setFileNameCodec("UTF-8"); + + if(!zip.open(QuaZip::mdCreate)) + { + qWarning("Export failed. Cause: zip.open(): %d", zip.getZipError()); + return false; + } + + if (!addDocumentToZip(pRootIndex, treeModel, zip)) { + zip.close(); + return false; + } + + zip.close(); + UBPlatformUtils::setFileType(filename, 0x5542647A /* UBdz */); + + return true; +} + +QString UBExportDocumentSetAdaptor::exportExtention() +{ + return QString(".ubx"); +} + +QString UBExportDocumentSetAdaptor::exportName() +{ + return tr("Export to Sankore UBX Format"); +} + +bool UBExportDocumentSetAdaptor::addDocumentToZip(const QModelIndex &pIndex, UBDocumentTreeModel *model, QuaZip &zip) +{ + static int i = 0; + i++; + + QModelIndex parentIndex = pIndex; + if (!parentIndex.isValid()) { + return false; + } + + UBDocumentProxy *pDocumentProxy = model->proxyForIndex(parentIndex); + if (pDocumentProxy) { + +// Q_ASSERT(QFileInfo(pDocumentProxy->persistencePath()).exists()); +// UBForeighnObjectsHandler cleaner; +// cleaner.cure(pDocumentProxy->persistencePath()); + + //UniboardSankoreTransition document; + QString documentPath(pDocumentProxy->persistencePath()); + //document.checkDocumentDirectory(documentPath); + + QDir documentDir = QDir(pDocumentProxy->persistencePath()); + QuaZipFile zipFile(&zip); + UBFileSystemUtils::compressDirInZip(documentDir, QFileInfo(documentPath).fileName() + "/", &zipFile, false); + + if(zip.getZipError() != 0) + { + qWarning("Export failed. Cause: zip.close(): %d", zip.getZipError()); + } + } + + for (int i = 0; i < model->rowCount(parentIndex); ++i) { + QModelIndex curIndex = model->index(i, 0, parentIndex); + if (!addDocumentToZip(curIndex, model, zip)) { + return false; + } + } + + return true; +} + +bool UBExportDocumentSetAdaptor::associatedActionactionAvailableFor(const QModelIndex &selectedIndex) +{ + const UBDocumentTreeModel *docModel = qobject_cast(selectedIndex.model()); + if (!selectedIndex.isValid() || docModel->isDocument(selectedIndex)) { + return false; + } + + return true; +} + diff --git a/src/adaptors/UBExportDocumentSetAdaptor.h b/src/adaptors/UBExportDocumentSetAdaptor.h new file mode 100644 index 00000000..8e463377 --- /dev/null +++ b/src/adaptors/UBExportDocumentSetAdaptor.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA) + * + * This file is part of Open-Sankoré. + * + * Open-Sankoré is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License, + * with a specific linking exception for the OpenSSL project's + * "OpenSSL" library (or with modified versions of it that use the + * same license as the "OpenSSL" library). + * + * Open-Sankoré 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Open-Sankoré. If not, see . + */ + + +#ifndef UBEXPORTDOCUMENTSETADAPTOR_H +#define UBEXPORTDOCUMENTSETADAPTOR_H + +#include +#include "UBExportAdaptor.h" +#include "frameworks/UBFileSystemUtils.h" +#include "globals/UBGlobals.h" + +THIRD_PARTY_WARNINGS_DISABLE +#include "quazip.h" +#include "quazipfile.h" +THIRD_PARTY_WARNINGS_ENABLE + +class UBDocumentProxy; +class UBDocumentTreeModel; + + +class UBExportDocumentSetAdaptor : public UBExportAdaptor +{ + Q_OBJECT + + public: + UBExportDocumentSetAdaptor(QObject *parent = 0); + virtual ~UBExportDocumentSetAdaptor(); + + virtual QString exportName(); + virtual QString exportExtention(); + + virtual void persist(UBDocumentProxy* pDocument); + bool persistData(const QModelIndex &pRootIndex, QString filename); + bool addDocumentToZip(const QModelIndex &pIndex, UBDocumentTreeModel *model, QuaZip &zip); + + virtual bool associatedActionactionAvailableFor(const QModelIndex &selectedIndex); + + +}; + +#endif // UBEXPORTDOCUMENTSETADAPTOR_H diff --git a/src/adaptors/UBExportFullPDF.cpp b/src/adaptors/UBExportFullPDF.cpp index 9b6684f0..d9bb03bc 100644 --- a/src/adaptors/UBExportFullPDF.cpp +++ b/src/adaptors/UBExportFullPDF.cpp @@ -43,6 +43,7 @@ #include "domain/UBGraphicsPDFItem.h" #include "document/UBDocumentProxy.h" +#include "document/UBDocumentController.h" #include "pdf/GraphicsPDFItem.h" @@ -265,6 +266,16 @@ bool UBExportFullPDF::persistsDocument(UBDocumentProxy* pDocumentProxy, const QS return true; } +bool UBExportFullPDF::associatedActionactionAvailableFor(const QModelIndex &selectedIndex) +{ + const UBDocumentTreeModel *docModel = qobject_cast(selectedIndex.model()); + if (!selectedIndex.isValid() || docModel->isCatalog(selectedIndex)) { + return false; + } + + return true; +} + QString UBExportFullPDF::exportExtention() { diff --git a/src/adaptors/UBExportFullPDF.h b/src/adaptors/UBExportFullPDF.h index ffe214f1..e43886f8 100644 --- a/src/adaptors/UBExportFullPDF.h +++ b/src/adaptors/UBExportFullPDF.h @@ -47,6 +47,7 @@ class UBExportFullPDF : public UBExportAdaptor virtual QString exportName(); virtual QString exportExtention(); virtual void persist(UBDocumentProxy* pDocument); + virtual bool associatedActionactionAvailableFor(const QModelIndex &selectedIndex); virtual bool persistsDocument(UBDocumentProxy* pDocument, const QString& filename); diff --git a/src/adaptors/UBExportPDF.cpp b/src/adaptors/UBExportPDF.cpp index f8d22cdc..78e324af 100644 --- a/src/adaptors/UBExportPDF.cpp +++ b/src/adaptors/UBExportPDF.cpp @@ -44,6 +44,7 @@ #include "domain/UBGraphicsPDFItem.h" #include "document/UBDocumentProxy.h" +#include "document/UBDocumentController.h" #include "pdf/GraphicsPDFItem.h" @@ -65,6 +66,16 @@ void UBExportPDF::persist(UBDocumentProxy* pDocumentProxy) persistLocally(pDocumentProxy, tr("Export as PDF File")); } +bool UBExportPDF::associatedActionactionAvailableFor(const QModelIndex &selectedIndex) +{ + const UBDocumentTreeModel *docModel = qobject_cast(selectedIndex.model()); + if (!selectedIndex.isValid() || docModel->isCatalog(selectedIndex)) { + return false; + } + + return true; +} + bool UBExportPDF::persistsDocument(UBDocumentProxy* pDocumentProxy, const QString& filename) { diff --git a/src/adaptors/UBExportPDF.h b/src/adaptors/UBExportPDF.h index 4980399a..694319a7 100644 --- a/src/adaptors/UBExportPDF.h +++ b/src/adaptors/UBExportPDF.h @@ -46,6 +46,7 @@ class UBExportPDF : public UBExportAdaptor virtual QString exportName(); virtual QString exportExtention(); virtual void persist(UBDocumentProxy* pDocument); + virtual bool associatedActionactionAvailableFor(const QModelIndex &selectedIndex); virtual bool persistsDocument(UBDocumentProxy* pDocument, const QString& filename); }; diff --git a/src/adaptors/UBImportCFF.cpp b/src/adaptors/UBImportCFF.cpp new file mode 100644 index 00000000..611b3168 --- /dev/null +++ b/src/adaptors/UBImportCFF.cpp @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA) + * + * This file is part of Open-Sankoré. + * + * Open-Sankoré is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License, + * with a specific linking exception for the OpenSSL project's + * "OpenSSL" library (or with modified versions of it that use the + * same license as the "OpenSSL" library). + * + * Open-Sankoré 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Open-Sankoré. If not, see . + */ + + + +#include +#include + +#include "core/UBApplication.h" +#include "core/UBPersistenceManager.h" +#include "core/UBDocumentManager.h" +#include "core/UBPersistenceManager.h" +#include "document/UBDocumentProxy.h" +#include "domain/UBGraphicsPDFItem.h" +#include "frameworks/UBFileSystemUtils.h" +#include "pdf/PDFRenderer.h" + +#include "UBCFFSubsetAdaptor.h" +#include "UBImportCFF.h" + +#include "globals/UBGlobals.h" + +THIRD_PARTY_WARNINGS_DISABLE +#include "quazip.h" +#include "quazipfile.h" +#include "quazipfileinfo.h" +THIRD_PARTY_WARNINGS_ENABLE + +#include "core/memcheck.h" + +UBImportCFF::UBImportCFF(QObject *parent) + : UBDocumentBasedImportAdaptor(parent) +{ + // NOOP +} + + +UBImportCFF::~UBImportCFF() +{ + // NOOP +} + + +QStringList UBImportCFF::supportedExtentions() +{ + return QStringList("iwb"); +} + + +QString UBImportCFF::importFileFilter() +{ + QString filter = tr("Common File Format ("); + QStringList formats = supportedExtentions(); + bool isFirst = true; + + foreach(QString format, formats) + { + if(isFirst) + isFirst = false; + else + filter.append(" "); + + filter.append("*."+format); + } + + filter.append(")"); + + return filter; +} + +bool UBImportCFF::addFileToDocument(UBDocumentProxy* pDocument, const QFile& pFile) +{ + QFileInfo fi(pFile); + UBApplication::showMessage(tr("Importing file %1...").arg(fi.baseName()), true); + + // first unzip the file to the correct place + //TODO create temporary path for iwb file content + QString path = QDir::tempPath(); + + QString documentRootFolder = expandFileToDir(pFile, path); + QString contentFile; + if (documentRootFolder.isEmpty()) //if file has failed to unzip it is probably just xml file + contentFile = pFile.fileName(); + else //get path to content xml (according to iwbcff specification) + contentFile = documentRootFolder.append("/content.xml"); + + if(!contentFile.length()){ + UBApplication::showMessage(tr("Import of file %1 failed.").arg(fi.baseName())); + return false; + } + else{ + //TODO convert expanded CFF file content to the destination document + //create destination document proxy + //fill metadata and save + UBDocumentProxy* destDocument = new UBDocumentProxy(UBPersistenceManager::persistenceManager()->generateUniqueDocumentPath()); + QDir dir; + dir.mkdir(destDocument->persistencePath()); + + //try to import cff to document + if (UBCFFSubsetAdaptor::ConvertCFFFileToUbz(contentFile, destDocument)) + { + UBPersistenceManager::persistenceManager()->addDirectoryContentToDocument(destDocument->persistencePath(), pDocument); + UBFileSystemUtils::deleteDir(destDocument->persistencePath()); + delete destDocument; + UBApplication::showMessage(tr("Import successful.")); + return true; + } + else + { + UBFileSystemUtils::deleteDir(destDocument->persistencePath()); + delete destDocument; + UBApplication::showMessage(tr("Import failed.")); + return false; + } + } +} + +QString UBImportCFF::expandFileToDir(const QFile& pZipFile, const QString& pDir) +{ + QuaZip zip(pZipFile.fileName()); + + if(!zip.open(QuaZip::mdUnzip)) { + qWarning() << "Import failed. Cause zip.open(): " << zip.getZipError(); + return ""; + } + + zip.setFileNameCodec("UTF-8"); + QuaZipFileInfo info; + QuaZipFile file(&zip); + + //create unique cff document root fodler + //use current date/time and temp number for folder name + QString documentRootFolder; + int tmpNumber = 0; + QDir rootDir; + while (true) { + QString tempPath = QString("%1/sank%2.%3") + .arg(pDir) + .arg(QDateTime::currentDateTime().toString("dd_MM_yyyy_HH-mm")) + .arg(tmpNumber); + if (!rootDir.exists(tempPath)) { + documentRootFolder = tempPath; + break; + } + tmpNumber++; + if (tmpNumber == 100000) { + qWarning() << "Import failed. Failed to create temporary directory for iwb file"; + return ""; + } + } + if (!rootDir.mkdir(documentRootFolder)) { + qWarning() << "Import failed. Couse: failed to create temp folder for cff package"; + } + + QFile out; + char c; + for(bool more=zip.goToFirstFile(); more; more=zip.goToNextFile()) { + if(!zip.getCurrentFileInfo(&info)) { + //TOD UB 4.3 O display error to user or use crash reporter + qWarning() << "Import failed. Cause: getCurrentFileInfo(): " << zip.getZipError(); + return ""; + } +// if(!file.open(QIODevice::ReadOnly)) { +// qWarning() << "Import failed. Cause: file.open(): " << zip.getZipError(); +// return ""; +// } + file.open(QIODevice::ReadOnly); + if(file.getZipError()!= UNZ_OK) { + qWarning() << "Import failed. Cause: file.getFileName(): " << zip.getZipError(); + return ""; + } + + QString newFileName = documentRootFolder + "/" + file.getActualFileName(); + + QFileInfo newFileInfo(newFileName); + rootDir.mkpath(newFileInfo.absolutePath()); + + out.setFileName(newFileName); + out.open(QIODevice::WriteOnly); + + while(file.getChar(&c)) + out.putChar(c); + + out.close(); + + if(file.getZipError()!=UNZ_OK) { + qWarning() << "Import failed. Cause: " << zip.getZipError(); + return ""; + } + if(!file.atEnd()) { + qWarning() << "Import failed. Cause: read all but not EOF"; + return ""; + } + + file.close(); + + if(file.getZipError()!=UNZ_OK) { + qWarning() << "Import failed. Cause: file.close(): " << file.getZipError(); + return ""; + } + } + + zip.close(); + + if(zip.getZipError()!=UNZ_OK) { + qWarning() << "Import failed. Cause: zip.close(): " << zip.getZipError(); + return ""; + } + + return documentRootFolder; +} + + +UBDocumentProxy* UBImportCFF::importFile(const QFile& pFile, const QString& pGroup) +{ + Q_UNUSED(pGroup); // group is defined in the imported file + + QFileInfo fi(pFile); + UBApplication::showMessage(tr("Importing file %1...").arg(fi.baseName()), true); + + // first unzip the file to the correct place + //TODO create temporary path for iwb file content + QString path = QDir::tempPath(); + + QString documentRootFolder = expandFileToDir(pFile, path); + QString contentFile; + if (documentRootFolder.isEmpty()) + //if file has failed to umzip it is probably just xml file + contentFile = pFile.fileName(); + else + //get path to content xml + contentFile = QString("%1/content.xml").arg(documentRootFolder); + + if(!contentFile.length()){ + UBApplication::showMessage(tr("Import of file %1 failed.").arg(fi.baseName())); + return 0; + } + else{ + //create destination document proxy + //fill metadata and save + UBDocumentProxy* destDocument = new UBDocumentProxy(UBPersistenceManager::persistenceManager()->generateUniqueDocumentPath()); + QDir dir; + dir.mkdir(destDocument->persistencePath()); + if (pGroup.length() > 0) + destDocument->setMetaData(UBSettings::documentGroupName, pGroup); + if (fi.baseName() > 0) + destDocument->setMetaData(UBSettings::documentName, fi.baseName()); + + destDocument->setMetaData(UBSettings::documentVersion, UBSettings::currentFileVersion); + destDocument->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(QDateTime::currentDateTime())); + + UBDocumentProxy* newDocument = NULL; + //try to import cff to document + if (UBCFFSubsetAdaptor::ConvertCFFFileToUbz(contentFile, destDocument)) + { + newDocument = UBPersistenceManager::persistenceManager()->createDocumentFromDir(destDocument->persistencePath() + ,"" + ,"" + ,false + ,false + ,true); + + UBApplication::showMessage(tr("Import successful.")); + } + else + { + UBFileSystemUtils::deleteDir(destDocument->persistencePath()); + UBApplication::showMessage(tr("Import failed.")); + } + delete destDocument; + + if (documentRootFolder.length() != 0) + UBFileSystemUtils::deleteDir(documentRootFolder); + return newDocument; + } +} diff --git a/src/adaptors/UBImportCFF.h b/src/adaptors/UBImportCFF.h new file mode 100644 index 00000000..d06b9d18 --- /dev/null +++ b/src/adaptors/UBImportCFF.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA) + * + * This file is part of Open-Sankoré. + * + * Open-Sankoré is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License, + * with a specific linking exception for the OpenSSL project's + * "OpenSSL" library (or with modified versions of it that use the + * same license as the "OpenSSL" library). + * + * Open-Sankoré 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Open-Sankoré. If not, see . + */ + + + +#ifndef UBIMPORTCFF_H +#define UBIMPORTCFF_H + +#include +#include "UBImportAdaptor.h" + +class UBDocumentProxy; + +class UBImportCFF : public UBDocumentBasedImportAdaptor +{ + Q_OBJECT; + + public: + UBImportCFF(QObject *parent = 0); + virtual ~UBImportCFF(); + + virtual QStringList supportedExtentions(); + virtual QString importFileFilter(); + + virtual bool addFileToDocument(UBDocumentProxy* pDocument, const QFile& pFile); + virtual UBDocumentProxy* importFile(const QFile& pFile, const QString& pGroup); + + private: + QString expandFileToDir(const QFile& pZipFile, const QString& pDir); +}; + +#endif // UBIMPORTCFF_H diff --git a/src/adaptors/UBImportDocumentSetAdaptor.cpp b/src/adaptors/UBImportDocumentSetAdaptor.cpp new file mode 100644 index 00000000..54888058 --- /dev/null +++ b/src/adaptors/UBImportDocumentSetAdaptor.cpp @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA) + * + * This file is part of Open-Sankoré. + * + * Open-Sankoré is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License, + * with a specific linking exception for the OpenSSL project's + * "OpenSSL" library (or with modified versions of it that use the + * same license as the "OpenSSL" library). + * + * Open-Sankoré 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Open-Sankoré. If not, see . + */ + + +#include "UBImportDocumentSetAdaptor.h" + +#include "document/UBDocumentProxy.h" + +#include "frameworks/UBFileSystemUtils.h" + +#include "core/UBApplication.h" +#include "core/UBSettings.h" +#include "core/UBPersistenceManager.h" + +#include "globals/UBGlobals.h" + +THIRD_PARTY_WARNINGS_DISABLE +#include "quazip.h" +#include "quazipfile.h" +#include "quazipfileinfo.h" +THIRD_PARTY_WARNINGS_ENABLE + +#include "core/memcheck.h" + +UBImportDocumentSetAdaptor::UBImportDocumentSetAdaptor(QObject *parent) + :UBImportAdaptor(parent) +{ + // NOOP +} + +UBImportDocumentSetAdaptor::~UBImportDocumentSetAdaptor() +{ + // NOOP +} + + +QStringList UBImportDocumentSetAdaptor::supportedExtentions() +{ + return QStringList("ubx"); +} + + +QString UBImportDocumentSetAdaptor::importFileFilter() +{ + return tr("Open-Sankore (set of documents) (*.ubx)"); +} + +QFileInfoList UBImportDocumentSetAdaptor::importData(const QString &zipFile, const QString &destination) +{ + //Create tmp directory to extract data, will be deleted after + QString tmpDir; + int i = 0; + QFileInfoList result; + do { + tmpDir = QDir::tempPath() + "/Sankore_tmpImportUBX_" + QString::number(i++); + } while (QFileInfo(tmpDir).exists()); + + QDir(QDir::tempPath()).mkdir(tmpDir); + + QFile fZipFile(zipFile); + + if (!extractFileToDir(fZipFile, tmpDir)) { + UBFileSystemUtils::deleteDir(tmpDir); + return QFileInfoList(); + } + + QDir tDir(tmpDir); + + foreach(QFileInfo readDir, tDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden , QDir::Name)) { + QString newFileName = readDir.fileName(); + if (QFileInfo(destination + "/" + readDir.fileName()).exists()) { + newFileName = QFileInfo(UBPersistenceManager::persistenceManager()->generateUniqueDocumentPath(tmpDir)).fileName(); + } + QString newFilePath = destination + "/" + newFileName; + if (UBFileSystemUtils::copy(readDir.absoluteFilePath(), newFilePath)) { + result.append(newFilePath); + } + } + + UBFileSystemUtils::deleteDir(tmpDir); + + return result; +} + + +bool UBImportDocumentSetAdaptor::extractFileToDir(const QFile& pZipFile, const QString& pDir) +{ + QDir rootDir(pDir); + QuaZip zip(pZipFile.fileName()); + + if(!zip.open(QuaZip::mdUnzip)) + { + qWarning() << "Import failed. Cause zip.open(): " << zip.getZipError(); + return false; + } + + zip.setFileNameCodec("UTF-8"); + QuaZipFileInfo info; + QuaZipFile file(&zip); + + QFile out; + char c; + + QString documentRoot = QFileInfo(pDir).absoluteFilePath(); + for(bool more=zip.goToFirstFile(); more; more=zip.goToNextFile()) + { + if(!zip.getCurrentFileInfo(&info)) + { + //TOD UB 4.3 O display error to user or use crash reporter + qWarning() << "Import failed. Cause: getCurrentFileInfo(): " << zip.getZipError(); + return false; + } + + if(!file.open(QIODevice::ReadOnly)) + { + qWarning() << "Import failed. Cause: file.open(): " << zip.getZipError(); + return false; + } + + if(file.getZipError()!= UNZ_OK) + { + qWarning() << "Import failed. Cause: file.getFileName(): " << zip.getZipError(); + return false; + } + + QString actFileName = file.getActualFileName(); +// int ind = actFileName.indexOf("/"); +// if ( ind!= -1) { +// actFileName.remove(0, ind + 1); +// } + QString newFileName = documentRoot + "/" + actFileName; + QFileInfo newFileInfo(newFileName); + if (!rootDir.mkpath(newFileInfo.absolutePath())) + return false; + + out.setFileName(newFileName); + if (!out.open(QIODevice::WriteOnly)) + return false; + + // Slow like hell (on GNU/Linux at least), but it is not my fault. + // Not ZIP/UNZIP package's fault either. + // The slowest thing here is out.putChar(c). + QByteArray outFileContent = file.readAll(); + if (out.write(outFileContent) == -1) + { + qWarning() << "Import failed. Cause: Unable to write file"; + out.close(); + return false; + } + + while(file.getChar(&c)) + out.putChar(c); + + out.close(); + + if(file.getZipError()!=UNZ_OK) + { + qWarning() << "Import failed. Cause: " << zip.getZipError(); + return false; + } + + if(!file.atEnd()) + { + qWarning() << "Import failed. Cause: read all but not EOF"; + return false; + } + + file.close(); + + if(file.getZipError()!=UNZ_OK) + { + qWarning() << "Import failed. Cause: file.close(): " << file.getZipError(); + return false; + } + + } + + zip.close(); + + if(zip.getZipError()!=UNZ_OK) + { + qWarning() << "Import failed. Cause: zip.close(): " << zip.getZipError(); + return false; + } + + return true; +} diff --git a/src/adaptors/UBImportDocumentSetAdaptor.h b/src/adaptors/UBImportDocumentSetAdaptor.h new file mode 100644 index 00000000..7e07cfc5 --- /dev/null +++ b/src/adaptors/UBImportDocumentSetAdaptor.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA) + * + * This file is part of Open-Sankoré. + * + * Open-Sankoré is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License, + * with a specific linking exception for the OpenSSL project's + * "OpenSSL" library (or with modified versions of it that use the + * same license as the "OpenSSL" library). + * + * Open-Sankoré 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Open-Sankoré. If not, see . + */ + + +#ifndef UBIMPORTDOCUMENTSETADAPTOR_H +#define UBIMPORTDOCUMENTSETADAPTOR_H + +#include +#include "UBImportAdaptor.h" + +class UBDocumentProxy; + +class UBImportDocumentSetAdaptor : public UBImportAdaptor +{ + Q_OBJECT + + public: + UBImportDocumentSetAdaptor(QObject *parent = 0); + virtual ~UBImportDocumentSetAdaptor(); + + virtual QStringList supportedExtentions(); + virtual QString importFileFilter(); + + QFileInfoList importData(const QString &zipFile, const QString &destination); + +// virtual UBDocumentProxy* importFile(const QFile& pFile, const QString& pGroup); +// virtual bool addFileToDocument(UBDocumentProxy* pDocument, const QFile& pFile); + + private: + bool extractFileToDir(const QFile& pZipFile, const QString& pDir); + +}; + +#endif // UBIMPORTDOCUMENTSETADAPTOR_H diff --git a/src/adaptors/UBWebPublisher.cpp b/src/adaptors/UBWebPublisher.cpp new file mode 100644 index 00000000..717dbc65 --- /dev/null +++ b/src/adaptors/UBWebPublisher.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA) + * + * This file is part of Open-Sankoré. + * + * Open-Sankoré is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License, + * with a specific linking exception for the OpenSSL project's + * "OpenSSL" library (or with modified versions of it that use the + * same license as the "OpenSSL" library). + * + * Open-Sankoré 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Open-Sankoré. If not, see . + */ + + + +#include "UBWebPublisher.h" + +#include "document/UBDocumentProxy.h" +#include "document/UBDocumentController.h" + +#include "adaptors/publishing/UBDocumentPublisher.h" + +#include "core/memcheck.h" + +UBWebPublisher::UBWebPublisher(QObject *parent) + : UBExportAdaptor(parent) +{ + // NOOP +} + + +UBWebPublisher::~UBWebPublisher() +{ + // NOOP +} + + +QString UBWebPublisher::exportName() +{ + return tr("Publish Document on Sankore Web"); +} + + +void UBWebPublisher::persist(UBDocumentProxy* pDocumentProxy) +{ + if (!pDocumentProxy) + return; + + //UniboardSankoreTransition document; + QString documentPath(pDocumentProxy->persistencePath()); + //document.checkDocumentDirectory(documentPath); + + UBDocumentPublisher* publisher = new UBDocumentPublisher(pDocumentProxy, this); // the publisher will self delete when publication finishes + publisher->publish(); + +} + +bool UBWebPublisher::associatedActionactionAvailableFor(const QModelIndex &selectedIndex) +{ + const UBDocumentTreeModel *docModel = qobject_cast(selectedIndex.model()); + if (!selectedIndex.isValid() || docModel->isCatalog(selectedIndex)) { + return false; + } + + return true; +} + + diff --git a/src/adaptors/UBWebPublisher.h b/src/adaptors/UBWebPublisher.h new file mode 100644 index 00000000..d2ac5147 --- /dev/null +++ b/src/adaptors/UBWebPublisher.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA) + * + * This file is part of Open-Sankoré. + * + * Open-Sankoré is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License, + * with a specific linking exception for the OpenSSL project's + * "OpenSSL" library (or with modified versions of it that use the + * same license as the "OpenSSL" library). + * + * Open-Sankoré 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Open-Sankoré. If not, see . + */ + + + +#ifndef UBWEBPUBLISHER_H_ +#define UBWEBPUBLISHER_H_ + +#include + +#include "UBExportAdaptor.h" + +#include "frameworks/UBFileSystemUtils.h" +#include "ui_webPublishing.h" + +class UBDocumentProxy; +class UBServerXMLHttpRequest; + +class UBWebPublisher : public UBExportAdaptor +{ + Q_OBJECT + + public: + UBWebPublisher(QObject *parent = 0); + virtual ~UBWebPublisher(); + + virtual QString exportName(); + + virtual void persist(UBDocumentProxy* pDocument); + + virtual bool associatedActionactionAvailableFor(const QModelIndex &selectedIndex); +}; + + +#endif /* UBWEBPUBLISHER_H_ */ diff --git a/src/adaptors/adaptors.pri b/src/adaptors/adaptors.pri index e21ac2ba..ed1d0c60 100644 --- a/src/adaptors/adaptors.pri +++ b/src/adaptors/adaptors.pri @@ -12,7 +12,13 @@ HEADERS += src/adaptors/UBExportAdaptor.h\ src/adaptors/UBImportImage.h \ src/adaptors/UBExportWeb.h \ src/adaptors/publishing/UBDocumentPublisher.h \ - src/adaptors/publishing/UBSvgSubsetRasterizer.h + src/adaptors/publishing/UBSvgSubsetRasterizer.h \ + $$PWD/UBExportDocumentSetAdaptor.h \ + $$PWD/UBImportDocumentSetAdaptor.h \ + $$PWD/UBExportCFF.h \ + $$PWD/UBImportCFF.h \ + $$PWD/UBWebPublisher.h \ + $$PWD/UBCFFSubsetAdaptor.h SOURCES += src/adaptors/UBExportAdaptor.cpp\ @@ -28,4 +34,10 @@ SOURCES += src/adaptors/UBExportAdaptor.cpp\ src/adaptors/UBImportImage.cpp \ src/adaptors/UBExportWeb.cpp \ src/adaptors/publishing/UBDocumentPublisher.cpp\ - src/adaptors/publishing/UBSvgSubsetRasterizer.cpp + src/adaptors/publishing/UBSvgSubsetRasterizer.cpp \ + $$PWD/UBExportDocumentSetAdaptor.cpp \ + $$PWD/UBImportDocumentSetAdaptor.cpp \ + $$PWD/UBExportCFF.cpp \ + $$PWD/UBImportCFF.cpp \ + $$PWD/UBWebPublisher.cpp \ + $$PWD/UBCFFSubsetAdaptor.cpp diff --git a/src/adaptors/publishing/UBDocumentPublisher.cpp b/src/adaptors/publishing/UBDocumentPublisher.cpp index 991486b0..6ada645f 100644 --- a/src/adaptors/publishing/UBDocumentPublisher.cpp +++ b/src/adaptors/publishing/UBDocumentPublisher.cpp @@ -1,35 +1,669 @@ /* - * Copyright (C) 2015-2016 Département de l'Instruction Publique (DIP-SEM) + * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA) * - * Copyright (C) 2013 Open Education Foundation + * This file is part of Open-Sankoré. * - * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour - * l'Education Numérique en Afrique (GIP ENA) - * - * This file is part of OpenBoard. - * - * OpenBoard is free software: you can redistribute it and/or modify + * Open-Sankoré is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 of the License, * with a specific linking exception for the OpenSSL project's * "OpenSSL" library (or with modified versions of it that use the * same license as the "OpenSSL" library). * - * OpenBoard is distributed in the hope that it will be useful, + * Open-Sankoré 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 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with OpenBoard. If not, see . + * along with Open-Sankoré. If not, see . */ +#include -#include #include "UBDocumentPublisher.h" +#include "frameworks/UBPlatformUtils.h" +#include "frameworks/UBFileSystemUtils.h" +#include "frameworks/UBStringUtils.h" + +#include "network/UBNetworkAccessManager.h" +#include "network/UBServerXMLHttpRequest.h" + +#include "core/UBDocumentManager.h" +#include "core/UBApplication.h" +#include "core/UBPersistenceManager.h" +#include "core/UBApplicationController.h" + +#include "board/UBBoardController.h" + +#include "gui/UBMainWindow.h" + +#include "document/UBDocumentProxy.h" +#include "document/UBDocumentContainer.h" + +#include "domain/UBGraphicsWidgetItem.h" + +#include "globals/UBGlobals.h" + +THIRD_PARTY_WARNINGS_DISABLE +#include "quazip.h" +#include "quazipfile.h" +THIRD_PARTY_WARNINGS_ENABLE + +#include "adaptors/UBExportFullPDF.h" +#include "adaptors/UBExportDocument.h" +#include "adaptors/UBSvgSubsetAdaptor.h" + +#include "UBSvgSubsetRasterizer.h" + +#include "../../core/UBApplication.h" + +#include "core/memcheck.h" + + +UBDocumentPublisher::UBDocumentPublisher(UBDocumentProxy* pDocument, QObject *parent) + : QObject(parent) + , mSourceDocument(pDocument) + , mUsername("") + , mPassword("") + , bLoginCookieSet(false) +{ + init(); +} + + +UBDocumentPublisher::~UBDocumentPublisher() +{ +} + + +void UBDocumentPublisher::publish() +{ + //check that the username and password are stored on preferences + UBSettings* settings = UBSettings::settings(); + + if(settings->communityUsername().isEmpty() || settings->communityPassword().isEmpty()){ + UBApplication::showMessage(tr("Credentials has to not been filled out yet.")); + qDebug() << "trying to connect to community without the required credentials"; + return; + } + + mUsername = settings->communityUsername(); + mPassword = settings->communityPassword(); + + UBPublicationDlg dlg; + if(QDialog::Accepted == dlg.exec()) + { + mDocInfos.title = dlg.title(); + mDocInfos.description = dlg.description(); + + buildUbwFile(); + + UBApplication::showMessage(tr("Uploading Sankore File on Web.")); + + sendUbw(mUsername, mPassword); + } +} + +void UBDocumentPublisher::buildUbwFile() +{ + QDir d; + d.mkpath(UBFileSystemUtils::defaultTempDirPath()); + + QString tmpDir = UBFileSystemUtils::createTempDir(); + + if (UBFileSystemUtils::copyDir(mSourceDocument->persistencePath(), tmpDir)) + { + QString documentName = mSourceDocument->name(); + //remove all the directory separators from the document name. + //we do not want to interperete them as directory separator + documentName = documentName.replace("/",".").replace("\\","."); + documentName = documentName.replace(":","-"); + mPublishingPath = tmpDir; + mPublishingSize = mSourceDocument->pageCount(); + + rasterizeScenes(); + + upgradeDocumentForPublishing(); + + UBExportFullPDF pdfExporter; + pdfExporter.setVerbose(false); + pdfExporter.persistsDocument(mSourceDocument, mPublishingPath + "/" + documentName + ".pdf"); + + UBExportDocument ubzExporter; + ubzExporter.setVerbose(false); + ubzExporter.persistsDocument(mSourceDocument, mPublishingPath + "/" + documentName + ".ubz"); + + // remove all useless files + + for (int pageIndex = 0; pageIndex < mPublishingSize; pageIndex++) { + QString filename = mPublishingPath + UBFileSystemUtils::digitFileFormat("/page%1.svg",pageIndex); + + QFile::remove(filename); + } + + UBFileSystemUtils::deleteDir(mPublishingPath + "/" + UBPersistenceManager::imageDirectory); + UBFileSystemUtils::deleteDir(mPublishingPath + "/" + UBPersistenceManager::objectDirectory); + UBFileSystemUtils::deleteDir(mPublishingPath + "/" + UBPersistenceManager::videoDirectory); + UBFileSystemUtils::deleteDir(mPublishingPath + "/" + UBPersistenceManager::audioDirectory); + + mTmpZipFile = UBFileSystemUtils::defaultTempDirPath() + "/" + UBStringUtils::toCanonicalUuid(QUuid::createUuid()) + ".ubw"; + + QuaZip zip(mTmpZipFile); + zip.setFileNameCodec("UTF-8"); + if (!zip.open(QuaZip::mdCreate)) + { + qWarning() << "Export failed. Cause: zip.open(): " << zip.getZipError() << "," << mTmpZipFile; + QApplication::restoreOverrideCursor(); + return; + } + + QuaZipFile outFile(&zip); + + if (!UBFileSystemUtils::compressDirInZip(mPublishingPath, "", &outFile, true)) + { + qWarning("Export failed. compressDirInZip failed ..."); + zip.close(); + UBApplication::showMessage(tr("Export failed.")); + QApplication::restoreOverrideCursor(); + return; + } + + if (zip.getZipError() != 0) + { + qWarning("Export failed. Cause: zip.close(): %d", zip.getZipError()); + zip.close(); + UBApplication::showMessage(tr("Export failed.")); + QApplication::restoreOverrideCursor(); + return; + } + + zip.close(); + + } + else + { + UBApplication::showMessage(tr("Export canceled ...")); + QApplication::restoreOverrideCursor(); + } +} + +void UBDocumentPublisher::rasterizeScenes() +{ + + for (int pageIndex = 0; pageIndex < mPublishingSize; pageIndex++) + { + UBApplication::showMessage(tr("Converting page %1/%2 ...").arg(UBDocumentContainer::pageFromSceneIndex(pageIndex)).arg(mPublishingSize), true); + + UBDocumentProxy publishingDocument(mPublishingPath); + UBSvgSubsetRasterizer rasterizer(&publishingDocument, pageIndex); + + QString filename = mPublishingPath + UBFileSystemUtils::digitFileFormat("/page%1.jpg",pageIndex); + + rasterizer.rasterizeToFile(filename); + + } +} + + +void UBDocumentPublisher::updateGoogleMapApiKey() +{ + /* + QDir widgestDir(mPublishingPath + "/" + UBPersistenceManager::widgetDirectory); + + QString uniboardWebGoogleMapApiKey = UBSettings::settings()->uniboardWebGoogleMapApiKey->get().toString(); + + foreach(QFileInfo dirInfo, widgestDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) + { + QString config = UBFileSystemUtils::readTextFile(dirInfo.absoluteFilePath() + "/config.xml").toLower(); + + if (config.contains("google") && config.contains("map")) + { + QDir widgetDir(dirInfo.absoluteFilePath()); + + foreach(QFileInfo fileInfo, widgetDir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot)) + { + QFile file(fileInfo.absoluteFilePath()); + + if (file.open(QIODevice::ReadWrite)) + { + QTextStream stream(&file); + QString content = stream.readAll(); + + if (content.contains("ABQIAAAA6vtVqAUu8kZ_eTz7c8kwSBT9UCAhw_xm0LNFHsWmQxTJAdp5lxSY_5r-lZriY_7sACaMnl80JcX6Og")) + { + content.replace("ABQIAAAA6vtVqAUu8kZ_eTz7c8kwSBT9UCAhw_xm0LNFHsWmQxTJAdp5lxSY_5r-lZriY_7sACaMnl80JcX6Og", + uniboardWebGoogleMapApiKey); + + file.resize(0); + file.write(content.toUtf8()); + } + file.close(); + } + } + } + } + */ +} + + +void UBDocumentPublisher::upgradeDocumentForPublishing() +{ + for (int pageIndex = 0; pageIndex < mPublishingSize; pageIndex++) + { + UBDocumentProxy publishingDocument(mPublishingPath); + UBGraphicsScene *scene = UBSvgSubsetAdaptor::loadScene(&publishingDocument, pageIndex); + + QList widgets; + + foreach(QGraphicsItem* item, scene->items()){ + UBGraphicsW3CWidgetItem *widgetItem = dynamic_cast(item); + + if(widgetItem){ + generateWidgetPropertyScript(widgetItem, UBDocumentContainer::pageFromSceneIndex(pageIndex)); + widgets << widgetItem; + } + } + + QString filename = mPublishingPath + UBFileSystemUtils::digitFileFormat("/page%1.json",pageIndex); + + QFile jsonFile(filename); + if (jsonFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) + { + jsonFile.write("{\n"); + jsonFile.write(QString(" \"scene\": {\n").toUtf8()); + jsonFile.write(QString(" \"x\": %1,\n").arg(scene->normalizedSceneRect().x()).toUtf8()); + jsonFile.write(QString(" \"y\": %1,\n").arg(scene->normalizedSceneRect().y()).toUtf8()); + jsonFile.write(QString(" \"width\": %1,\n").arg(scene->normalizedSceneRect().width()).toUtf8()); + jsonFile.write(QString(" \"height\": %1\n").arg(scene->normalizedSceneRect().height()).toUtf8()); + jsonFile.write(QString(" },\n").toUtf8()); + + jsonFile.write(QString(" \"widgets\": [\n").toUtf8()); + + bool first = true; + + foreach(UBGraphicsW3CWidgetItem* widget, widgets) + { + if (!first) + jsonFile.write(QString(" ,\n").toUtf8()); + jsonFile.write(QString(" {\n").toUtf8()); + jsonFile.write(QString(" \"uuid\": \"%1\",\n").arg(UBStringUtils::toCanonicalUuid(widget->uuid())).toUtf8()); + jsonFile.write(QString(" \"id\": \"%1\",\n").arg(widget->metadatas().id).toUtf8()); + + jsonFile.write(QString(" \"name\": \"%1\",\n").arg(widget->metadatas().name).toUtf8()); + jsonFile.write(QString(" \"description\": \"%1\",\n").arg(widget->metadatas().description).toUtf8()); + jsonFile.write(QString(" \"author\": \"%1\",\n").arg(widget->metadatas().author).toUtf8()); + jsonFile.write(QString(" \"authorEmail\": \"%1\",\n").arg(widget->metadatas().authorEmail).toUtf8()); + jsonFile.write(QString(" \"authorHref\": \"%1\",\n").arg(widget->metadatas().authorHref).toUtf8()); + jsonFile.write(QString(" \"version\": \"%1\",\n").arg(widget->metadatas().authorHref).toUtf8()); + + jsonFile.write(QString(" \"x\": %1,\n").arg(widget->sceneBoundingRect().x()).toUtf8()); + jsonFile.write(QString(" \"y\": %1,\n").arg(widget->sceneBoundingRect().y()).toUtf8()); + jsonFile.write(QString(" \"width\": %1,\n").arg(widget->sceneBoundingRect().width()).toUtf8()); + jsonFile.write(QString(" \"height\": %1,\n").arg(widget->sceneBoundingRect().height()).toUtf8()); + + jsonFile.write(QString(" \"nominalWidth\": %1,\n").arg(widget->boundingRect().width()).toUtf8()); + jsonFile.write(QString(" \"nominalHeight\": %1,\n").arg(widget->boundingRect().height()).toUtf8()); + + QString url = UBPersistenceManager::widgetDirectory + "/" + widget->uuid().toString() + ".wgt"; + jsonFile.write(QString(" \"src\": \"%1\",\n").arg(url).toUtf8()); + QString startFile = widget->mainHtmlFileName(); + jsonFile.write(QString(" \"startFile\": \"%1\",\n").arg(startFile).toUtf8()); + + QMap preferences = widget->UBGraphicsWidgetItem::preferences(); + + jsonFile.write(QString(" \"preferences\": {\n").toUtf8()); + + foreach(QString key, preferences.keys()) + { + QString sep = ","; + if (key == preferences.keys().last()) + sep = ""; + + jsonFile.write(QString(" \"%1\": \"%2\"%3\n") + .arg(key) + .arg(preferences.value(key)) + .arg(sep) + .toUtf8()); + } + jsonFile.write(QString(" },\n").toUtf8()); + + jsonFile.write(QString(" \"datastore\": {\n").toUtf8()); + + QMap datastoreEntries = widget->datastoreEntries(); + + foreach(QString entry, datastoreEntries.keys()) + { + QString sep = ","; + if (entry == datastoreEntries.keys().last()) + sep = ""; + + jsonFile.write(QString(" \"%1\": \"%2\"%3\n") + .arg(entry) + .arg(datastoreEntries.value(entry)) + .arg(sep) + .toUtf8()); + } + jsonFile.write(QString(" }\n").toUtf8()); + + jsonFile.write(QString(" }\n").toUtf8()); + + first = false; + } + + jsonFile.write(" ]\n"); + jsonFile.write("}\n"); + } + else + { + qWarning() << "Cannot open file" << filename << "for saving page state"; + } + + delete scene; + } + + updateGoogleMapApiKey(); +} + + +void UBDocumentPublisher::generateWidgetPropertyScript(UBGraphicsW3CWidgetItem *widgetItem, int pageNumber) +{ + + QMap preferences = widgetItem->UBGraphicsWidgetItem::preferences(); + QMap datastoreEntries = widgetItem->datastoreEntries(); + + QString startFileName = widgetItem->mainHtmlFileName(); + + if (!startFileName.startsWith("http://")) + { + QString startFilePath = mPublishingPath + "/" + UBPersistenceManager::widgetDirectory + "/" + widgetItem->uuid().toString() + ".wgt/" + startFileName; + + QFile startFile(startFilePath); + + if (startFile.exists()) + { + if (startFile.open(QIODevice::ReadWrite)) + { + QTextStream stream(&startFile); + QStringList lines; + + bool addedJs = false; + + QString line; + do + { + line = stream.readLine(); + if (!line.isNull()) + { + lines << line; + + if (!addedJs && line.contains("") ) // TODO UB 4.6, this is naive ... the HEAD tag may be on several lines + { + lines << ""; + lines << " "; + lines << ""; + + addedJs = true; + } + } + } + while (!line.isNull()); + + startFile.resize(0); + startFile.write(lines.join("\n").toUtf8()); // TODO UB 4.x detect real html encoding + + startFile.close(); + } + } + } + else{ + qWarning() << "Remote Widget start file, cannot inject widget preferences and datastore entries"; + } +} + +void UBDocumentPublisher::init() +{ + mCrlf=0x0d; + mCrlf+=0x0a; + mDocInfos.title = ""; + mDocInfos.description = ""; + + mpCookieJar = new QNetworkCookieJar(); + mpNetworkMgr = new QNetworkAccessManager(this); + + connect(mpNetworkMgr, SIGNAL(finished(QNetworkReply*)), this, SLOT(onFinished(QNetworkReply*))); +} + +void UBDocumentPublisher::onFinished(QNetworkReply *reply) +{ + QVariant cookieHeader = reply->rawHeader("Set-Cookie"); + // First we concatenate all the Set-Cookie values (the packet can contains many of them) + QStringList qslCookie = cookieHeader.toString().split("\n"); + QString qsCookieValue = qslCookie.at(0); + for (int i = 1; i < qslCookie.size(); i++) { + qsCookieValue += "; " +qslCookie.at(i); + } + + // Now we isolate every cookie value + QStringList qslCookieVals = qsCookieValue.split("; "); + + bool bTransferOk = false; + + for(int j = 0; j < qslCookieVals.size(); j++) + { + qDebug() << j << qslCookieVals.at(j); + if(qslCookieVals.at(j).startsWith("assetStatus")) + { + QStringList qslAsset = qslCookieVals.at(j).split("="); + if(qslAsset.at(1) == "UPLOADED") + { + bTransferOk = true; + break; + } + } + } + + if(bTransferOk) + { + UBApplication::showMessage(tr("Document uploaded correctly on the web.")); + } + else + { + UBApplication::showMessage(tr("Failed to upload document on the web.")); + } + + reply->deleteLater(); +} + +void UBDocumentPublisher::sendUbw(QString username, QString password) +{ + if (QFile::exists(mTmpZipFile)) + { + QFile f(mTmpZipFile); + if (f.open(QIODevice::ReadOnly)) + { + QFileInfo fi(f); + QByteArray ba = f.readAll(); + QString boundary,data, multipartHeader; + QByteArray datatoSend; + + boundary = "---WebKitFormBoundaryDKBTgA53MiyWrzLY"; + multipartHeader = "multipart/form-data; boundary="+boundary; + + data="--"+boundary+mCrlf; + data+="Content-Disposition: form-data; name=\"title\"" + mCrlf + mCrlf + mDocInfos.title + mCrlf; + data+="--"+boundary+mCrlf; + data+="Content-Disposition: form-data; name=\"description\"" + mCrlf + mCrlf + mDocInfos.description.remove("\n") + mCrlf; + data+="--"+boundary+mCrlf; + data+="Content-Disposition: form-data; name=\"file\"; filename=\""+ fi.fileName() +"\""+mCrlf; + data+="Content-Type: application/octet-stream"+mCrlf+mCrlf; + datatoSend=data.toLatin1(); // convert data string to byte array for request + datatoSend += ba; + datatoSend += mCrlf; + datatoSend += QString("--%0--%1").arg(boundary).arg(mCrlf); + + QNetworkRequest request(QUrl(QString(DOCPUBLICATION_URL).toLatin1().constData())); + + request.setHeader(QNetworkRequest::ContentTypeHeader, multipartHeader); + request.setHeader(QNetworkRequest::ContentLengthHeader,datatoSend.size()); + QString b64Auth = getBase64Of(QString("%0:%1").arg(username).arg(password)); + request.setRawHeader("Authorization", QString("Basic %0").arg(b64Auth).toLatin1().constData()); + request.setRawHeader("Host", "planete.sankore.org"); + request.setRawHeader("Accept", "*/*"); + request.setRawHeader("Accept-Language", "en-US,*"); + + mpCookieJar->setCookiesFromUrl(mCookies, QUrl(DOCPUBLICATION_URL)); + mpNetworkMgr->setCookieJar(mpCookieJar); + + // Send the file + mpNetworkMgr->post(request,datatoSend); + } + } +} + +QString UBDocumentPublisher::getBase64Of(QString stringToEncode) +{ + return stringToEncode.toLatin1().toBase64(); +} + +// --------------------------------------------------------- UBProxyLoginDlg::UBProxyLoginDlg(QWidget *parent, const char *name):QDialog(parent) , mpLayout(NULL) , mpUserLayout(NULL) diff --git a/src/adaptors/publishing/UBDocumentPublisher.h b/src/adaptors/publishing/UBDocumentPublisher.h index 7ee2be65..89bf4a35 100644 --- a/src/adaptors/publishing/UBDocumentPublisher.h +++ b/src/adaptors/publishing/UBDocumentPublisher.h @@ -1,44 +1,34 @@ /* - * Copyright (C) 2015-2016 Département de l'Instruction Publique (DIP-SEM) + * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA) * - * Copyright (C) 2013 Open Education Foundation + * This file is part of Open-Sankoré. * - * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour - * l'Education Numérique en Afrique (GIP ENA) - * - * This file is part of OpenBoard. - * - * OpenBoard is free software: you can redistribute it and/or modify + * Open-Sankoré is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 of the License, * with a specific linking exception for the OpenSSL project's * "OpenSSL" library (or with modified versions of it that use the * same license as the "OpenSSL" library). * - * OpenBoard is distributed in the hope that it will be useful, + * Open-Sankoré 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 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with OpenBoard. If not, see . + * along with Open-Sankoré. If not, see . */ - - #ifndef UBDOCUMENTPUBLISHER_H #define UBDOCUMENTPUBLISHER_H #include #include -#include -#include -#include -#include -#include -#include -#include + +#include "ui_webPublishing.h" + +#define DOCPUBLICATION_URL "http://planete.sankore.org/xwiki/bin/view/CreateResources/UniboardUpload?xpage=plain&outputSyntax=plain" typedef struct { @@ -94,4 +84,58 @@ private: QTextEdit* mpDescription; QDialogButtonBox* mpButtons; }; + + +class UBDocumentPublisher : public QObject +{ + Q_OBJECT; + +public: + explicit UBDocumentPublisher(UBDocumentProxy* sourceDocument, QObject *parent = 0); + virtual ~UBDocumentPublisher(); + + void publish(); + +signals: + + void loginDone(); + +protected: + + virtual void updateGoogleMapApiKey(); + virtual void rasterizeScenes(); + virtual void upgradeDocumentForPublishing(); + virtual void generateWidgetPropertyScript(UBGraphicsW3CWidgetItem *widgetItem, int pageNumber); + +private slots: + + void onFinished(QNetworkReply* reply); + +private: + + UBDocumentProxy *mSourceDocument; + + //UBDocumentProxy *mPublishingDocument; + QString mPublishingPath; + int mPublishingSize; + + + void init(); + void sendUbw(QString username, QString password); + QString getBase64Of(QString stringToEncode); + + QHBoxLayout* mpLayout; + QNetworkAccessManager* mpNetworkMgr; + QNetworkCookieJar* mpCookieJar; + QString mUsername; + QString mPassword; + QString mCrlf; + bool bLoginCookieSet; + + void buildUbwFile(); + QString mTmpZipFile; + QList mCookies; + sDocumentInfos mDocInfos; + +}; #endif // UBDOCUMENTPUBLISHER_H diff --git a/src/adaptors/publishing/UBSvgSubsetRasterizer.cpp b/src/adaptors/publishing/UBSvgSubsetRasterizer.cpp index b0ca3d4e..458eb1c9 100644 --- a/src/adaptors/publishing/UBSvgSubsetRasterizer.cpp +++ b/src/adaptors/publishing/UBSvgSubsetRasterizer.cpp @@ -1,32 +1,26 @@ /* - * Copyright (C) 2015-2016 Département de l'Instruction Publique (DIP-SEM) + * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA) * - * Copyright (C) 2013 Open Education Foundation + * This file is part of Open-Sankoré. * - * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour - * l'Education Numérique en Afrique (GIP ENA) - * - * This file is part of OpenBoard. - * - * OpenBoard is free software: you can redistribute it and/or modify + * Open-Sankoré is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 of the License, * with a specific linking exception for the OpenSSL project's * "OpenSSL" library (or with modified versions of it that use the * same license as the "OpenSSL" library). * - * OpenBoard is distributed in the hope that it will be useful, + * Open-Sankoré 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 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with OpenBoard. If not, see . + * along with Open-Sankoré. If not, see . */ - #include "UBSvgSubsetRasterizer.h" #include "domain/UBGraphicsScene.h" diff --git a/src/adaptors/publishing/UBSvgSubsetRasterizer.h b/src/adaptors/publishing/UBSvgSubsetRasterizer.h index dcbe1e50..fcc40cff 100644 --- a/src/adaptors/publishing/UBSvgSubsetRasterizer.h +++ b/src/adaptors/publishing/UBSvgSubsetRasterizer.h @@ -1,32 +1,26 @@ /* - * Copyright (C) 2015-2016 Département de l'Instruction Publique (DIP-SEM) + * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA) * - * Copyright (C) 2013 Open Education Foundation + * This file is part of Open-Sankoré. * - * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour - * l'Education Numérique en Afrique (GIP ENA) - * - * This file is part of OpenBoard. - * - * OpenBoard is free software: you can redistribute it and/or modify + * Open-Sankoré is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 of the License, * with a specific linking exception for the OpenSSL project's * "OpenSSL" library (or with modified versions of it that use the * same license as the "OpenSSL" library). * - * OpenBoard is distributed in the hope that it will be useful, + * Open-Sankoré 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 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with OpenBoard. If not, see . + * along with Open-Sankoré. If not, see . */ - #ifndef UBSVGSUBSETRASTERIZER_H_ #define UBSVGSUBSETRASTERIZER_H_ diff --git a/src/board/UBBoardController.cpp b/src/board/UBBoardController.cpp index e9ba86c1..a93a5c07 100644 --- a/src/board/UBBoardController.cpp +++ b/src/board/UBBoardController.cpp @@ -854,7 +854,6 @@ void UBBoardController::blackout() UBApplication::applicationController->blackout(); } - void UBBoardController::showKeyboard(bool show) { if(show) @@ -1993,7 +1992,7 @@ void UBBoardController::persistCurrentScene(bool isAnAutomaticBackup, bool force && (mActiveSceneIndex >= 0) && mActiveSceneIndex != mMovingSceneIndex && (mActiveScene->isModified())) { - UBPersistenceManager::persistenceManager()->persistDocumentScene(selectedDocument(), mActiveScene, mActiveSceneIndex, isAnAutomaticBackup,forceImmediateSave); + UBPersistenceManager::persistenceManager()->persistDocumentScene(selectedDocument(), mActiveScene, mActiveSceneIndex); updatePage(mActiveSceneIndex); } } diff --git a/src/board/UBBoardController.h b/src/board/UBBoardController.h index cc674ccc..513385f1 100644 --- a/src/board/UBBoardController.h +++ b/src/board/UBBoardController.h @@ -253,6 +253,8 @@ class UBBoardController : public UBDocumentContainer void saveData(SaveFlags fls = sf_none); + //void regenerateThumbnails(); + signals: void newPageAdded(); void activeSceneChanged(); diff --git a/src/core/UBApplicationController.cpp b/src/core/UBApplicationController.cpp index f3a6e199..fe9c8280 100644 --- a/src/core/UBApplicationController.cpp +++ b/src/core/UBApplicationController.cpp @@ -635,6 +635,7 @@ void UBApplicationController::mirroringEnabled(bool enabled) } + void UBApplicationController::closing() { if (mMirror) @@ -642,12 +643,18 @@ void UBApplicationController::closing() if (mUninoteController) { + mUninoteController->hideWindow(); mUninoteController->close(); } + /* + if (UBApplication::documentController) UBApplication::documentController->closing(); + */ + + UBPersistenceManager::persistenceManager()->closing(); // ALTI/AOU - 20140616 : to update the file "documents/folders.xml" } diff --git a/src/core/UBDocumentManager.cpp b/src/core/UBDocumentManager.cpp index 6e95158c..114ec9b4 100644 --- a/src/core/UBDocumentManager.cpp +++ b/src/core/UBDocumentManager.cpp @@ -1,32 +1,26 @@ /* - * Copyright (C) 2015-2016 Département de l'Instruction Publique (DIP-SEM) + * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA) * - * Copyright (C) 2013 Open Education Foundation + * This file is part of Open-Sankoré. * - * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour - * l'Education Numérique en Afrique (GIP ENA) - * - * This file is part of OpenBoard. - * - * OpenBoard is free software: you can redistribute it and/or modify + * Open-Sankoré is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 of the License, * with a specific linking exception for the OpenSSL project's * "OpenSSL" library (or with modified versions of it that use the * same license as the "OpenSSL" library). * - * OpenBoard is distributed in the hope that it will be useful, + * Open-Sankoré 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 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with OpenBoard. If not, see . + * along with Open-Sankoré. If not, see . */ - #include "UBDocumentManager.h" #include "frameworks/UBStringUtils.h" @@ -34,9 +28,14 @@ #include "adaptors/UBExportFullPDF.h" #include "adaptors/UBExportDocument.h" #include "adaptors/UBExportWeb.h" +#include "adaptors/UBExportCFF.h" +#include "adaptors/UBExportDocumentSetAdaptor.h" +#include "adaptors/UBWebPublisher.h" #include "adaptors/UBImportDocument.h" #include "adaptors/UBImportPDF.h" #include "adaptors/UBImportImage.h" +#include "adaptors/UBImportCFF.h" +#include "adaptors/UBImportDocumentSetAdaptor.h" #include "domain/UBGraphicsScene.h" #include "domain/UBGraphicsSvgItem.h" @@ -50,6 +49,8 @@ #include "UBSettings.h" #include "UBPersistenceManager.h" +#include "../adaptors/UBExportWeb.h" + #include "core/memcheck.h" UBDocumentManager* UBDocumentManager::sDocumentManager = 0; @@ -73,17 +74,30 @@ UBDocumentManager::UBDocumentManager(QObject *parent) QString dummyObjects = tr("objects"); QString dummyWidgets = tr("widgets"); + UBExportCFF* cffExporter = new UBExportCFF(this); UBExportFullPDF* exportFullPdf = new UBExportFullPDF(this); UBExportDocument* exportDocument = new UBExportDocument(this); + UBWebPublisher* webPublished = new UBWebPublisher(this); + UBExportDocumentSetAdaptor *exportDocumentSet = new UBExportDocumentSetAdaptor(this); mExportAdaptors.append(exportDocument); + mExportAdaptors.append(exportDocumentSet); + mExportAdaptors.append(webPublished); mExportAdaptors.append(exportFullPdf); + mExportAdaptors.append(cffExporter); + +// UBExportWeb* exportWeb = new UBExportWeb(this); +// mExportAdaptors.append(exportWeb); UBImportDocument* documentImport = new UBImportDocument(this); mImportAdaptors.append(documentImport); + UBImportDocumentSetAdaptor *documentSetImport = new UBImportDocumentSetAdaptor(this); + mImportAdaptors.append(documentSetImport); UBImportPDF* pdfImport = new UBImportPDF(this); mImportAdaptors.append(pdfImport); UBImportImage* imageImport = new UBImportImage(this); mImportAdaptors.append(imageImport); + UBImportCFF* cffImport = new UBImportCFF(this); + mImportAdaptors.append(cffImport); } @@ -93,38 +107,60 @@ UBDocumentManager::~UBDocumentManager() } -QStringList UBDocumentManager::importFileExtensions() +QStringList UBDocumentManager::importFileExtensions(bool notUbx) { QStringList result; foreach (UBImportAdaptor *importAdaptor, mImportAdaptors) { - result << importAdaptor->supportedExtentions(); + //issue 1629 - NNE - 20131213 : add test to remove ubx extention if necessary + if(!(notUbx && importAdaptor->supportedExtentions().at(0) == "ubx")){ + result << importAdaptor->supportedExtentions(); + } } return result; } -QString UBDocumentManager::importFileFilter() +QString UBDocumentManager::importFileFilter(bool notUbx) { QString result; - result += tr("All supported files (*.%1)").arg(importFileExtensions().join(" *.")); + result += tr("All supported files (*.%1)").arg(importFileExtensions(notUbx).join(" *.")); foreach (UBImportAdaptor *importAdaptor, mImportAdaptors) { if (importAdaptor->importFileFilter().length() > 0) { - if (result.length()) - { - result += ";;"; + //issue 1629 - NNE - 20131213 : Add a test on ubx before put in the list + if(!(notUbx && importAdaptor->supportedExtentions().at(0) == "ubx")){ + if (result.length()) + { + result += ";;"; + } + + result += importAdaptor->importFileFilter(); } - result += importAdaptor->importFileFilter(); } } qDebug() << "import file filter" << result; return result; } +QFileInfoList UBDocumentManager::importUbx(const QString &Incomingfile, const QString &destination) +{ + UBImportDocumentSetAdaptor *docSetAdaptor; + foreach (UBImportAdaptor *curAdaptor, mImportAdaptors) { + docSetAdaptor = qobject_cast(curAdaptor); + if (docSetAdaptor) { + break; + } + } + if (!docSetAdaptor) { + return QFileInfoList(); + } + + return docSetAdaptor->importData(Incomingfile, destination); +} UBDocumentProxy* UBDocumentManager::importFile(const QFile& pFile, const QString& pGroup) { @@ -150,7 +186,12 @@ UBDocumentProxy* UBDocumentManager::importFile(const QFile& pFile, const QString // Document import procedure..... QString documentName = QFileInfo(pFile.fileName()).completeBaseName(); - document = UBPersistenceManager::persistenceManager()->createDocument(pGroup, documentName,false); + document = UBPersistenceManager::persistenceManager()->createDocument(pGroup + ,documentName + , false // Issue 1630 - CFA - 201410503 - suppression de la page vide ajoutee à l'import des pdfs + , QString() + , 0 + , true); QUuid uuid = QUuid::createUuid(); QString filepath = pFile.fileName(); @@ -159,7 +200,6 @@ UBDocumentProxy* UBDocumentManager::importFile(const QFile& pFile, const QString bool b = UBPersistenceManager::persistenceManager()->addFileToDocument(document, pFile.fileName(), importAdaptor->folderToCopy() , uuid, filepath); if (!b) { - UBPersistenceManager::persistenceManager()->deleteDocument(document); UBApplication::setDisabled(false); return NULL; } @@ -169,8 +209,9 @@ UBDocumentProxy* UBDocumentManager::importFile(const QFile& pFile, const QString int nPage = 0; foreach(UBGraphicsItem* page, pages) { + UBApplication::showMessage(tr("Inserting page %1 of %2").arg(++nPage).arg(pages.size()), true); -#ifdef Q_OS_OSX +#ifdef Q_WS_MACX //Workaround for issue 912 QApplication::processEvents(); #endif @@ -211,9 +252,11 @@ int UBDocumentManager::addFilesToDocument(UBDocumentProxy* document, QStringList if (adaptor->isDocumentBased()) { - UBDocumentBasedImportAdaptor* importAdaptor = (UBDocumentBasedImportAdaptor*)adaptor; + //issue 1629 - NNE - 20131212 : Resolve a segfault, but for .ubx, actually + //the file will be not imported... + UBDocumentBasedImportAdaptor* importAdaptor = dynamic_cast(adaptor); - if (importAdaptor->addFileToDocument(document, file)) + if (importAdaptor && importAdaptor->addFileToDocument(document, file)) nImportedDocuments++; } else @@ -240,7 +283,7 @@ int UBDocumentManager::addFilesToDocument(UBDocumentProxy* document, QStringList UBGraphicsScene* scene = UBPersistenceManager::persistenceManager()->createDocumentSceneAt(document, pageIndex); importAdaptor->placeImportedItemToScene(scene, page); UBPersistenceManager::persistenceManager()->persistDocumentScene(document, scene, pageIndex); - UBApplication::boardController->addEmptyThumbPage(); + UBApplication::boardController->insertThumbPage(pageIndex); } UBPersistenceManager::persistenceManager()->persistDocumentMetadata(document); diff --git a/src/core/UBDocumentManager.h b/src/core/UBDocumentManager.h index dd598e04..43f506f6 100644 --- a/src/core/UBDocumentManager.h +++ b/src/core/UBDocumentManager.h @@ -45,9 +45,12 @@ class UBDocumentManager : public QObject static UBDocumentManager* documentManager(); virtual ~UBDocumentManager(); - QString importFileFilter(); - QStringList importFileExtensions(); + QString importFileFilter(bool notUbx = false); + + QStringList importFileExtensions(bool notUbx = false); + + QFileInfoList importUbx(const QString &Incomingfile, const QString &destination); UBDocumentProxy* importFile(const QFile& pFile, const QString& pGroup); int addFilesToDocument(UBDocumentProxy* pDocument, QStringList fileNames); diff --git a/src/core/UBForeignObjectsHandler.cpp b/src/core/UBForeignObjectsHandler.cpp new file mode 100644 index 00000000..71620a23 --- /dev/null +++ b/src/core/UBForeignObjectsHandler.cpp @@ -0,0 +1,628 @@ +/* +* Copyright (C) 2010-2013 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA) +* +* This file is part of Open-Sankoré. +* +* Open-Sankoré is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, version 3 of the License, +* with a specific linking exception for the OpenSSL project's +* "OpenSSL" library (or with modified versions of it that use the +* same license as the "OpenSSL" library). +* +* Open-Sankoré 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with Open-Sankoré. If not, see . +*/ + +#include "UBForeignObjectsHandler.h" + +#include +#include +#include "UBSettings.h" + +const QString tVideo = "video"; +const QString tAudio = "audio"; +const QString tImage = "image"; +const QString tForeignObject = "foreignObject"; +const QString tTeacherGuide = "teacherGuide"; +const QString tMedia = "media"; +const QString tGroups = "groups"; + +const QString aHref = "xlink:href"; +const QString aType = "ub:type"; +const QString aReqExt = "requiredExtensions"; +const QString aSrc = "ub:src"; +const QString aMediaType = "mediaType"; +const QString aRelativePath = "relativePath"; +const QString aActionMedia = "ub:actionFirstParameter"; + +const QString vText = "text"; +const QString vReqExt = "http://ns.adobe.com/pdf/1.3/"; + +const QString wgtSuff = ".wgt"; +const QString thumbSuff = ".png"; + +const QString scanDirs = "audios,images,videos,teacherGuideObjects,widgets"; +const QStringList trashFilter = QStringList() << "*.swf"; + +static QString strIdFrom(const QString &filePath) +{ + if ((filePath).isEmpty()) { + return QString(); + } + + QRegExp rx("\\{.(?!.*\\{).*\\}"); + if (rx.indexIn(filePath) == -1) { + return QString(); + } + + return rx.cap(); +} + +static bool rm_r(const QString &rmPath) +{ + QFileInfo fi(rmPath); + if (!fi.exists()) { + qDebug() << rmPath << "does not exist"; + return false; + } else if (fi.isFile()) { + if (!QFile::remove(rmPath)) { + qDebug() << "can't remove file" << rmPath; + return false; + } + return true; + } else if (fi.isDir()) { + QFileInfoList fList = QDir(rmPath).entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot); + foreach (QFileInfo sub, fList) { + rm_r(sub.absoluteFilePath()); + } + if (!QDir().rmdir(rmPath)) { + qDebug() << "can't remove dir" << rmPath; + return false; + } + return true; + } + return false; +} + +static bool cp_rf(const QString &what, const QString &where) +{ + QFileInfo whatFi(what); + QFileInfo whereFi = QFileInfo(where); + + if (!whatFi.exists()) { + qDebug() << what << "does not exist" << Q_FUNC_INFO; + return false; + } else if (whatFi.isFile()) { + QString whereDir = where.section("/", 0, -2, QString::SectionSkipEmpty | QString::SectionIncludeLeadingSep); + QString newFilePath = where; + if (!whereFi.exists()) { + QDir().mkpath(whereDir); + } else if (whereFi.isDir()) { + newFilePath = whereDir + "/" + whatFi.fileName(); + } + if (QFile::exists(newFilePath)) { + QFile::remove(newFilePath); + } + if (!QFile::copy(what, newFilePath)) { + qDebug() << "can't copy" << what << "to" << where << Q_FUNC_INFO; + return false; + } + return true; + } else if (whatFi.isDir()) { + + if (whereFi.isFile() && whereFi.fileName().toLower() == whatFi.fileName().toLower()) { + qDebug() << "can't copy dir" << what << "to file" << where << Q_FUNC_INFO; + return false; + } else if (whereFi.isDir()) { + rm_r(where); + } + + QDir().mkpath(where); + + QFileInfoList fList = QDir(what).entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot); + foreach (QFileInfo sub, fList) { + if (!cp_rf(sub.absoluteFilePath(), where + "/" + sub.fileName())) + return false; + } + return true; + } + return true; +} + +static QString thumbFileNameFrom(const QString &filePath) +{ + if (filePath.isEmpty()) { + return QString(); + } + + QString thumbPath = filePath; + thumbPath.replace(QRegExp("[\\{\\}]"), "").replace(wgtSuff, thumbSuff); + + return thumbPath; +} + + +QString svgPageName(int pageIndex) +{ + return QString("page%1.svg").arg(pageIndex, 3, 10, QLatin1Char('0')); +} + +static QDomDocument createDomFromSvg(const QString &svgUrl) +{ + Q_ASSERT(QFile::exists(svgUrl)); + QString mFoldersXmlStorageName = svgUrl; + + if (QFileInfo(mFoldersXmlStorageName).exists()) { + QDomDocument xmlDom; + QFile inFile(mFoldersXmlStorageName); + if (inFile.open(QIODevice::ReadOnly)) { + QString domString(inFile.readAll()); + + int errorLine = 0; int errorColumn = 0; + QString errorStr; + + if (xmlDom.setContent(domString, &errorStr, &errorLine, &errorColumn)) { + return xmlDom; + } else { + qDebug() << "Error reading content of " << mFoldersXmlStorageName << endl + << "Error:" << inFile.errorString() + << "Line:" << errorLine + << "Column:" << errorColumn; + } + inFile.close(); + } else { + qDebug() << "Error reading" << mFoldersXmlStorageName << endl + << "Error:" << inFile.errorString(); + } + } + + return QDomDocument(); +} + +class Cleaner +{ +public: + void cure(const QUrl &dir) + { + mCurrentDir = dir.toLocalFile(); + cleanTrash(); + + // Gathering information from svg files + QFileInfoList svgInfos = QDir(mCurrentDir).entryInfoList(QStringList() << "*.svg", QDir::NoDotAndDotDot | QDir::Files); + foreach (QFileInfo svgInfo, svgInfos) { + cureIdsFromSvgDom(createDomFromSvg(svgInfo.absoluteFilePath())); + } + + fitIdsFromFileSystem(); + QVector deleteCandidates; + findRedundandElements(deleteCandidates); + + foreach (QString key, deleteCandidates) { + QString delPath = mPresentIdsMap.value(key); + if (delPath.isNull()) { + continue; + } else if (delPath.endsWith(wgtSuff)) { //remove corresponding thumb + QString thumbPath = thumbFileNameFrom(delPath); + + //N/C - NNE - 20140417 + if (QFile::exists(thumbPath)) { + rm_r(thumbPath); + } + } + rm_r(delPath); + // Clear parent dir if empty + QDir dir(delPath); + dir.cdUp(); + if (dir.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot).isEmpty()) { + dir.rmdir(dir.absolutePath()); + } + } + + qDebug() << "Ok on cure"; + } + +private: + void cleanTrash() + { + QFileInfoList ifs = QDir(mCurrentDir).entryInfoList(trashFilter, QDir::NoDotAndDotDot | QDir::Files); + foreach (QFileInfo ifo, ifs) { + rm_r(ifo.absoluteFilePath()); + } + } + + void cureIdsFromSvgDom(const QDomDocument &dom) + { + Q_ASSERT(!dom.isNull()); + + QDomElement nextElement = dom.documentElement().firstChildElement(); + while (!nextElement.isNull()) { + QString nextTag = nextElement.tagName(); + qDebug() << "Tag name of the next parsed element is" << nextTag; + if (nextTag == tGroups) + { + nextElement = nextElement.firstChildElement("group"); + } + + invokeFromText(nextTag, nextElement); + nextElement = nextElement.nextSiblingElement(); + } + } + + void fitIdsFromFileSystem() + { + QString absPrefix = mCurrentDir + "/"; + QStringList dirsList = scanDirs.split(",", QString::SkipEmptyParts); + foreach (QString dirName, dirsList) { + QString absPath = absPrefix + dirName; + if (!QFile::exists(absPath)) { + continue; + } + fitIdsFromDir(absPath); + } + + } + + void fitIdsFromDir(const QString &scanDir) + { + QFileInfoList fileList = QDir(scanDir).entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot); + foreach (QFileInfo nInfo, fileList) { + QString uid = strIdFrom(nInfo.fileName()); + if (uid.isNull()) { + continue; + } + mPresentIdsMap.insert(uid, nInfo.absoluteFilePath()); + } + } + + void findRedundandElements(QVector &v) + { + // Taking information from the physical file system + QStringList domIds = mDomIdsMap.keys(); + QStringList presentIds = mPresentIdsMap.keys(); + v.resize(qMax(domIds.count(), presentIds.count())); + QVector::iterator it_diff; + + it_diff=std::set_symmetric_difference(domIds.begin(), domIds.end() + , presentIds.begin(), presentIds.end() + , v.begin()); + v.resize(it_diff - v.begin()); + } + + void invokeFromText(const QString &what, const QDomElement &element) + { + if (what == tVideo + || what == tAudio + || what == tImage) { + mediaToContainer(element); + } else if (what == tForeignObject) { + foreingObjectToContainer(element); + + //N/C - NNE - 20140317 + cleanObjectFolder(element); + + //N/C - NNE - 20140520 + //foreign object may referer resource which are not present in the svg + addResourceIdToSvg(element); + } else if (what == tTeacherGuide) { + teacherGuideToContainer(element); + } + + pullActionFromElement(element); + } + + // N/C - NNE - 20140317 : When export, reduce the size of the ubz file + void cleanObjectFolder(const QDomElement &element) + { + //QDomElement preference = element.firstChildElement("ub:preference"); + + //N/C - NNE - 20141021 + QDomNodeList childrenNode = element.elementsByTagName("ub:preference"); + + QVector objectsIdUsed; + + for(int i = 0; i < childrenNode.size(); i++){ + QDomElement preference = childrenNode.at(i).toElement(); + + if(!preference.isNull()){ + QString value = preference.attribute("value"); + + int findPos = value.indexOf("objects/"); + int endPos; + + //find all objects used + while(findPos != -1){ + endPos = value.indexOf("\"", findPos); + objectsIdUsed << value.mid(findPos, endPos - findPos); + findPos = value.indexOf("objects/", endPos); + } + } + } + //N/C - NNE - 20141021 : END + + + QString path = element.attribute(aSrc); + QString objectsFolderPath = mCurrentDir + "/" + path + "/objects/"; + + QDir dir(objectsFolderPath); + dir.setFilter(QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot); + + //then check all files in the objects directory + //delete the file not used (not in te objectIdUsed variable) + QFileInfoList list = dir.entryInfoList(); + for (int i = 0; i < list.size(); i++) { + QFileInfo fileInfo = list.at(i); + + if(!objectsIdUsed.contains("objects/"+fileInfo.fileName())){ + QFile(fileInfo.filePath()).remove(); + } + + } + } + // N/C - NNE - 20140317 : END + + //N/C - NNE - 20140520 + void addResourceIdToSvg(const QDomElement& element) + { + QDomElement textContent = element.firstChildElement("itemTextContent"); + + QString value = textContent.text(); + + int findPos = value.indexOf("images/"); + int endPos; + + //find all objects used + while(findPos != -1){ + endPos = value.indexOf("\"", findPos); + + QString path = value.mid(findPos, endPos - findPos); + + QString uuid = path.split("/").at(1).split(".").at(0); + + mDomIdsMap.insert(uuid, path); + + findPos = value.indexOf("images/", endPos); + } + } + //N/C - NNE - 20140520 : END + + void pullActionFromElement(const QDomElement &element) + { + if (!element.hasAttribute(aActionMedia)) { + return; + } + + QString path = element.attribute(aActionMedia); + if (path.isNull()) { + return; + } + + QString uid = strIdFrom(path); + if (uid.isNull()) { + return; + } + + mDomIdsMap.insert(uid, path); + } + + void teacherGuideToContainer(const QDomElement &element) + { + QDomElement nMediaElement = element.firstChildElement(tMedia); + while (!nMediaElement.isNull()) { + + QString path = nMediaElement.attribute(aRelativePath); + if (path.isNull()) { + continue; + } + + QString uid = strIdFrom(path); + if (uid.isNull()) { + return; + } + mDomIdsMap.insert(uid, path); + + nMediaElement = nMediaElement.nextSiblingElement(tMedia); + } + } + + void mediaToContainer(const QDomElement &element) + { + QString path = element.attribute(aHref); + if (path.isNull()) { + return; + } + QString uid = strIdFrom(path); + if (uid.isNull()) { + return; + } + mDomIdsMap.insert(uid, path); + } + + void foreingObjectToContainer(const QDomElement &element) + { + QString type = element.attribute(aType); + if (type == vText) { // We don't have to care of the text object + return; + } + + QString path = element.attribute(aSrc); + if (path.isNull()) { + return; + } + + QString uid = strIdFrom(path); + if (uid.isNull()) { + return; + } + + mDomIdsMap.insert(uid, path); + } + +private: + QString mCurrentDir; + QDomDocument mSvgData; + QMap mDomIdsMap; + QMap mPresentIdsMap; +}; + +class PageCopier +{ +public: + void copyPage (const QUrl &fromDir, int fromIndex, const QUrl &toDir, int toIndex) + { + mFromDir = fromDir.toLocalFile(); + mToDir = toDir.toLocalFile(); + mFromIndex = fromIndex; + mToIndex = toIndex; + + QString svgFrom = mFromDir + "/" + svgPageName(fromIndex); + QString svgTo = toDir.toLocalFile() + "/" + svgPageName(toIndex); + QDomDocument dd = createDomFromSvg(svgFrom); + QFile fl(svgTo); + if (!fl.open(QIODevice::WriteOnly)) { + qDebug() << Q_FUNC_INFO << "can't open" << fl.fileName() << "for writing"; + return; + } + cureIdsFromSvgDom(dd); + + QTextStream str(&fl); + dd.save(str, 0); + fl.close(); + qDebug() << Q_FUNC_INFO; + } + +private: + void cureIdsFromSvgDom(const QDomDocument &dom) + { + Q_ASSERT(!dom.isNull()); + + QDomElement nextElement = dom.documentElement().firstChildElement(); + while (!nextElement.isNull( )) { + qDebug() << "Tag name of the next parsed element is" << nextElement.tagName(); + QString nextTag = nextElement.tagName(); + cureFromText(nextTag, nextElement); + nextElement = nextElement.nextSiblingElement(); + } + } + + void cureFromText(const QString &tagName, QDomElement element) + { + if (tagName == tVideo + || tagName == tAudio + || tagName == tImage) { + QString newRelative = cureNCopy(element.attribute(aHref)); + element.setAttribute(aHref, newRelative); + if (element.hasAttribute(aActionMedia)) { + QString newActionPath = cureNCopy(element.attribute(aActionMedia)); + element.setAttribute(aActionMedia, newActionPath); + } + } else if (tagName == tForeignObject) { + //Pdf object is a special case. Detect if it ends with #reference + QString reqExt = element.attribute(aReqExt); + if (reqExt == vReqExt) { //pdf reference + QString ref = element.attribute(aHref); + if (ref.isEmpty()) { + return; + } + ref.replace(QRegExp("^(.*pdf\\#page\\=).*$"), QString("\\1%1").arg(mToIndex)); + return; + } + + QString type = element.attribute(aType); + if (type == vText) { // We don't have to care of the text object + if (element.hasAttribute(aActionMedia)) { + QString newRelative = cureNCopy(element.attribute(aActionMedia)); + element.setAttribute(aActionMedia, newRelative); + } + return; + } + QString newRelative = cureNCopy(element.attribute(aSrc)); + element.setAttribute(aSrc, newRelative); + } else if (tagName == tTeacherGuide) { + QDomElement nMediaElement = element.firstChildElement(tMedia); + while (!nMediaElement.isNull()) { + QString newRelative = cureNCopy(nMediaElement.attribute(aRelativePath)); + nMediaElement.setAttribute(aRelativePath, newRelative); + nMediaElement = nMediaElement.nextSiblingElement(tMedia); + } + } + } + + QString cureNCopy(const QString &relativePath) + { + QString relative = relativePath; + QUuid newUuid = QUuid::createUuid(); + QString newPath = relative.replace(QRegExp("\\{.*\\}"), newUuid.toString()); + cp_rf(mFromDir + "/" + relativePath, mToDir + "/" + newPath); + + return newPath; + } + +private: + QString mFromDir; + QString mToDir; + int mFromIndex; + int mToIndex; +}; + +class UBForeighnObjectsHandlerPrivate { + UBForeighnObjectsHandlerPrivate(UBForeighnObjectsHandler *pq) + : q(pq) + { + } + +public: + void cure(const QUrl &dir) + { + Cleaner *cleaner = new Cleaner; + cleaner->cure(dir); + delete cleaner; + cleaner = 0; + } + + void copyPage (const QUrl &fromDir, int fromIndex, const QUrl &toDir, int toIndex) + { + PageCopier *copier = new PageCopier; + copier->copyPage(fromDir, fromIndex, toDir, toIndex); + delete copier; + copier = 0; + } + +private: + UBForeighnObjectsHandler *q; + friend class UBForeighnObjectsHandler; +}; + +UBForeighnObjectsHandler::UBForeighnObjectsHandler() + : d(new UBForeighnObjectsHandlerPrivate(this)) +{ + +} + +UBForeighnObjectsHandler::~UBForeighnObjectsHandler() +{ + delete d; +} + +void UBForeighnObjectsHandler::cure(const QList &dirs) +{ + foreach (QUrl dir, dirs) { + cure(dir); + } +} + +void UBForeighnObjectsHandler::cure(const QUrl &dir) +{ + d->cure(dir); +} + +void UBForeighnObjectsHandler::copyPage(const QUrl &fromDir, int fromIndex, const QUrl &toDir, int toIndex) +{ + d->copyPage(fromDir, fromIndex, toDir, toIndex); +} + diff --git a/src/core/UBForeignObjectsHandler.h b/src/core/UBForeignObjectsHandler.h new file mode 100644 index 00000000..18726964 --- /dev/null +++ b/src/core/UBForeignObjectsHandler.h @@ -0,0 +1,49 @@ +/* +* Copyright (C) 2010-2013 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA) +* +* This file is part of Open-Sankoré. +* +* Open-Sankoré is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, version 3 of the License, +* with a specific linking exception for the OpenSSL project's +* "OpenSSL" library (or with modified versions of it that use the +* same license as the "OpenSSL" library). +* +* Open-Sankoré 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with Open-Sankoré. If not, see . +*/ + +#ifndef UBFOREIGHNOBJECTSHANDLER_H +#define UBFOREIGHNOBJECTSHANDLER_H + +#include +#include +#include + +class UBForeighnObjectsHandlerPrivate; + +class UBForeighnObjectsHandler +{ +public: + UBForeighnObjectsHandler(); + ~UBForeighnObjectsHandler(); + + void cure(const QList &dirs); + void cure(const QUrl &dir); + + void copyPage(const QUrl &fromDir, int fromIndex, + const QUrl &toDir, int toIndex); + +private: + UBForeighnObjectsHandlerPrivate *d; + + friend class UBForeighnObjectsHandlerPrivate; +}; + +#endif // UBFOREIGHNOBJECTSHANDLER_H diff --git a/src/core/UBPersistenceManager.cpp b/src/core/UBPersistenceManager.cpp index 4f3f9704..0b05343f 100644 --- a/src/core/UBPersistenceManager.cpp +++ b/src/core/UBPersistenceManager.cpp @@ -1,36 +1,34 @@ /* - * Copyright (C) 2015-2016 Département de l'Instruction Publique (DIP-SEM) + * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA) * - * Copyright (C) 2013 Open Education Foundation + * This file is part of Open-Sankoré. * - * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour - * l'Education Numérique en Afrique (GIP ENA) - * - * This file is part of OpenBoard. - * - * OpenBoard is free software: you can redistribute it and/or modify + * Open-Sankoré is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 of the License, * with a specific linking exception for the OpenSSL project's * "OpenSSL" library (or with modified versions of it that use the * same license as the "OpenSSL" library). * - * OpenBoard is distributed in the hope that it will be useful, + * Open-Sankoré 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 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with OpenBoard. If not, see . + * along with Open-Sankoré. If not, see . */ - #include "UBPersistenceManager.h" #include "gui/UBMainWindow.h" #include +#include +#include +#include +#include #include "frameworks/UBPlatformUtils.h" #include "frameworks/UBFileSystemUtils.h" @@ -38,6 +36,7 @@ #include "core/UBApplication.h" #include "core/UBSettings.h" #include "core/UBSetting.h" +#include "core/UBForeignObjectsHandler.h" #include "document/UBDocumentProxy.h" @@ -46,14 +45,11 @@ #include "adaptors/UBThumbnailAdaptor.h" #include "adaptors/UBMetadataDcSubsetAdaptor.h" -#include "domain/UBGraphicsMediaItem.h" -#include "domain/UBGraphicsWidgetItem.h" -#include "domain/UBGraphicsPixmapItem.h" -#include "domain/UBGraphicsSvgItem.h" - #include "board/UBBoardController.h" #include "board/UBBoardPaletteManager.h" +#include "document/UBDocumentController.h" + #include "core/memcheck.h" const QString UBPersistenceManager::imageDirectory = "images"; // added to UBPersistenceManager::mAllDirectories @@ -61,56 +57,39 @@ const QString UBPersistenceManager::objectDirectory = "objects"; // added to UBP const QString UBPersistenceManager::widgetDirectory = "widgets"; // added to UBPersistenceManager::mAllDirectories const QString UBPersistenceManager::videoDirectory = "videos"; // added to UBPersistenceManager::mAllDirectories const QString UBPersistenceManager::audioDirectory = "audios"; // added to +const QString UBPersistenceManager::fileDirectory = "files"; // Issue 1683 (Evolution) - AOU - 20131206 + +const QString UBPersistenceManager::myDocumentsName = "MyDocuments"; +const QString UBPersistenceManager::modelsName = "Models"; +const QString UBPersistenceManager::untitledDocumentsName = "UntitledDocuments"; +const QString UBPersistenceManager::fFolders = "folders.xml"; +const QString UBPersistenceManager::tFolder = "folder"; +const QString UBPersistenceManager::aName = "name"; UBPersistenceManager * UBPersistenceManager::sSingleton = 0; UBPersistenceManager::UBPersistenceManager(QObject *pParent) : QObject(pParent) , mHasPurgedDocuments(false) - , mIsApplicationClosing(false) - , mIsWorkerFinished(false) { + xmlFolderStructureFilename = "model"; + mDocumentSubDirectories << imageDirectory; mDocumentSubDirectories << objectDirectory; mDocumentSubDirectories << widgetDirectory; mDocumentSubDirectories << videoDirectory; mDocumentSubDirectories << audioDirectory; + mDocumentSubDirectories << fileDirectory; // Issue 1683 (Evolution) - AOU - 20131206 - documentProxies = allDocumentProxies(); - - mThread = new QThread; - mWorker = new UBPersistenceWorker(); - mWorker->moveToThread(mThread); - connect(mWorker, SIGNAL(error(QString)), - this, SLOT(errorString(QString))); - - connect(mThread, SIGNAL(started()), - mWorker, SLOT(process())); - - connect(mWorker, SIGNAL(finished()), - mThread, SLOT(quit())); - - connect(mWorker, SIGNAL(finished()), - this, SLOT(onWorkerFinished())); - - connect(mWorker, SIGNAL(finished()), - mWorker, SLOT(deleteLater())); - - connect(mThread, SIGNAL(finished()), - mThread, SLOT(deleteLater())); - - connect(mWorker, SIGNAL(sceneLoaded(QByteArray,UBDocumentProxy*,int)), - this, SLOT(onSceneLoaded(QByteArray,UBDocumentProxy*,int))); - - connect(mWorker, SIGNAL(scenePersisted(UBGraphicsScene*)), - this, SLOT(onScenePersisted(UBGraphicsScene*))); + mDocumentRepositoryPath = UBSettings::userDocumentDirectory(); + mFoldersXmlStorageName = mDocumentRepositoryPath + "/" + fFolders; - connect(mWorker, SIGNAL(metadataPersisted(UBDocumentProxy*)), - this, SLOT(onMetadataPersisted(UBDocumentProxy*))); + mDocumentTreeStructureModel = new UBDocumentTreeModel(this); + createDocumentProxiesStructure(); - mThread->start(); + emit proxyListChanged(); } UBPersistenceManager* UBPersistenceManager::persistenceManager() @@ -130,105 +109,191 @@ void UBPersistenceManager::destroy() sSingleton = NULL; } -void UBPersistenceManager::onScenePersisted(UBGraphicsScene* scene) +UBPersistenceManager::~UBPersistenceManager() { - if (!mIsApplicationClosing) { - delete scene; - scene = NULL; - } } -void UBPersistenceManager::onMetadataPersisted(UBDocumentProxy* proxy) +void UBPersistenceManager::createDocumentProxiesStructure(bool interactive) { - delete proxy; -} + mDocumentRepositoryPath = UBSettings::userDocumentDirectory(); -void UBPersistenceManager::onWorkerFinished() -{ - mIsWorkerFinished = true; + QDir rootDir(mDocumentRepositoryPath); + rootDir.mkpath(rootDir.path()); + + QFileInfoList contentList = rootDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Time | QDir::Reversed); + createDocumentProxiesStructure(contentList, interactive); + + if (QFileInfo(mFoldersXmlStorageName).exists()) { + QDomDocument xmlDom; + QFile inFile(mFoldersXmlStorageName); + if (inFile.open(QIODevice::ReadOnly)) { + QString domString(inFile.readAll()); + + int errorLine = 0; int errorColumn = 0; + QString errorStr; + + if (xmlDom.setContent(domString, &errorStr, &errorLine, &errorColumn)) { + loadFolderTreeFromXml("", xmlDom.firstChildElement()); + } else { + qDebug() << "Error reading content of " << mFoldersXmlStorageName << endl + << "Error:" << inFile.errorString() + << "Line:" << errorLine + << "Column:" << errorColumn; + } + inFile.close(); + } else { + qDebug() << "Error reading" << mFoldersXmlStorageName << endl + << "Error:" << inFile.errorString(); + } + } } -UBPersistenceManager::~UBPersistenceManager() +void UBPersistenceManager::createDocumentProxiesStructure(const QFileInfoList &contentInfo, bool interactive) { - mIsApplicationClosing = true; - - if(mWorker) - mWorker->applicationWillClose(); + foreach(QFileInfo path, contentInfo) + { + QString fullPath = path.absoluteFilePath(); - QTime time; - time.start(); - qDebug() << "start waiting"; + QDir dir(fullPath); - while(!mIsWorkerFinished) - QCoreApplication::processEvents(QEventLoop::AllEvents, 100); - qDebug() << "stop waiting after " << time.elapsed() << " ms"; + if (dir.entryList(QDir::Files | QDir::NoDotAndDotDot).size() > 0) + { + QMap metadatas = UBMetadataDcSubsetAdaptor::load(fullPath); + QString docGroupName = metadatas.value(UBSettings::documentGroupName, QString()).toString(); + QString docName = metadatas.value(UBSettings::documentName, QString()).toString(); - foreach(QPointer proxyGuard, documentProxies) - { - if (!proxyGuard.isNull()) - delete proxyGuard.data(); - } + if (docName.isEmpty()) { + qDebug() << "Group name and document name are empty in UBPersistenceManager::createDocumentProxiesStructure()"; + continue; + } - // to be sure that all the scenes are stored on disk -} + QModelIndex parentIndex = mDocumentTreeStructureModel->goTo(docGroupName); + if (!parentIndex.isValid()) { + return; + } -void UBPersistenceManager::errorString(QString error) -{ - qDebug() << "peristence thread return the error " << error; -} + UBDocumentProxy* docProxy = new UBDocumentProxy(fullPath); // managed in UBDocumentTreeNode + foreach(QString key, metadatas.keys()) { + docProxy->setMetaData(key, metadatas.value(key)); + } -void UBPersistenceManager::onSceneLoaded(QByteArray scene, UBDocumentProxy* proxy, int sceneIndex) -{ - Q_UNUSED(scene); - qDebug() << "scene loaded " << sceneIndex; - QTime time; - time.start(); - mSceneCache.insert(proxy, sceneIndex, loadDocumentScene(proxy, sceneIndex, false)); - qDebug() << "millisecond for sceneCache " << time.elapsed(); + docProxy->setPageCount(sceneCount(docProxy)); + bool addDoc = false; + if (!interactive) { + addDoc = true; + } else if (processInteractiveReplacementDialog(docProxy) == QDialog::Accepted) { + addDoc = true; + } + if (addDoc) { + mDocumentTreeStructureModel->addDocument(docProxy, parentIndex); + } + } + } } -QList > UBPersistenceManager::allDocumentProxies() +QDialog::DialogCode UBPersistenceManager::processInteractiveReplacementDialog(UBDocumentProxy *pProxy) { - mDocumentRepositoryPath = UBSettings::userDocumentDirectory(); - - QDir rootDir(mDocumentRepositoryPath); - rootDir.mkpath(rootDir.path()); + //TODO claudio remove this hack necessary on double click on ubz file + Qt::CursorShape saveShape; + if(UBApplication::overrideCursor()){ + saveShape = UBApplication::overrideCursor()->shape(); + UBApplication::overrideCursor()->setShape(Qt::ArrowCursor); + } + else + saveShape = Qt::ArrowCursor; + QDialog::DialogCode result = QDialog::Rejected; - QStringList dirList = rootDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Time | QDir::Reversed); + if (UBApplication::documentController + && UBApplication::documentController->mainWidget()) { + QString docGroupName = pProxy->metaData(UBSettings::documentGroupName).toString(); + QModelIndex parentIndex = mDocumentTreeStructureModel->goTo(docGroupName); + if (!parentIndex.isValid()) { + UBApplication::overrideCursor()->setShape(saveShape); + return QDialog::Rejected; + } - foreach(QString path, dirList) - { - shiftPagesToStartWithTheZeroOne(rootDir.path() + "/" + path); + QStringList docList = mDocumentTreeStructureModel->nodeNameList(parentIndex); + QString docName = pProxy->metaData(UBSettings::documentName).toString(); + + if (docList.contains(docName)) { + UBDocumentReplaceDialog *replaceDialog = new UBDocumentReplaceDialog(docName + , docList + , /*UBApplication::documentController->mainWidget()*/0 + , Qt::Widget); + if (replaceDialog->exec() == QDialog::Accepted) { + result = QDialog::Accepted; + QString resultName = replaceDialog->lineEditText(); + int i = docList.indexOf(resultName); + if (i != -1) { //replace + QModelIndex replaceIndex = mDocumentTreeStructureModel->index(i, 0, parentIndex); + UBDocumentProxy *replaceProxy = mDocumentTreeStructureModel->proxyData(replaceIndex); + if (replaceProxy) { + deleteDocument(replaceProxy); + } + if (replaceIndex.isValid()) { + mDocumentTreeStructureModel->removeRow(i, parentIndex); + } + } + pProxy->setMetaData(UBSettings::documentName, resultName); + } + replaceDialog->setParent(0); + delete replaceDialog; + } else { + result = QDialog::Accepted; + } } + //TODO claudio the if is an hack + if(UBApplication::overrideCursor()) + UBApplication::overrideCursor()->setShape(saveShape); -// QFileSystemWatcher* watcher = new QFileSystemWatcher(this); -// watcher->addPath(mDocumentRepositoryPath); - -// connect(watcher, SIGNAL(directoryChanged(const QString&)), this, SLOT(documentRepositoryChanged(const QString&))); - - QList > proxies; + return result; +} - foreach(QString path, dirList) - { - QString fullPath = rootDir.path() + "/" + path; +QString UBPersistenceManager::adjustDocumentVirtualPath(const QString &str) +{ + QStringList pathList = str.split("/", QString::SkipEmptyParts); - QDir dir(fullPath); + if (pathList.isEmpty()) { + pathList.append(myDocumentsName); + pathList.append(untitledDocumentsName); + } - if (dir.entryList(QDir::Files | QDir::NoDotAndDotDot).size() > 0) - { - UBDocumentProxy* proxy = new UBDocumentProxy(fullPath); // deleted in UBPersistenceManager::destructor + if (pathList.first() != myDocumentsName + && pathList.first() != UBSettings::trashedDocumentGroupNamePrefix + && pathList.first() != modelsName) { + pathList.prepend(myDocumentsName); + } - proxy->setPageCount(sceneCount(proxy)); + return pathList.join("/"); +} - proxies << QPointer(proxy); +void UBPersistenceManager::closing() +{ + QDir rootDir(mDocumentRepositoryPath); + rootDir.mkpath(rootDir.path()); - } + QFile outFile(mFoldersXmlStorageName); + if (outFile.open(QIODevice::WriteOnly)) { + QXmlStreamWriter writer(&outFile); + writer.setAutoFormatting(true); + writer.writeStartDocument(); + writer.writeStartElement("content"); + saveFoldersTreeToXml(writer, QModelIndex()); + writer.writeEndElement(); + writer.writeEndDocument(); + + outFile.close(); + } else { + qDebug() << "failed to open document" << mFoldersXmlStorageName << "for writing" << endl + << "Error string:" << outFile.errorString(); } - - return proxies; } +bool UBPersistenceManager::isSceneInCached(UBDocumentProxy *proxy, int index) const +{ + return mSceneCache.contains(proxy, index); +} QStringList UBPersistenceManager::allShapes() { @@ -324,11 +389,22 @@ QStringList UBPersistenceManager::allWidgets(const QDir& dir) } -UBDocumentProxy* UBPersistenceManager::createDocument(const QString& pGroupName, const QString& pName, bool withEmptyPage) +UBDocumentProxy* UBPersistenceManager::createDocument(const QString& pGroupName + , const QString& pName + , bool withEmptyPage + , QString directory + , int pageCount + , bool promptDialogIfExists) { - checkIfDocumentRepositoryExists(); - - UBDocumentProxy *doc = new UBDocumentProxy(); // deleted in UBPersistenceManager::destructor + UBDocumentProxy *doc; + if(directory.length() != 0 ){ + doc = new UBDocumentProxy(directory); // deleted in UBPersistenceManager::destructor + doc->setPageCount(pageCount); + } + else{ + checkIfDocumentRepositoryExists(); + doc = new UBDocumentProxy(); + } if (pGroupName.length() > 0) { @@ -341,30 +417,63 @@ UBDocumentProxy* UBPersistenceManager::createDocument(const QString& pGroupName, } doc->setMetaData(UBSettings::documentVersion, UBSettings::currentFileVersion); - QString currentDate = UBStringUtils::toUtcIsoDateTime(QDateTime::currentDateTime()); + QString currentDate = UBStringUtils::toUtcIsoDateTime(QDateTime::currentDateTime()); doc->setMetaData(UBSettings::documentUpdatedAt,currentDate); doc->setMetaData(UBSettings::documentDate,currentDate); - if (withEmptyPage) + if (withEmptyPage) { createDocumentSceneAt(doc, 0); - else - generatePathIfNeeded(doc); + } + else{ + this->generatePathIfNeeded(doc); + QDir dir(doc->persistencePath()); + if (!dir.mkpath(doc->persistencePath())) + { + return 0; // if we can't create the path, abort function. + } + } - documentProxies.insert(0, QPointer(doc)); + bool addDoc = false; + if (!promptDialogIfExists) { + addDoc = true; + } else if (processInteractiveReplacementDialog(doc) == QDialog::Accepted) { + addDoc = true; + } + if (addDoc) { + mDocumentTreeStructureModel->addDocument(doc); + emit proxyListChanged(); + } else { + deleteDocument(doc); + doc = 0; + } - emit documentCreated(doc); + return doc; +} - mDocumentCreatedDuringSession << doc; +UBDocumentProxy* UBPersistenceManager::createNewDocument(const QString& pGroupName + , const QString& pName + , bool withEmptyPage + , QString directory + , int pageCount + , bool promptDialogIfExists) +{ + UBDocumentProxy *resultDoc = createDocument(pGroupName, pName, withEmptyPage, directory, pageCount, promptDialogIfExists); + if (resultDoc) { + mDocumentTreeStructureModel->markDocumentAsNew(resultDoc); + } - return doc; + return resultDoc; } -UBDocumentProxy* UBPersistenceManager::createDocumentFromDir(const QString& pDocumentDirectory, const QString& pGroupName, const QString& pName) +UBDocumentProxy* UBPersistenceManager::createDocumentFromDir(const QString& pDocumentDirectory + , const QString& pGroupName + , const QString& pName + , bool withEmptyPage + , bool addTitlePage + , bool promptDialogIfExists) { checkIfDocumentRepositoryExists(); - UBPersistenceManager::persistenceManager()->shiftPagesToStartWithTheZeroOne(pDocumentDirectory); - UBDocumentProxy* doc = new UBDocumentProxy(pDocumentDirectory); // deleted in UBPersistenceManager::destructor if (pGroupName.length() > 0) @@ -377,6 +486,16 @@ UBDocumentProxy* UBPersistenceManager::createDocumentFromDir(const QString& pDoc doc->setMetaData(UBSettings::documentName, pName); } + QMap metadatas = UBMetadataDcSubsetAdaptor::load(pDocumentDirectory); + + if(withEmptyPage) createDocumentSceneAt(doc, 0); + if(addTitlePage) persistDocumentScene(doc, mSceneCache.createScene(doc, 0, false), 0); + + foreach(QString key, metadatas.keys()) + { + doc->setMetaData(key, metadatas.value(key)); + } + doc->setUuid(QUuid::createUuid()); doc->setPageCount(sceneCount(doc)); @@ -387,10 +506,21 @@ UBDocumentProxy* UBPersistenceManager::createDocumentFromDir(const QString& pDoc UBSvgSubsetAdaptor::setSceneUuid(doc, i, QUuid::createUuid()); } - documentProxies << QPointer(doc); - - - emit documentCreated(doc); + //work around the + bool addDoc = false; + if (!promptDialogIfExists) { + addDoc = true; + } else if (processInteractiveReplacementDialog(doc) == QDialog::Accepted) { + addDoc = true; + } + if (addDoc) { + mDocumentTreeStructureModel->addDocument(doc); + emit proxyListChanged(); + emit documentCreated(doc); + } else { + deleteDocument(doc); + doc = 0; + } return doc; } @@ -402,18 +532,14 @@ void UBPersistenceManager::deleteDocument(UBDocumentProxy* pDocumentProxy) emit documentWillBeDeleted(pDocumentProxy); + Q_ASSERT(QFileInfo(pDocumentProxy->persistencePath()).exists()); UBFileSystemUtils::deleteDir(pDocumentProxy->persistencePath()); - documentProxies.removeAll(QPointer(pDocumentProxy)); - mDocumentCreatedDuringSession.removeAll(pDocumentProxy); - mSceneCache.removeAllScenes(pDocumentProxy); pDocumentProxy->deleteLater(); - } - UBDocumentProxy* UBPersistenceManager::duplicateDocument(UBDocumentProxy* pDocumentProxy) { checkIfDocumentRepositoryExists(); @@ -433,7 +559,7 @@ UBDocumentProxy* UBPersistenceManager::duplicateDocument(UBDocumentProxy* pDocum foreach(QString key, pDocumentProxy->metaDatas().keys()) { copy->setMetaData(key, pDocumentProxy->metaDatas().value(key)); - } + } copy->setMetaData(UBSettings::documentName, pDocumentProxy->metaData(UBSettings::documentName).toString() + " " + tr("(copy)")); @@ -444,7 +570,7 @@ UBDocumentProxy* UBPersistenceManager::duplicateDocument(UBDocumentProxy* pDocum copy->setPageCount(sceneCount(copy)); - documentProxies << QPointer(copy); + emit proxyListChanged(); emit documentCreated(copy); @@ -478,18 +604,15 @@ void UBPersistenceManager::deleteDocumentScenes(UBDocumentProxy* proxy, const QL foreach(int index, compactedIndexes) { - // trig the reload of the thumbnails emit documentSceneWillBeDeleted(proxy, index); } - QString sourceGroupName = proxy->metaData(UBSettings::documentGroupName).toString(); QString sourceName = proxy->metaData(UBSettings::documentName).toString(); - UBDocumentProxy *trashDocProxy = createDocument(UBSettings::trashedDocumentGroupNamePrefix + sourceGroupName, sourceName, false); - generatePathIfNeeded(trashDocProxy); + UBDocumentProxy *trashDocProxy = createDocument(UBSettings::trashedDocumentGroupNamePrefix/* + sourceGroupName*/, sourceName, false); foreach(int index, compactedIndexes) { - UBGraphicsScene *scene = loadDocumentScene(proxy, index, false); + UBGraphicsScene *scene = loadDocumentScene(proxy, index); if (scene) { //scene is about to move into new document @@ -502,10 +625,9 @@ void UBPersistenceManager::deleteDocumentScenes(UBDocumentProxy* proxy, const QL QDir d = fi.dir(); d.mkpath(d.absolutePath()); - QFile::rename(source, target); + QFile::copy(source, target); } - UBApplication::showMessage(tr("Moving page to trash folder...")); insertDocumentSceneAt(trashDocProxy, scene, trashDocProxy->pageCount()); } } @@ -552,8 +674,6 @@ void UBPersistenceManager::deleteDocumentScenes(UBDocumentProxy* proxy, const QL } - - void UBPersistenceManager::duplicateDocumentScene(UBDocumentProxy* proxy, int index) { checkIfDocumentRepositoryExists(); @@ -570,81 +690,45 @@ void UBPersistenceManager::duplicateDocumentScene(UBDocumentProxy* proxy, int in copyPage(proxy, index , index + 1); + proxy->incPageCount(); - //TODO: write a proper way to handle object on disk - UBGraphicsScene *scene = loadDocumentScene(proxy, index + 1); - - foreach(QGraphicsItem* item, scene->items()) - { - UBGraphicsMediaItem *mediaItem = qgraphicsitem_cast (item); - - if (mediaItem){ - QString source = mediaItem->mediaFileUrl().toLocalFile(); - QString destination = source; - QUuid newUuid = QUuid::createUuid(); - QString fileName = QFileInfo(source).completeBaseName(); - destination = destination.replace(fileName,newUuid.toString()); - QFile::copy(source,destination); - mediaItem->setMediaFileUrl(QUrl::fromLocalFile(destination)); - continue; - } - - UBGraphicsWidgetItem* widget = qgraphicsitem_cast(item); - if(widget){ - QUuid newUUid = QUuid::createUuid(); - QString newUUidString = newUUid.toString().remove("{").remove("}"); - QString actualUuidString = widget->uuid().toString().remove("{").remove("}"); - - QString widgetSourcePath = proxy->persistencePath() + "/" + UBPersistenceManager::widgetDirectory + "/{" + actualUuidString + "}.wgt"; - QString screenshotSourcePath = proxy->persistencePath() + "/" + UBPersistenceManager::widgetDirectory + "/" + actualUuidString + ".png"; - - QString widgetDestinationPath = widgetSourcePath; - widgetDestinationPath = widgetDestinationPath.replace(actualUuidString,newUUidString); - QString screenshotDestinationPath = screenshotSourcePath; - screenshotDestinationPath = screenshotDestinationPath.replace(actualUuidString,newUUidString); - - UBFileSystemUtils::copyDir(widgetSourcePath,widgetDestinationPath); - QFile::copy(screenshotSourcePath,screenshotDestinationPath); + emit documentSceneCreated(proxy, index + 1); +} - widget->setUuid(newUUid); +void UBPersistenceManager::copyDocumentScene(UBDocumentProxy *from, int fromIndex, UBDocumentProxy *to, int toIndex) +{ + if (from == to && toIndex <= fromIndex) { + qDebug() << "operation is not supported" << Q_FUNC_INFO; + return; + } - widget->widgetUrl(QUrl::fromLocalFile(widgetDestinationPath)); + checkIfDocumentRepositoryExists(); - continue; - } + for (int i = to->pageCount(); i > toIndex; i--) { + renamePage(to, i - 1, i); + mSceneCache.moveScene(to, i - 1, i); + } - UBGraphicsPixmapItem* pixmapItem = qgraphicsitem_cast(item); - if(pixmapItem){ - QString source = proxy->persistencePath() + "/" + UBPersistenceManager::imageDirectory + "/" + pixmapItem->uuid().toString() + ".png"; - QString destination = source; - QUuid newUuid = QUuid::createUuid(); - QString fileName = QFileInfo(source).completeBaseName(); - destination = destination.replace(fileName,newUuid.toString()); - QFile::copy(source,destination); - pixmapItem->setUuid(newUuid); - continue; - } + UBForeighnObjectsHandler hl; + hl.copyPage(QUrl::fromLocalFile(from->persistencePath()), fromIndex, + QUrl::fromLocalFile(to->persistencePath()), toIndex); - UBGraphicsSvgItem* svgItem = qgraphicsitem_cast(item); - if(svgItem){ - QString source = proxy->persistencePath() + "/" + UBPersistenceManager::imageDirectory + "/" + svgItem->uuid().toString() + ".svg"; - QString destination = source; - QUuid newUuid = QUuid::createUuid(); - QString fileName = QFileInfo(source).completeBaseName(); - destination = destination.replace(fileName,newUuid.toString()); - QFile::copy(source,destination); - svgItem->setUuid(newUuid); - continue; - } + to->incPageCount(); - } - scene->setModified(true); + QString thumbTmp(from->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.thumbnail.jpg", fromIndex)); + QString thumbTo(to->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.thumbnail.jpg", toIndex)); - persistDocumentScene(proxy,scene, index + 1); + QFile::remove(thumbTo); + QFile::copy(thumbTmp, thumbTo); - proxy->incPageCount(); + Q_ASSERT(QFileInfo(thumbTmp).exists()); + Q_ASSERT(QFileInfo(thumbTo).exists()); + const QPixmap *pix = new QPixmap(thumbTmp); + UBDocumentController *ctrl = UBApplication::documentController; + ctrl->addPixmapAt(pix, toIndex); + ctrl->TreeViewSelectionChanged(ctrl->firstSelectedTreeIndex(), QModelIndex()); - emit documentSceneCreated(proxy, index + 1); +// emit documentSceneCreated(to, toIndex + 1); } @@ -659,10 +743,6 @@ UBGraphicsScene* UBPersistenceManager::createDocumentSceneAt(UBDocumentProxy* pr UBGraphicsScene *newScene = mSceneCache.createScene(proxy, index, useUndoRedoStack); - newScene->setBackground(UBSettings::settings()->isDarkBackground(), - UBSettings::settings()->UBSettings::pageBackground()); - - newScene->setBackgroundGridSize(UBSettings::settings()->crossSize); persistDocumentScene(proxy, newScene, index); proxy->incPageCount(); @@ -673,7 +753,7 @@ UBGraphicsScene* UBPersistenceManager::createDocumentSceneAt(UBDocumentProxy* pr } -void UBPersistenceManager::insertDocumentSceneAt(UBDocumentProxy* proxy, UBGraphicsScene* scene, int index) +void UBPersistenceManager::insertDocumentSceneAt(UBDocumentProxy* proxy, UBGraphicsScene* scene, int index, bool persist) { scene->setDocument(proxy); @@ -688,7 +768,9 @@ void UBPersistenceManager::insertDocumentSceneAt(UBDocumentProxy* proxy, UBGraph mSceneCache.insert(proxy, index, scene); - persistDocumentScene(proxy, scene, index); + if (persist) { + persistDocumentScene(proxy, scene, index); + } proxy->incPageCount(); @@ -735,73 +817,63 @@ void UBPersistenceManager::moveSceneToIndex(UBDocumentProxy* proxy, int source, } -UBGraphicsScene* UBPersistenceManager::loadDocumentScene(UBDocumentProxy* proxy, int sceneIndex, bool cacheNeighboringScenes) +UBGraphicsScene* UBPersistenceManager::loadDocumentScene(UBDocumentProxy* proxy, int sceneIndex) { - UBGraphicsScene* scene = NULL; - if (mSceneCache.contains(proxy, sceneIndex)) - scene = mSceneCache.value(proxy, sceneIndex); + return mSceneCache.value(proxy, sceneIndex); else { - scene = UBSvgSubsetAdaptor::loadScene(proxy, sceneIndex); + UBGraphicsScene* scene = UBSvgSubsetAdaptor::loadScene(proxy, sceneIndex); + if(!scene){ + createDocumentSceneAt(proxy,0); + scene = UBSvgSubsetAdaptor::loadScene(proxy, 0); + } if (scene) mSceneCache.insert(proxy, sceneIndex, scene); - } - if (cacheNeighboringScenes) { - if(sceneIndex + 1 < proxy->pageCount() && !mSceneCache.contains(proxy, sceneIndex + 1)) - mWorker->readScene(proxy,sceneIndex+1); - if(sceneIndex - 1 >= 0 && !mSceneCache.contains(proxy, sceneIndex - 1)) - mWorker->readScene(proxy,sceneIndex-1); + return scene; } +} - return scene; +void UBPersistenceManager::reassignDocProxy(UBDocumentProxy *newDocument, UBDocumentProxy *oldDocument) +{ + return mSceneCache.reassignDocProxy(newDocument, oldDocument); } -void UBPersistenceManager::persistDocumentScene(UBDocumentProxy* pDocumentProxy, UBGraphicsScene* pScene, const int pSceneIndex, bool isAnAutomaticBackup, bool forceImmediateSaving) +void UBPersistenceManager::persistDocumentScene(UBDocumentProxy* pDocumentProxy, UBGraphicsScene* pScene, const int pSceneIndex) { checkIfDocumentRepositoryExists(); - if(!isAnAutomaticBackup) - pScene->deselectAllItems(); + pScene->deselectAllItems(); generatePathIfNeeded(pDocumentProxy); QDir dir(pDocumentProxy->persistencePath()); dir.mkpath(pDocumentProxy->persistencePath()); - mSceneCache.insert(pDocumentProxy, pSceneIndex, pScene); + if (pDocumentProxy->isModified()) + UBMetadataDcSubsetAdaptor::persist(pDocumentProxy); if (pScene->isModified()) { - //qDebug() << "Persisting scene"; - if (pDocumentProxy->isModified()) - persistDocumentMetadata(pDocumentProxy, forceImmediateSaving); + UBSvgSubsetAdaptor::persistScene(pDocumentProxy, pScene, pSceneIndex); UBThumbnailAdaptor::persistScene(pDocumentProxy, pScene, pSceneIndex); - if(forceImmediateSaving) - UBSvgSubsetAdaptor::persistScene(pDocumentProxy,pScene,pSceneIndex); - else { - UBGraphicsScene* copiedScene = pScene->sceneDeepCopy(); - mWorker->saveScene(pDocumentProxy, copiedScene, pSceneIndex); - pScene->setModified(false); - } + pScene->setModified(false); } + + mSceneCache.insert(pDocumentProxy, pSceneIndex, pScene); } -void UBPersistenceManager::persistDocumentMetadata(UBDocumentProxy* pDocumentProxy, bool forceImmediateSaving) +UBDocumentProxy* UBPersistenceManager::persistDocumentMetadata(UBDocumentProxy* pDocumentProxy) { - if (forceImmediateSaving) { - UBMetadataDcSubsetAdaptor::persist(pDocumentProxy); - emit documentMetadataChanged(pDocumentProxy); - } + UBMetadataDcSubsetAdaptor::persist(pDocumentProxy); - else { - UBDocumentProxy* copy = pDocumentProxy->deepCopy(); - mWorker->saveMetadata(copy); - } + emit documentMetadataChanged(pDocumentProxy); + + return pDocumentProxy; } @@ -830,8 +902,10 @@ void UBPersistenceManager::copyPage(UBDocumentProxy* pDocumentProxy, const int s int UBPersistenceManager::sceneCount(const UBDocumentProxy* proxy) { const QString pPath = proxy->persistencePath(); + int pageIndex = 0; bool moreToProcess = true; + bool addedMissingZeroPage = false; while (moreToProcess) { @@ -840,9 +914,19 @@ int UBPersistenceManager::sceneCount(const UBDocumentProxy* proxy) QFile file(fileName); if (file.exists()) + { pageIndex++; + } else + { moreToProcess = false; + } + } + + if(pageIndex == 1 && addedMissingZeroPage){ + // increment is done only to check if there are other pages than the missing zero page + // This situation means -> no pages on the document + return 0; } return pageIndex; @@ -859,7 +943,7 @@ QString UBPersistenceManager::generateUniqueDocumentPath(const QString& baseFold QDateTime now = QDateTime::currentDateTime(); QString dirName = now.toString("yyyy-MM-dd hh-mm-ss.zzz"); - return baseFolder + QString("/OpenBoard Document %1").arg(dirName); + return baseFolder + QString("/Sankore Document %1").arg(dirName); } QString UBPersistenceManager::generateUniqueDocumentPath() @@ -912,24 +996,8 @@ bool UBPersistenceManager::addDirectoryContentToDocument(const QString& document pDocument->setPageCount(sceneCount(pDocument)); - return false; -} - - -UBDocumentProxy* UBPersistenceManager::documentByUuid(const QUuid& pUuid) -{ - for(int i = 0 ; i < documentProxies.length(); i++) - { - UBDocumentProxy* proxy = documentProxies.at(i); - - if (proxy && proxy->uuid() == pUuid) - { - return proxy; - } - } - - return 0; - + //issue NC - NNE - 20131213 : At this point, all is well done. + return true; } @@ -941,35 +1009,14 @@ bool UBPersistenceManager::isEmpty(UBDocumentProxy* pDocumentProxy) if (pDocumentProxy->pageCount() > 1) return false; - UBGraphicsScene *theSoleScene = mSceneCache.value(pDocumentProxy, 0) ? mSceneCache.value(pDocumentProxy, 0) : UBSvgSubsetAdaptor::loadScene(pDocumentProxy, 0); + UBGraphicsScene *theSoleScene = UBSvgSubsetAdaptor::loadScene(pDocumentProxy, 0); bool empty = false; if (theSoleScene) { empty = theSoleScene->isEmpty(); - if(empty){ - mSceneCache.removeScene(pDocumentProxy,0); - theSoleScene = NULL; - } - else{ - //the scene can contain Delegate buttons and the selection frame - // but this doesn't means that there is something useful on the frame - bool usefulItemFound = false; - foreach(QGraphicsItem* eachItem, theSoleScene->getFastAccessItems()){ - if(eachItem->type() > QGraphicsItem::UserType - && eachItem->type() != UBGraphicsItemType::DelegateButtonType - && eachItem->type() != UBGraphicsItemType::SelectionFrameType){ - usefulItemFound = true; - break; - } - } - if(!usefulItemFound){ - mSceneCache.removeScene(pDocumentProxy,0); - theSoleScene = NULL; - empty = true; - } - } + delete theSoleScene; } else { @@ -982,24 +1029,19 @@ bool UBPersistenceManager::isEmpty(UBDocumentProxy* pDocumentProxy) void UBPersistenceManager::purgeEmptyDocuments() { - if(!mHasPurgedDocuments) // hack to workaround the fact that app closing is called twice :-( - { - QList toBeDeleted; - - foreach(UBDocumentProxy* docProxy, mDocumentCreatedDuringSession) - { - if (isEmpty(docProxy)) - { - toBeDeleted << docProxy; - } - } + QList toBeDeleted; - foreach(UBDocumentProxy* docProxy, toBeDeleted) + foreach(UBDocumentProxy* docProxy, mDocumentTreeStructureModel->newDocuments()) + { + if (isEmpty(docProxy)) { - deleteDocument(docProxy); + toBeDeleted << docProxy; } + } - mHasPurgedDocuments = true; + foreach(UBDocumentProxy* docProxy, toBeDeleted) + { + deleteDocument(docProxy); } } @@ -1018,13 +1060,16 @@ bool UBPersistenceManager::addFileToDocument(UBDocumentProxy* pDocumentProxy, if (data == NULL && !fi.exists()) return false; + qDebug() << fi.suffix(); + QString fileName = subdir + "/" + objectUuid.toString() + "." + fi.suffix(); + destinationPath = pDocumentProxy->persistencePath() + "/" + fileName; if (!QFile::exists(destinationPath)) { QDir dir; - dir.mkpath(pDocumentProxy->persistencePath() + "/" + subdir); + dir.mkdir(pDocumentProxy->persistencePath() + "/" + subdir); if (!QFile::exists(pDocumentProxy->persistencePath() + "/" + subdir)) return false; @@ -1100,25 +1145,88 @@ void UBPersistenceManager::checkIfDocumentRepositoryExists() QString humanPath = QDir::cleanPath(mDocumentRepositoryPath); humanPath = QDir::toNativeSeparators(humanPath); - UBApplication::mainWindow->warning(tr("Document Repository Loss"),qApp->applicationName() + tr("has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well.").arg(humanPath)); + UBApplication::mainWindow->warning(tr("Document Repository Loss"),tr("Sankore has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well.").arg(humanPath)); UBApplication::quit(); } } -void UBPersistenceManager::shiftPagesToStartWithTheZeroOne(QString persistencePath) +void UBPersistenceManager::saveFoldersTreeToXml(QXmlStreamWriter &writer, const QModelIndex &parentIndex) { - if(!QFile(persistencePath + "/page000.svg").exists() && QFile(persistencePath + "/page001.svg").exists()){ - int i = 1; + for (int i = 0; i < mDocumentTreeStructureModel->rowCount(parentIndex); i++) + { + QModelIndex currentIndex = mDocumentTreeStructureModel->index(i, 0, parentIndex); + if (mDocumentTreeStructureModel->isCatalog(currentIndex)) + { + writer.writeStartElement(tFolder); + writer.writeAttribute(aName, mDocumentTreeStructureModel->nodeFromIndex(currentIndex)->nodeName()); + saveFoldersTreeToXml(writer, currentIndex); + writer.writeEndElement(); + } + } +} - while(QFile(persistencePath + UBFileSystemUtils::digitFileFormat("/page%1.svg",i)).exists()){ - QFile svg(persistencePath + UBFileSystemUtils::digitFileFormat("/page%1.svg", i)); - svg.rename(persistencePath + UBFileSystemUtils::digitFileFormat("/page%1.svg", i-1)); +void UBPersistenceManager::loadFolderTreeFromXml(const QString &path, const QDomElement &element) +{ - QFile thumb(persistencePath + UBFileSystemUtils::digitFileFormat("/page%1.thumbnail.jpg", i)); - thumb.rename(persistencePath + UBFileSystemUtils::digitFileFormat("/page%1.thumbnail.jpg", i-1)); + QDomElement iterElement = element.firstChildElement(); + while(!iterElement.isNull()) + { + QString leafPath; + if (tFolder == iterElement.tagName()) + { + leafPath = iterElement.attribute(aName); - i+=1; + if (!leafPath.isEmpty()) + { + mDocumentTreeStructureModel->goTo(path + "/" + leafPath); + if (!iterElement.firstChildElement().isNull()) + loadFolderTreeFromXml(path + "/" + leafPath, iterElement); + } } + iterElement = iterElement.nextSiblingElement(); } } + +bool UBPersistenceManager::mayHaveVideo(UBDocumentProxy* pDocumentProxy) +{ + QDir videoDir(pDocumentProxy->persistencePath() + "/" + UBPersistenceManager::videoDirectory); + + return videoDir.exists() && videoDir.entryInfoList().length() > 0; +} + +bool UBPersistenceManager::mayHaveAudio(UBDocumentProxy* pDocumentProxy) +{ + QDir audioDir(pDocumentProxy->persistencePath() + "/" + UBPersistenceManager::audioDirectory); + + return audioDir.exists() && audioDir.entryInfoList().length() > 0; +} + +bool UBPersistenceManager::mayHavePDF(UBDocumentProxy* pDocumentProxy) +{ + QDir objectDir(pDocumentProxy->persistencePath() + "/" + UBPersistenceManager::objectDirectory); + + QStringList filters; + filters << "*.pdf"; + + return objectDir.exists() && objectDir.entryInfoList(filters).length() > 0; +} + + +bool UBPersistenceManager::mayHaveSVGImages(UBDocumentProxy* pDocumentProxy) +{ + QDir imageDir(pDocumentProxy->persistencePath() + "/" + UBPersistenceManager::imageDirectory); + + QStringList filters; + filters << "*.svg"; + + return imageDir.exists() && imageDir.entryInfoList(filters).length() > 0; +} + + +bool UBPersistenceManager::mayHaveWidget(UBDocumentProxy* pDocumentProxy) +{ + QDir widgetDir(pDocumentProxy->persistencePath() + "/" + UBPersistenceManager::widgetDirectory); + + return widgetDir.exists() && widgetDir.entryInfoList(QDir::Dirs).length() > 0; +} diff --git a/src/core/UBPersistenceManager.h b/src/core/UBPersistenceManager.h index 19d10f66..58b789a4 100644 --- a/src/core/UBPersistenceManager.h +++ b/src/core/UBPersistenceManager.h @@ -1,32 +1,26 @@ /* - * Copyright (C) 2015-2016 Département de l'Instruction Publique (DIP-SEM) + * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA) * - * Copyright (C) 2013 Open Education Foundation + * This file is part of Open-Sankoré. * - * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour - * l'Education Numérique en Afrique (GIP ENA) - * - * This file is part of OpenBoard. - * - * OpenBoard is free software: you can redistribute it and/or modify + * Open-Sankoré is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 of the License, * with a specific linking exception for the OpenSSL project's * "OpenSSL" library (or with modified versions of it that use the * same license as the "OpenSSL" library). * - * OpenBoard is distributed in the hope that it will be useful, + * Open-Sankoré 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 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with OpenBoard. If not, see . + * along with Open-Sankoré. If not, see . */ - #ifndef UBPERSISTENCEMANAGER_H_ #define UBPERSISTENCEMANAGER_H_ @@ -34,11 +28,13 @@ #include "UBSceneCache.h" -#include "UBPersistenceWorker.h" - +class QDomNode; +class QDomElement; class UBDocument; class UBDocumentProxy; class UBGraphicsScene; +class UBDocumentTreeNode; +class UBDocumentTreeModel; class UBPersistenceManager : public QObject { @@ -57,16 +53,40 @@ class UBPersistenceManager : public QObject static const QString videoDirectory; static const QString audioDirectory; static const QString widgetDirectory; - static const QString teacherGuideDirectory; - static void shiftPagesToStartWithTheZeroOne(QString persistencePath); + static const QString fileDirectory; // Issue 1683 (Evolution) - AOU - 20131206 + + static const QString myDocumentsName; + static const QString modelsName; + static const QString untitledDocumentsName; + static const QString fFolders; + static const QString tFolder; + static const QString aName; static UBPersistenceManager* persistenceManager(); static void destroy(); - virtual UBDocumentProxy* createDocument(const QString& pGroupName = "", const QString& pName = "", bool withEmptyPage = true); - virtual UBDocumentProxy* createDocumentFromDir(const QString& pDocumentDirectory, const QString& pGroupName = "", const QString& pName = ""); - - virtual void persistDocumentMetadata(UBDocumentProxy* pDocumentProxy, bool forceImmediateSaving = false); + virtual UBDocumentProxy* createDocument(const QString& pGroupName = "" + , const QString& pName = "" + , bool withEmptyPage = true + , QString directory =QString() + , int pageCount = 0 + , bool promptDialogIfExists = false); + + virtual UBDocumentProxy *createNewDocument(const QString& pGroupName = "" + , const QString& pName = "" + , bool withEmptyPage = true + , QString directory =QString() + , int pageCount = 0 + , bool promptDialogIfExists = false); + + virtual UBDocumentProxy* createDocumentFromDir(const QString& pDocumentDirectory + , const QString& pGroupName = "" + , const QString& pName = "" + , bool withEmptyPage = false + , bool addTitlePage = false + , bool promptDialogIfExists = false); + + virtual UBDocumentProxy* persistDocumentMetadata(UBDocumentProxy* pDocumentProxy); virtual UBDocumentProxy* duplicateDocument(UBDocumentProxy* pDocumentProxy); @@ -76,19 +96,24 @@ class UBPersistenceManager : public QObject virtual void duplicateDocumentScene(UBDocumentProxy* pDocumentProxy, int index); + virtual void copyDocumentScene(UBDocumentProxy *from, int fromIndex, UBDocumentProxy *to, int toIndex); + virtual void persistDocumentScene(UBDocumentProxy* pDocumentProxy, - UBGraphicsScene* pScene, const int pSceneIndex, bool isAnAutomaticBackup = false, bool forceImmediateSaving = false); + UBGraphicsScene* pScene, const int pSceneIndex); virtual UBGraphicsScene* createDocumentSceneAt(UBDocumentProxy* pDocumentProxy, int index, bool useUndoRedoStack = true); - virtual void insertDocumentSceneAt(UBDocumentProxy* pDocumentProxy, UBGraphicsScene* scene, int index); + virtual void insertDocumentSceneAt(UBDocumentProxy* pDocumentProxy, UBGraphicsScene* scene, int index, bool persist = true); virtual void moveSceneToIndex(UBDocumentProxy* pDocumentProxy, int source, int target); - virtual UBGraphicsScene* loadDocumentScene(UBDocumentProxy* pDocumentProxy, int sceneIndex, bool cacheNeighboringScenes = true); + virtual UBGraphicsScene* loadDocumentScene(UBDocumentProxy* pDocumentProxy, int sceneIndex); UBGraphicsScene *getDocumentScene(UBDocumentProxy* pDocumentProxy, int sceneIndex) {return mSceneCache.value(pDocumentProxy, sceneIndex);} + void reassignDocProxy(UBDocumentProxy *newDocument, UBDocumentProxy *oldDocument); - QList > documentProxies; +// QList > documentProxies; + UBDocumentTreeNode *mDocumentTreeStructure; + UBDocumentTreeModel *mDocumentTreeStructureModel; virtual QStringList allShapes(); virtual QStringList allGips(); @@ -101,7 +126,9 @@ class UBPersistenceManager : public QObject bool addDirectoryContentToDocument(const QString& documentRootFolder, UBDocumentProxy* pDocument); - virtual UBDocumentProxy* documentByUuid(const QUuid& pUuid); + void createDocumentProxiesStructure(bool interactive = false); + void createDocumentProxiesStructure(const QFileInfoList &contentInfo, bool interactive = false); + QDialog::DialogCode processInteractiveReplacementDialog(UBDocumentProxy *pProxy); QStringList documentSubDirectories() { @@ -114,7 +141,21 @@ class UBPersistenceManager : public QObject bool addGraphicsWidgetToDocument(UBDocumentProxy *mDocumentProxy, QString path, QUuid objectUuid, QString& destinationPath); bool addFileToDocument(UBDocumentProxy* pDocumentProxy, QString path, const QString& subdir, QUuid objectUuid, QString& destinationPath, QByteArray* data = NULL); + bool mayHaveVideo(UBDocumentProxy* pDocumentProxy); + bool mayHaveAudio(UBDocumentProxy* pDocumentProxy); + bool mayHavePDF(UBDocumentProxy* pDocumentProxy); + bool mayHaveSVGImages(UBDocumentProxy* pDocumentProxy); + bool mayHaveWidget(UBDocumentProxy* pDocumentProxy); + + QString adjustDocumentVirtualPath(const QString &str); + + void closing(); + bool isSceneInCached(UBDocumentProxy *proxy, int index) const; + signals: + + void proxyListChanged(); + void documentCreated(UBDocumentProxy* pDocumentProxy); void documentMetadataChanged(UBDocumentProxy* pDocumentProxy); void documentWillBeDeleted(UBDocumentProxy* pDocumentProxy); @@ -122,50 +163,31 @@ class UBPersistenceManager : public QObject void documentSceneCreated(UBDocumentProxy* pDocumentProxy, int pIndex); void documentSceneWillBeDeleted(UBDocumentProxy* pDocumentProxy, int pIndex); - private: - +private: int sceneCount(const UBDocumentProxy* pDocumentProxy); - static QStringList getSceneFileNames(const QString& folder); - - QList > allDocumentProxies(); - void renamePage(UBDocumentProxy* pDocumentProxy, - const int sourceIndex, const int targetIndex); - + const int sourceIndex, const int targetIndex); void copyPage(UBDocumentProxy* pDocumentProxy, - const int sourceIndex, const int targetIndex); - + const int sourceIndex, const int targetIndex); void generatePathIfNeeded(UBDocumentProxy* pDocumentProxy); - void checkIfDocumentRepositoryExists(); - UBSceneCache mSceneCache; + void saveFoldersTreeToXml(QXmlStreamWriter &writer, const QModelIndex &parentIndex); + void loadFolderTreeFromXml(const QString &path, const QDomElement &element); - QStringList mDocumentSubDirectories; + QString xmlFolderStructureFilename; + UBSceneCache mSceneCache; + QStringList mDocumentSubDirectories; QMutex mDeletedListMutex; - bool mHasPurgedDocuments; - - QList mDocumentCreatedDuringSession; - QString mDocumentRepositoryPath; - - UBPersistenceWorker* mWorker; - - QThread* mThread; - bool mIsWorkerFinished; - - bool mIsApplicationClosing; + QString mFoldersXmlStorageName; private slots: void documentRepositoryChanged(const QString& path); - void errorString(QString error); - void onSceneLoaded(QByteArray,UBDocumentProxy*,int); - void onWorkerFinished(); - void onScenePersisted(UBGraphicsScene* scene); - void onMetadataPersisted(UBDocumentProxy* proxy); + }; diff --git a/src/core/UBSceneCache.cpp b/src/core/UBSceneCache.cpp index 756acef4..8c4b0063 100644 --- a/src/core/UBSceneCache.cpp +++ b/src/core/UBSceneCache.cpp @@ -190,6 +190,33 @@ void UBSceneCache::moveScene(UBDocumentProxy* proxy, int sourceIndex, int target } +void UBSceneCache::reassignDocProxy(UBDocumentProxy *newDocument, UBDocumentProxy *oldDocument) +{ + if (!newDocument || !oldDocument) { + return; + } + if (newDocument->pageCount() != oldDocument->pageCount()) { + return; + } + if (!QFileInfo(oldDocument->persistencePath()).exists()) { + return; + } + for (int i = 0; i < oldDocument->pageCount(); i++) { + + UBSceneCacheID sourceKey(oldDocument, i); + UBGraphicsScene *currentScene = value(sourceKey); + if (currentScene) { + currentScene->setDocument(newDocument); + } + mCachedKeyFIFO.removeAll(sourceKey); + int count = QHash::remove(sourceKey); + mCachedSceneCount -= count; + + insert(newDocument, i, currentScene); + } + +} + void UBSceneCache::shiftUpScenes(UBDocumentProxy* proxy, int startIncIndex, int endIncIndex) { diff --git a/src/core/UBSceneCache.h b/src/core/UBSceneCache.h index ea80320e..1d71fb84 100644 --- a/src/core/UBSceneCache.h +++ b/src/core/UBSceneCache.h @@ -98,6 +98,8 @@ class UBSceneCache : public QHash void moveScene(UBDocumentProxy* proxy, int sourceIndex, int targetIndex); + void reassignDocProxy(UBDocumentProxy *newDocument, UBDocumentProxy *oldDocument); + void shiftUpScenes(UBDocumentProxy* proxy, int startIncIndex, int endIncIndex); diff --git a/src/core/core.pri b/src/core/core.pri index a60978ad..3ad1846b 100644 --- a/src/core/core.pri +++ b/src/core/core.pri @@ -15,7 +15,8 @@ HEADERS += src/core/UB.h \ src/core/UBDownloadThread.h \ src/core/UBOpenSankoreImporter.h \ src/core/UBTextTools.h \ - src/core/UBPersistenceWorker.h + src/core/UBPersistenceWorker.h \ + $$PWD/UBForeignObjectsHandler.h SOURCES += src/core/main.cpp \ src/core/UBApplication.cpp \ @@ -33,4 +34,5 @@ SOURCES += src/core/main.cpp \ src/core/UBDownloadThread.cpp \ src/core/UBOpenSankoreImporter.cpp \ src/core/UBTextTools.cpp \ - src/core/UBPersistenceWorker.cpp + src/core/UBPersistenceWorker.cpp \ + $$PWD/UBForeignObjectsHandler.cpp diff --git a/src/customWidgets/UBActionableWidget.cpp b/src/customWidgets/UBActionableWidget.cpp new file mode 100644 index 00000000..ba334aca --- /dev/null +++ b/src/customWidgets/UBActionableWidget.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA) + * + * This file is part of Open-Sankoré. + * + * Open-Sankoré is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License, + * with a specific linking exception for the OpenSSL project's + * "OpenSSL" library (or with modified versions of it that use the + * same license as the "OpenSSL" library). + * + * Open-Sankoré 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Open-Sankoré. If not, see . + */ + + + +#include +#include + +#include "UBActionableWidget.h" + +#include "core/memcheck.h" + +UBActionableWidget::UBActionableWidget(QWidget *parent, const char *name):QWidget(parent) + , mShowActions(false) +{ + setObjectName(name); + mActions.clear(); + mCloseButtons.setIcon(QIcon(QPixmap(":images/close.svg"))); + mCloseButtons.setGeometry(0, 0, 2*ACTIONSIZE, ACTIONSIZE); + mCloseButtons.setVisible(false); + connect(&mCloseButtons, SIGNAL(clicked()), this, SLOT(onCloseClicked())); +} + +UBActionableWidget::~UBActionableWidget() +{ + +} + +void UBActionableWidget::addAction(eAction act) +{ + if(!mActions.contains(act)){ + mActions << act; + } +} + +void UBActionableWidget::removeAction(eAction act) +{ + if(mActions.contains(act)){ + mActions.remove(mActions.indexOf(act)); + } +} + +void UBActionableWidget::removeAllActions() +{ + mActions.clear(); +} + +void UBActionableWidget::setActionsVisible(bool bVisible) +{ + if(!mActions.empty() && mActions.contains(eAction_Close)){ + mCloseButtons.setVisible(bVisible); + } +} + +void UBActionableWidget::onCloseClicked() +{ + emit close(this); +} + +void UBActionableWidget::setActionsParent(QWidget *parent) +{ + if(mActions.contains(eAction_Close)){ + mCloseButtons.setParent(parent); + } +} + +void UBActionableWidget::unsetActionsParent() +{ + if(mActions.contains(eAction_Close)){ + mCloseButtons.setParent(this); + } +} diff --git a/src/customWidgets/UBActionableWidget.h b/src/customWidgets/UBActionableWidget.h new file mode 100644 index 00000000..cdc8e767 --- /dev/null +++ b/src/customWidgets/UBActionableWidget.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA) + * + * This file is part of Open-Sankoré. + * + * Open-Sankoré is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License, + * with a specific linking exception for the OpenSSL project's + * "OpenSSL" library (or with modified versions of it that use the + * same license as the "OpenSSL" library). + * + * Open-Sankoré 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Open-Sankoré. If not, see . + */ + + + +#ifndef UBACTIONABLEWIDGET_H +#define UBACTIONABLEWIDGET_H + +#include +#include +#include +#include + +#define ACTIONSIZE 16 + +typedef enum{ + eAction_Close, + eAction_MoveUp, + eAction_MoveDown +}eAction; + +class UBActionableWidget : public QWidget +{ + Q_OBJECT +public: + UBActionableWidget(QWidget* parent=0, const char* name="UBActionableWidget"); + ~UBActionableWidget(); + void addAction(eAction act); + void removeAction(eAction act); + void removeAllActions(); + void setActionsVisible(bool bVisible); + +signals: + void close(QWidget* w); + +protected: + void setActionsParent(QWidget* parent); + void unsetActionsParent(); + QVector mActions; + QPushButton mCloseButtons; + +private slots: + void onCloseClicked(); + +private: + bool mShowActions; + +}; + +#endif // UBACTIONABLEWIDGET_H diff --git a/src/customWidgets/UBGraphicsItemAction.cpp b/src/customWidgets/UBGraphicsItemAction.cpp new file mode 100644 index 00000000..1d6a7a09 --- /dev/null +++ b/src/customWidgets/UBGraphicsItemAction.cpp @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA) + * + * This file is part of Open-Sankoré. + * + * Open-Sankoré is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License, + * with a specific linking exception for the OpenSSL project's + * "OpenSSL" library (or with modified versions of it that use the + * same license as the "OpenSSL" library). + * + * Open-Sankoré 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Open-Sankoré. If not, see . + */ + + + +#include "UBGraphicsItemAction.h" +#include "core/UBApplication.h" +#include "core/UBPersistenceManager.h" +#include "board/UBBoardController.h" +#include "web/UBWebController.h" +#include "document/UBDocumentController.h" +#include "document/UBDocumentProxy.h" +#include "document/UBDocumentContainer.h" + +#include "board/UBBoardController.h" + + +UBGraphicsItemAction::UBGraphicsItemAction(eUBGraphicsItemLinkType linkType, QObject *parent) : + QObject(parent) +{ + mLinkType = linkType; +} + + void UBGraphicsItemAction::actionRemoved() + { + //NOOP + } + + +UBGraphicsItemPlayAudioAction::UBGraphicsItemPlayAudioAction(QString audioFile, bool onImport, QObject *parent) : + UBGraphicsItemAction(eLinkToAudio,parent) + , mMediaObject(0) + , mIsLoading(true) +{ + Q_ASSERT(audioFile.length() > 0); + + if(onImport){ + QString extension = QFileInfo(audioFile).completeSuffix(); + QString destDir = UBApplication::boardController->selectedDocument()->persistencePath() + "/" + UBPersistenceManager::audioDirectory; + QString destFile = destDir + "/" + QUuid::createUuid().toString() + "." + extension; + if(!QDir(destDir).exists()) + QDir(UBApplication::boardController->selectedDocument()->persistencePath()).mkdir(destDir); + //explanation : the audioFile could be relative. The method copy will return false so a second try is done adding + // the document file path + if(!QFile(audioFile).copy(destFile)) + QFile(UBApplication::boardController->selectedDocument()->persistencePath() + "/" + audioFile).copy(destFile); + mAudioPath = destFile; + mFullPath = destFile; + } + else + { + //On import don't recreate the file + mAudioPath = audioFile; + mFullPath = mAudioPath; + } + + mAudioOutput = new Phonon::AudioOutput(Phonon::MusicCategory, this); + mMediaObject = new Phonon::MediaObject(this); + Phonon::createPath(mMediaObject, mAudioOutput); + mMediaObject->setCurrentSource(Phonon::MediaSource(mAudioPath)); +} + + +UBGraphicsItemPlayAudioAction::UBGraphicsItemPlayAudioAction() : + UBGraphicsItemAction(eLinkToAudio,NULL) + , mMediaObject(0) + , mIsLoading(true) +{ +} + + +void UBGraphicsItemPlayAudioAction::setPath(QString audioPath) +{ + Q_ASSERT(audioPath.length() > 0); + mAudioPath = audioPath; + mFullPath = mAudioPath; + mAudioOutput = new Phonon::AudioOutput(Phonon::MusicCategory, this); + mMediaObject = new Phonon::MediaObject(this); + Phonon::createPath(mMediaObject, mAudioOutput); + mMediaObject->setCurrentSource(Phonon::MediaSource(mAudioPath)); +} + +QString UBGraphicsItemPlayAudioAction::fullPath() +{ + return mFullPath; +} + +UBGraphicsItemPlayAudioAction::~UBGraphicsItemPlayAudioAction() +{ + if(!mMediaObject && mMediaObject->state() == Phonon::PlayingState) + mMediaObject->stop(); +} + +void UBGraphicsItemPlayAudioAction::onSourceHide() +{ + if(mMediaObject && mMediaObject->state() == Phonon::PlayingState){ + mMediaObject->stop(); + } +} + +void UBGraphicsItemPlayAudioAction::play() +{ + if(mMediaObject->state() == Phonon::PlayingState){ + mMediaObject->stop(); + } + mMediaObject->seek(0); + mMediaObject->play(); +} + + +QStringList UBGraphicsItemPlayAudioAction::save() +{ + //Another hack + if(UBApplication::documentController && UBApplication::documentController->selectedDocument()){ + QString documentPath = UBApplication::documentController->selectedDocument()->persistencePath() + "/"; + return QStringList() << QString("%1").arg(eLinkToAudio) << mAudioPath.replace(documentPath,""); + } + else{ + int index = mAudioPath.indexOf("/audios/"); + QString relativePath = mAudioPath.remove(0,index + 1); + return QStringList() << QString("%1").arg(eLinkToAudio) << relativePath; + } +} + +void UBGraphicsItemPlayAudioAction::actionRemoved() +{ + QFile(mAudioPath).remove(); +} + + +UBGraphicsItemMoveToPageAction::UBGraphicsItemMoveToPageAction(eUBGraphicsItemMovePageAction actionType, int page, QObject* parent) : + UBGraphicsItemAction(eLinkToPage,parent) +{ + mActionType = actionType; + mPage = page; +} + +void UBGraphicsItemMoveToPageAction::play() +{ + UBBoardController* boardController = UBApplication::boardController; + + switch (mActionType) { + case eMoveToFirstPage: + boardController->firstScene(); + break; + case eMoveToLastPage: + boardController->lastScene(); + break; + case eMoveToPreviousPage: + boardController->previousScene(); + break; + case eMoveToNextPage: + boardController->nextScene(); + break; + case eMoveToPage: + if(mPage >= 0 && mPage < boardController->pageCount()) + boardController->setActiveDocumentScene(mPage); + else + qWarning() << "scene number " << mPage << "ins't accessible anymore"; + break; + default: + break; + } +} + +QStringList UBGraphicsItemMoveToPageAction::save() +{ + return QStringList() << QString("%1").arg(eLinkToPage) << QString("%1").arg(mActionType) << QString("%1").arg(mPage); +} + + +UBGraphicsItemLinkToWebPageAction::UBGraphicsItemLinkToWebPageAction(QString url, QObject *parent) : + UBGraphicsItemAction(eLinkToWebUrl,parent) +{ + if (url.length() > 0) + { + if(!url.startsWith("http://")) + url = "http://" + url; + mUrl = url; + } + else + mUrl = QString(); +} + +void UBGraphicsItemLinkToWebPageAction::play() +{ + if (mUrl.length() > 0) + UBApplication::webController->loadUrl(QUrl(mUrl)); +} + +QStringList UBGraphicsItemLinkToWebPageAction::save() +{ + return QStringList() << QString("%1").arg(eLinkToWebUrl) << mUrl; +} diff --git a/src/customWidgets/UBGraphicsItemAction.h b/src/customWidgets/UBGraphicsItemAction.h new file mode 100644 index 00000000..f88a42c8 --- /dev/null +++ b/src/customWidgets/UBGraphicsItemAction.h @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA) + * + * This file is part of Open-Sankoré. + * + * Open-Sankoré is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License, + * with a specific linking exception for the OpenSSL project's + * "OpenSSL" library (or with modified versions of it that use the + * same license as the "OpenSSL" library). + * + * Open-Sankoré 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Open-Sankoré. If not, see . + */ + + + +#ifndef UBGRAPHICSITEMSACTIONS_H +#define UBGRAPHICSITEMSACTIONS_H + +#include +#include + + +enum eUBGraphicsItemMovePageAction { + eMoveToFirstPage = 0, + eMoveToLastPage, + eMoveToPreviousPage, + eMoveToNextPage, + eMoveToPage +} ; + +enum eUBGraphicsItemLinkType +{ + eLinkToAudio = 0, + eLinkToPage, + eLinkToWebUrl +}; + +class UBGraphicsItemAction : public QObject +{ + Q_OBJECT +public: + UBGraphicsItemAction(eUBGraphicsItemLinkType linkType,QObject* parent = 0); + virtual void play() = 0; + virtual QStringList save() = 0; + virtual void actionRemoved(); + virtual QString path() {return "";} + eUBGraphicsItemLinkType linkType() { return mLinkType;} + +signals: + +public slots: +private: + eUBGraphicsItemLinkType mLinkType; + +}; + + +class UBGraphicsItemPlayAudioAction : public UBGraphicsItemAction +{ + Q_OBJECT + +public: + UBGraphicsItemPlayAudioAction(QString audioFile, bool onImport = true, QObject* parent = 0); + UBGraphicsItemPlayAudioAction(); + ~UBGraphicsItemPlayAudioAction(); + void play(); + QStringList save(); + void actionRemoved(); + QString path() {return mAudioPath;} + void setPath(QString audioPath); + QString fullPath(); + +public slots: + void onSourceHide(); + +private: + QString mAudioPath; + Phonon::MediaObject *mMediaObject; + Phonon::AudioOutput *mAudioOutput; + bool mIsLoading; + QString mFullPath; +}; + + +class UBGraphicsItemMoveToPageAction : public UBGraphicsItemAction +{ + Q_OBJECT + +public: + UBGraphicsItemMoveToPageAction(eUBGraphicsItemMovePageAction actionType, int page = 0, QObject* parent = 0); + void play(); + QStringList save(); + int page(){return mPage;} + eUBGraphicsItemMovePageAction actionType(){return mActionType;} + +private: + eUBGraphicsItemMovePageAction mActionType; + int mPage; + +}; + +class UBGraphicsItemLinkToWebPageAction : public UBGraphicsItemAction +{ + Q_OBJECT + +public: + UBGraphicsItemLinkToWebPageAction(QString url, QObject* parent = 0); + void play(); + QStringList save(); + QString url(){return mUrl;} + +private: + QString mUrl; +}; + +#endif // UBGRAPHICSITEMSACTIONS_H diff --git a/src/customWidgets/UBMediaWidget.cpp b/src/customWidgets/UBMediaWidget.cpp new file mode 100644 index 00000000..48aecdb0 --- /dev/null +++ b/src/customWidgets/UBMediaWidget.cpp @@ -0,0 +1,368 @@ +/* + * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA) + * + * This file is part of Open-Sankoré. + * + * Open-Sankoré is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License, + * with a specific linking exception for the OpenSSL project's + * "OpenSSL" library (or with modified versions of it that use the + * same license as the "OpenSSL" library). + * + * Open-Sankoré 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Open-Sankoré. If not, see . + */ + + + +#include "core/UBApplication.h" +#include "globals/UBGlobals.h" +#include "UBMediaWidget.h" + +#include "core/memcheck.h" + +/** + * \brief Constructor + * @param type as the media type + * @param parent as the parent widget + * @param name as the object name + */ +UBMediaWidget::UBMediaWidget(eMediaType type, QWidget *parent, const char *name):UBActionableWidget(parent, name) + , mpMediaObject(NULL) + , mpVideoWidget(NULL) + , mpAudioOutput(NULL) + , mpLayout(NULL) + , mpSeekerLayout(NULL) + , mpPlayStopButton(NULL) + , mpPauseButton(NULL) + , mpSlider(NULL) + , mAutoUpdate(false) + , mGeneratingThumbnail(false) + , mBorder(5) + , mpMediaContainer(NULL) + , mMediaLayout(NULL) + , mpCover(NULL) +{ + SET_STYLE_SHEET(); + + addAction(eAction_Close); + mType = type; + mpLayout = new QVBoxLayout(this); + setLayout(mpLayout); + + mpPlayStopButton = new UBMediaButton(this); + mpPlayStopButton->setPixmap(QPixmap(":images/play.svg")); + mpPauseButton = new UBMediaButton(this); + mpPauseButton->setPixmap(QPixmap(":images/pause.svg")); + mpPauseButton->setEnabled(false); + mpSlider = new QSlider(this); + mpSlider->setOrientation(Qt::Horizontal); + mpSlider->setMinimum(0); + mpSlider->setMaximum(0); + + mpSeekerLayout = new QHBoxLayout(); + mpSeekerLayout->addWidget(mpPlayStopButton, 0); + mpSeekerLayout->addWidget(mpPauseButton, 0); + mpSeekerLayout->addWidget(mpSlider, 1); + mpSeekerLayout->setContentsMargins(0, 0, 0, 0); + + connect(mpPlayStopButton, SIGNAL(clicked()), this, SLOT(onPlayStopClicked())); + connect(mpPauseButton, SIGNAL(clicked()), this, SLOT(onPauseClicked())); + connect(mpSlider, SIGNAL(valueChanged(int)), this, SLOT(onSliderChanged(int))); +} + +/** + * \brief Destructor + */ +UBMediaWidget::~UBMediaWidget() +{ + unsetActionsParent(); + DELETEPTR(mpMediaObject); + DELETEPTR(mpSlider); + DELETEPTR(mpPauseButton); + DELETEPTR(mpPlayStopButton); + DELETEPTR(mpAudioOutput); + DELETEPTR(mpVideoWidget); + DELETEPTR(mpCover); + DELETEPTR(mpMediaContainer); + DELETEPTR(mpSeekerLayout); + DELETEPTR(mpLayout); +} + +/** + * \brief Set the media file + * @param filePath as the media file path + */ +void UBMediaWidget::setFile(const QString &filePath) +{ + Q_ASSERT("" != filePath); + mFilePath = filePath; + mpMediaObject = new Phonon::MediaObject(this); + mpMediaObject->setTickInterval(TICK_INTERVAL); + connect(mpMediaObject, SIGNAL(stateChanged(Phonon::State,Phonon::State)), this, SLOT(onStateChanged(Phonon::State,Phonon::State))); + connect(mpMediaObject, SIGNAL(totalTimeChanged(qint64)), this, SLOT(onTotalTimeChanged(qint64))); + connect(mpMediaObject, SIGNAL(tick(qint64)), this, SLOT(onTick(qint64))); + mpMediaObject->setCurrentSource(Phonon::MediaSource(filePath)); + createMediaPlayer(); +} + +/** + * \brief Get the media type + * @returns the media type + */ +eMediaType UBMediaWidget::mediaType() +{ + return mType; +} + +void UBMediaWidget::showEvent(QShowEvent* event) +{ + if(mType == eMediaType_Audio){ + return; + }else{ + if(!mpVideoWidget){ + mpVideoWidget = new Phonon::VideoWidget(this); + mMediaLayout->addStretch(1); + mMediaLayout->addWidget(mpVideoWidget); + mMediaLayout->addStretch(1); + Phonon::createPath(mpMediaObject, mpVideoWidget); + adaptSizeToVideo(); + mpMediaObject->play(); + mpMediaObject->stop(); + } + QWidget::showEvent(event); + } +} + +void UBMediaWidget::hideEvent(QHideEvent* event) +{ + if(mpMediaObject->state() == Phonon::PlayingState) + mpMediaObject->stop(); + UBActionableWidget::hideEvent(event); +} + +/** + * \brief Create the media player + */ +void UBMediaWidget::createMediaPlayer() +{ + mpMediaContainer = new QWidget(); + mpMediaContainer->setObjectName("UBMediaVideoContainer"); + mMediaLayout = new QHBoxLayout(); + mpMediaContainer->setLayout(mMediaLayout); + + if(eMediaType_Video == mType){ + mMediaLayout->setContentsMargins(10, 10, 10, 10); + if(isVisible()){ + mpVideoWidget = new Phonon::VideoWidget(this); + mMediaLayout->addStretch(1); + mMediaLayout->addWidget(mpVideoWidget); + mMediaLayout->addStretch(1); + Phonon::createPath(mpMediaObject, mpVideoWidget); + adaptSizeToVideo(); + } + mpAudioOutput = new Phonon::AudioOutput(Phonon::VideoCategory, this); + Phonon::createPath(mpMediaObject, mpAudioOutput); + }else if(eMediaType_Audio == mType){ + mMediaLayout->setContentsMargins(10, 10, 10, 10); + mpCover = new QLabel(mpMediaContainer); + //mpMediaContainer->setStyleSheet(QString("background: none;")); + setAudioCover(":images/libpalette/soundIcon.svg"); + mpCover->setScaledContents(true); + mMediaLayout->addStretch(1); + mMediaLayout->addWidget(mpCover); + mMediaLayout->addStretch(1); + mpAudioOutput = new Phonon::AudioOutput(Phonon::MusicCategory, this); + Phonon::createPath(mpMediaObject, mpAudioOutput); + } + mpLayout->addWidget(mpMediaContainer, 1); + mpLayout->addLayout(mpSeekerLayout, 0); + setActionsParent(mpMediaContainer); +} + +/** + * \brief Adapt the widget size to the video in order to keep the good aspect ratio + */ +void UBMediaWidget::adaptSizeToVideo() +{ + if(NULL != mpMediaContainer){ + int origW = mpMediaContainer->width(); + int origH = mpMediaContainer->height(); + int newW = width(); + float scaleFactor = (float)origW/(float)newW; + int newH = origH/scaleFactor; + resize(newW, height() + newH); + } +} + +/** + * \brief Handle the media state change notification + * @param newState as the new state + * @param oldState as the old state + */ +void UBMediaWidget::onStateChanged(Phonon::State newState, Phonon::State oldState) +{ + if(!mGeneratingThumbnail){ + if(Phonon::LoadingState == oldState && Phonon::StoppedState == newState){ + if(eMediaType_Video == mType){ + // We do that here to generate the thumbnail of the video + mGeneratingThumbnail = true; + mpMediaObject->play(); + mpMediaObject->pause(); + mGeneratingThumbnail = false; + } + }else if(Phonon::PlayingState == oldState && Phonon::PausedState == newState){ + mpPlayStopButton->setPixmap(QPixmap(":images/play.svg")); + mpPauseButton->setEnabled(false); + }else if((Phonon::PausedState == oldState && Phonon::PlayingState == newState) || + (Phonon::StoppedState == oldState && Phonon::PlayingState == newState)){ + mpPlayStopButton->setPixmap(QPixmap(":images/stop.svg")); + mpPauseButton->setEnabled(true); + }else if(Phonon::PlayingState == oldState && Phonon::StoppedState == newState){ + mpPlayStopButton->setPixmap(QPixmap(":images/play.svg")); + mpPauseButton->setEnabled(false); + mpSlider->setValue(0); + } + + } + // if(mType == eMediaType_Video) + // updateView(newState); +} + +/** + * \brief Handles the total time change notification + * @param total as the new total time + */ +void UBMediaWidget::onTotalTimeChanged(qint64 total) +{ + mpSlider->setMaximum(total); +} + +/** + * \brief Handles the tick notification + * @param currentTime as the current time + */ +void UBMediaWidget::onTick(qint64 currentTime) +{ + mAutoUpdate = true; + mpSlider->setValue((int)currentTime); + mAutoUpdate = false; +} + +/** + * \brief Handles the seeker value change notification + * @param value as the new seeker value + */ +void UBMediaWidget::onSliderChanged(int value) +{ + if(!mAutoUpdate){ + mpMediaObject->seek(value); + } +} + +/** + * \brief Toggle Play-Stop + */ +void UBMediaWidget::onPlayStopClicked() +{ + switch(mpMediaObject->state()){ + case Phonon::PlayingState: + mpMediaObject->stop(); + break; + + case Phonon::StoppedState: + case Phonon::PausedState: + mpMediaObject->play(); + break; + default: + break; + } +} + +/** + * \brief Pause the media + */ +void UBMediaWidget::onPauseClicked() +{ + mpMediaObject->pause(); +} + +/** + * Get the border + * @returns the actual border + */ +int UBMediaWidget::border() +{ + return mBorder; +} + +/** + * \brief Handles the resize event + * @param ev as the resize event + */ +void UBMediaWidget::resizeEvent(QResizeEvent* ev) +{ + Q_UNUSED(ev); +} + +/** + * \brief Set the audio cover + * @param coverPath as the cover image file path + */ +void UBMediaWidget::setAudioCover(const QString &coverPath) +{ + if(NULL != mpCover){ + mpCover->setPixmap(QPixmap(coverPath)); + } +} + +// ----------------------------------------------------------------------------------------------------------- +/** + * \brief Constructor + * @param parent as the parent widget + * @param name as the object name + */ +UBMediaButton::UBMediaButton(QWidget *parent, const char *name):QLabel(parent) + , mPressed(false) +{ + setObjectName(name); + resize(UBMEDIABUTTON_SIZE, UBMEDIABUTTON_SIZE); + setStyleSheet(QString("padding:0px 0px 0px 0px; margin:0px 0px 0px 0px;")); +} + +/** + * \brief Destructor + */ +UBMediaButton::~UBMediaButton() +{ + +} + +/** + * \brief Handles the mouse press notification + * @param ev as the mouse press event + */ +void UBMediaButton::mousePressEvent(QMouseEvent* ev) +{ + Q_UNUSED(ev); + mPressed = true; +} + +/** + * \brief Handles the mouse release notification + * @param ev as the mouse release event + */ +void UBMediaButton::mouseReleaseEvent(QMouseEvent* ev) +{ + Q_UNUSED(ev); + if(mPressed){ + mPressed = false; + emit clicked(); + } +} diff --git a/src/customWidgets/UBMediaWidget.h b/src/customWidgets/UBMediaWidget.h new file mode 100644 index 00000000..1a6f0db6 --- /dev/null +++ b/src/customWidgets/UBMediaWidget.h @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA) + * + * This file is part of Open-Sankoré. + * + * Open-Sankoré is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License, + * with a specific linking exception for the OpenSSL project's + * "OpenSSL" library (or with modified versions of it that use the + * same license as the "OpenSSL" library). + * + * Open-Sankoré 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Open-Sankoré. If not, see . + */ + + + +#ifndef UBMEDIAWIDGET_H +#define UBMEDIAWIDGET_H + +#include +#include +#include +#include +#include +#include +#include + +#include "UBActionableWidget.h" + +#define UBMEDIABUTTON_SIZE 32 +#define TICK_INTERVAL 1000 + +/** + * \brief The media type + */ +typedef enum{ + eMediaType_Video, + eMediaType_Audio +}eMediaType; + +class UBMediaButton : public QLabel +{ + Q_OBJECT +public: + UBMediaButton(QWidget* parent=0, const char* name="UBMediaButton"); + ~UBMediaButton(); + +signals: + void clicked(); + +protected: + void mousePressEvent(QMouseEvent* ev); + void mouseReleaseEvent(QMouseEvent* ev); + +private: + /** And indicator of the press event in progress */ + bool mPressed; +}; + +class UBMediaWidget : public UBActionableWidget +{ + Q_OBJECT +public: + UBMediaWidget(eMediaType type = eMediaType_Video, QWidget* parent=0, const char* name="UBMediaWidget"); + ~UBMediaWidget(); + void setFile(const QString& filePath); + eMediaType mediaType(); + int border(); + void setAudioCover(const QString& coverPath); + void setUrl(const QString& url){mUrl = url;} + QString url(){return mUrl;} + +protected: + void resizeEvent(QResizeEvent* ev); + void showEvent(QShowEvent* event); + void hideEvent(QHideEvent* event); + /** The current media file path */ + QString mFilePath; + +private slots: + void onPlayStopClicked(); + void onPauseClicked(); + void onStateChanged(Phonon::State newState, Phonon::State oldState); + void onTotalTimeChanged(qint64 total); + void onTick(qint64 currentTime); + void onSliderChanged(int value); + +private: + void createMediaPlayer(); + void adaptSizeToVideo(); + + /** The current media type */ + eMediaType mType; + /** The media object */ + Phonon::MediaObject* mpMediaObject; + /** The video renderer */ + Phonon::VideoWidget* mpVideoWidget; + /** The audio renderer */ + Phonon::AudioOutput* mpAudioOutput; + /** The principal layout of this widget */ + QVBoxLayout* mpLayout; + /** The seeker layout */ + QHBoxLayout* mpSeekerLayout; + /** The play-stop button */ + UBMediaButton* mpPlayStopButton; + /** The pause button */ + UBMediaButton* mpPauseButton; + /** The seeker slider */ + QSlider* mpSlider; + /** An indicator of the seeker auto update in progress */ + bool mAutoUpdate; + /** An indicator of the thumbnail generation in progress */ + bool mGeneratingThumbnail; + /** The border */ + int mBorder; + /** A widget that will contain the media */ + QWidget* mpMediaContainer; + /** The media layout */ + QHBoxLayout* mMediaLayout; + /** The audio cover */ + QLabel* mpCover; + /** The media url */ + QString mUrl; +}; + +#endif // UBMEDIAWIDGET_H diff --git a/src/customWidgets/customWidgets.pri b/src/customWidgets/customWidgets.pri new file mode 100644 index 00000000..553f6705 --- /dev/null +++ b/src/customWidgets/customWidgets.pri @@ -0,0 +1,10 @@ + +HEADERS += \ + src/customWidgets/UBMediaWidget.h \ + src/customWidgets/UBActionableWidget.h \ + src/customWidgets/UBGraphicsItemAction.h + +SOURCES += \ + src/customWidgets/UBMediaWidget.cpp \ + src/customWidgets/UBActionableWidget.cpp \ + src/customWidgets/UBGraphicsItemAction.cpp diff --git a/src/document/UBDocumentContainer.cpp b/src/document/UBDocumentContainer.cpp index 7af9e433..5420bc10 100644 --- a/src/document/UBDocumentContainer.cpp +++ b/src/document/UBDocumentContainer.cpp @@ -102,6 +102,14 @@ void UBDocumentContainer::addPage(int index) emit addThumbnailRequired(this, index); } + +void UBDocumentContainer::addPixmapAt(const QPixmap *pix, int index) +{ + mDocumentThumbs.insert(index, pix); + emit documentThumbnailsUpdated(this); +} + + void UBDocumentContainer::clearThumbPage() { qDeleteAll(mDocumentThumbs); diff --git a/src/document/UBDocumentContainer.h b/src/document/UBDocumentContainer.h index 428a2b30..7993c202 100644 --- a/src/document/UBDocumentContainer.h +++ b/src/document/UBDocumentContainer.h @@ -42,6 +42,7 @@ class UBDocumentContainer : public QObject virtual ~UBDocumentContainer(); void setDocument(UBDocumentProxy* document, bool forceReload = false); + void pureSetDocument(UBDocumentProxy *document) {mCurrentDocument = document;} UBDocumentProxy* selectedDocument(){return mCurrentDocument;} int pageCount(){return mCurrentDocument->pageCount();} @@ -56,8 +57,10 @@ class UBDocumentContainer : public QObject void clearThumbPage(); void initThumbPage(); void addPage(int index); + void addPixmapAt(const QPixmap *pix, int index); void updatePage(int index); void addEmptyThumbPage(); + void reloadThumbnails(); void insertThumbPage(int index); @@ -69,7 +72,6 @@ class UBDocumentContainer : public QObject protected: void deleteThumbPage(int index); void updateThumbPage(int index); - void reloadThumbnails(); signals: void documentSet(UBDocumentProxy* document); diff --git a/src/document/UBDocumentController.cpp b/src/document/UBDocumentController.cpp index 47eeee22..82b0dc37 100644 --- a/src/document/UBDocumentController.cpp +++ b/src/document/UBDocumentController.cpp @@ -1,32 +1,25 @@ /* - * Copyright (C) 2015-2016 Département de l'Instruction Publique (DIP-SEM) + * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA) * - * Copyright (C) 2013 Open Education Foundation + * This file is part of Open-Sankoré. * - * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour - * l'Education Numérique en Afrique (GIP ENA) - * - * This file is part of OpenBoard. - * - * OpenBoard is free software: you can redistribute it and/or modify + * Open-Sankoré is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 of the License, * with a specific linking exception for the OpenSSL project's * "OpenSSL" library (or with modified versions of it that use the * same license as the "OpenSSL" library). * - * OpenBoard is distributed in the hope that it will be useful, + * Open-Sankoré 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 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with OpenBoard. If not, see . + * along with Open-Sankoré. If not, see . */ - - #include "UBDocumentController.h" #include @@ -42,6 +35,8 @@ #include "core/UBApplicationController.h" #include "core/UBSettings.h" #include "core/UBSetting.h" +#include "core/UBMimeData.h" +#include "core/UBForeignObjectsHandler.h" #include "adaptors/UBExportPDF.h" #include "adaptors/UBThumbnailAdaptor.h" @@ -70,371 +65,2086 @@ #include "core/memcheck.h" -UBDocumentController::UBDocumentController(UBMainWindow* mainWindow) - : UBDocumentContainer(mainWindow->centralWidget()) - , mSelectionType(None) - , mParentWidget(mainWindow->centralWidget()) - , mBoardController(UBApplication::boardController) - , mDocumentUI(0) - , mMainWindow(mainWindow) - , mDocumentWidget(0) - , mIsClosing(false) - , mToolsPalette(0) - , mToolsPalettePositionned(false) - , mTrashTi(0) - , mDocumentTrashGroupName(tr("Trash")) - , mDefaultDocumentGroupName(tr("Untitled Documents")) +static bool lessThan(UBDocumentTreeNode *lValue, UBDocumentTreeNode *rValue) { - setupViews(); - setupToolbar(); - this->selectDocument(UBApplication::boardController->selectedDocument()); - connect(this, SIGNAL(exportDone()), mMainWindow, SLOT(onExportDone())); - connect(this, SIGNAL(documentThumbnailsUpdated(UBDocumentContainer*)), this, SLOT(refreshDocumentThumbnailsView(UBDocumentContainer*))); + if (lValue->nodeType() == UBDocumentTreeNode::Catalog) { + if (rValue->nodeType() == UBDocumentTreeNode::Catalog) { + return lValue->nodeName() < rValue->nodeName(); + } else { + return true; + } + } else { + if (rValue->nodeType() == UBDocumentTreeNode::Catalog) { + return false; + } else { + Q_ASSERT(lValue->proxyData()); + Q_ASSERT(rValue->proxyData()); + + //N/C - NNE - 20140402 : Default order set to alphabetical order + //return lValue->nodeName() < rValue->nodeName(); + + QDateTime lTime = lValue->proxyData()->documentDate(); + QDateTime rTime = rValue->proxyData()->documentDate(); + + return lTime > rTime; + } + } + + return false; } -UBDocumentController::~UBDocumentController() +UBDocumentReplaceDialog::UBDocumentReplaceDialog(const QString &pIncommingName, const QStringList &pFileList, QWidget *parent, Qt::WindowFlags pFlags) + : QDialog(parent, pFlags) + , mFileNameList(pFileList) + , mIncommingName(pIncommingName) + , acceptText(tr("Accept")) + , replaceText(tr("Replace")) + , cancelText(tr("Cancel")) + , mLabelText(0) { - if (mDocumentUI) - delete mDocumentUI; -} + this->setStyleSheet("background:white;"); + QVBoxLayout *mainLayout = new QVBoxLayout(this); -void UBDocumentController::createNewDocument() -{ - UBDocumentGroupTreeItem* group = selectedDocumentGroupTreeItem(); + QVBoxLayout *labelLayout = new QVBoxLayout(); - if (group) - { - UBDocumentProxy *document = UBPersistenceManager::persistenceManager()->createDocument(group->groupName()); + mLabelText = new QLabel(labelTextWithName(pIncommingName), this); + mLineEdit = new QLineEdit(this); + mLineEdit->setText(pIncommingName); + mLineEdit->selectedText(); - selectDocument(document); - } -} + mValidator = new QRegExpValidator(QRegExp("[^\\/\\:\\?\\*\\|\\<\\>\\\"]{1,}"), this); +// mLineEdit->setValidator(mValidator); + labelLayout->addWidget(mLabelText); + labelLayout->addWidget(mLineEdit); + QHBoxLayout *buttonLayout = new QHBoxLayout(); -UBDocumentProxyTreeItem* UBDocumentController::findDocument(UBDocumentProxy* proxy) -{ - QTreeWidgetItemIterator it(mDocumentUI->documentTreeWidget); + acceptButton = new QPushButton(acceptText, this); + QPushButton *cancelButton = new QPushButton(cancelText, this); + buttonLayout->addWidget(acceptButton); + buttonLayout->addWidget(cancelButton); - while (*it) - { - UBDocumentProxyTreeItem *treeItem = dynamic_cast((*it)); + mainLayout->addLayout(labelLayout); + mainLayout->addLayout(buttonLayout); - if (treeItem && treeItem->proxy() == proxy) - return treeItem; + acceptButton->setEnabled(false); - ++it; - } + connect(acceptButton, SIGNAL(clicked()), this, SLOT(accept())); + connect(cancelButton, SIGNAL(clicked()), this, SLOT(reject())); + connect(mLineEdit, SIGNAL(textEdited(QString)), this, SLOT(reactOnTextChanged(QString))); - return 0; + reactOnTextChanged(mIncommingName); } - -void UBDocumentController::selectDocument(UBDocumentProxy* proxy, bool setAsCurrentDocument) +void UBDocumentReplaceDialog::setRegexp(const QRegExp pRegExp) { - if (proxy==NULL) - { - setDocument(NULL); - return; - } + mValidator->setRegExp(pRegExp); +} +bool UBDocumentReplaceDialog::validString(const QString &pStr) +{ + Q_UNUSED(pStr); + return mLineEdit->hasAcceptableInput(); +} - QTreeWidgetItemIterator it(mDocumentUI->documentTreeWidget); +void UBDocumentReplaceDialog::setFileNameAndList(const QString &fileName, const QStringList &pLst) +{ + mFileNameList = pLst; + mIncommingName = fileName; + mLabelText->setText(labelTextWithName(fileName)); + mLineEdit->setText(fileName); + mLineEdit->selectAll(); + mLineEdit->selectedText(); +} - mDocumentUI->documentTreeWidget->clearSelection(); - mDocumentUI->documentTreeWidget->setCurrentItem(0); +QString UBDocumentReplaceDialog::labelTextWithName(const QString &documentName) const +{ + return tr("The name %1 is allready used.\nKeeping this name will replace the document.\nProviding a new name will create a new document.") + .arg(documentName); +} - UBDocumentProxyTreeItem* selected = 0; +void UBDocumentReplaceDialog::accept() +{ + QDialog::accept(); +} +void UBDocumentReplaceDialog::reject() +{ + mLineEdit->clear(); + emit closeDialog(); - while (*it) - { - UBDocumentProxyTreeItem* pi = dynamic_cast((*it)); + QDialog::reject(); +} - if (pi) - { - if (setAsCurrentDocument) - pi->setIcon(0, QIcon("")); +void UBDocumentReplaceDialog::reactOnTextChanged(const QString &pStr) +{ +// if !mFileNameList.contains(pStr.trimmed(), Qt::CaseSensitive) - pi->setSelected(false); + if (!validString(pStr)) { + acceptButton->setEnabled(false); + mLineEdit->setStyleSheet("background:#FFB3C8;"); + acceptButton->setEnabled(false); - if (pi->proxy() == proxy) - { - selected = pi; - } - } + } else if (mFileNameList.contains(pStr.trimmed(), Qt::CaseSensitive)) { + acceptButton->setEnabled(true); + mLineEdit->setStyleSheet("background:#FFB3C8;"); + acceptButton->setText(replaceText); - ++it; + } else { + acceptButton->setEnabled(true); + mLineEdit->setStyleSheet("background:white;"); + acceptButton->setText(acceptText); } +} - if (selected) - { - setDocument(proxy); - emit documentThumbnailsUpdated(this); +UBDocumentTreeNode::UBDocumentTreeNode(Type pType, const QString &pName, const QString &pDisplayName, UBDocumentProxy *pProxy ) : + mType(pType) + , mName(pName) + , mDisplayName(pDisplayName) + , mProxy(pProxy) +{ + if (pDisplayName.isEmpty()) { + mDisplayName = mName; + } + mParent = 0; +} - selected->setSelected(true); +void UBDocumentTreeNode::addChild(UBDocumentTreeNode *pChild) +{ + if (pChild) { + mChildren += pChild; + pChild->mParent = this; + } +} - selected->parent()->setExpanded(true); - selected->setText(0, proxy->name()); +void UBDocumentTreeNode::insertChild(int pIndex, UBDocumentTreeNode *pChild) +{ + if (pChild) { + mChildren.insert(pIndex, pChild); + pChild->mParent = this; + } +} - if (setAsCurrentDocument) - { - selected->setIcon(0, QIcon(":/images/currentDocument.png")); - if (proxy != mBoardController->selectedDocument()) - mBoardController->setActiveDocumentScene(proxy); - } +void UBDocumentTreeNode::moveChild(UBDocumentTreeNode *child, int index, UBDocumentTreeNode *newParent) +{ + int childIndex = mChildren.indexOf(child); + if (childIndex == -1) { + return; + } - mDocumentUI->documentTreeWidget->setCurrentItem(selected); + newParent->insertChild(index, child); + mChildren.removeAt(childIndex); +} - mDocumentUI->documentTreeWidget->scrollToItem(selected); +void UBDocumentTreeNode::removeChild(int index) +{ + if (index < 0 || index > mChildren.count() - 1) { + return; + } - mSelectionType = Document; + UBDocumentTreeNode *curChild = mChildren[index]; + while (curChild->mChildren.count()) { + curChild->removeChild(0); } + + mChildren.removeAt(index); + delete curChild; } -void UBDocumentController::createNewDocumentGroup() +UBDocumentTreeNode *UBDocumentTreeNode::clone() { - UBDocumentGroupTreeItem* docGroupItem = new UBDocumentGroupTreeItem(0); // deleted by the tree widget - int i = 1; - QString newFolderName = tr("New Folder"); - while (allGroupNames().contains(newFolderName)) - { - newFolderName = tr("New Folder") + " " + QVariant(i++).toString(); + return new UBDocumentTreeNode(this->mType + , this->mName + , this->mDisplayName + , this->mProxy ? new UBDocumentProxy(*this->mProxy) + : 0); +} +QString UBDocumentTreeNode::dirPathInHierarchy() +{ + QString result; + UBDocumentTreeNode *curNode = this; + //protect the 2nd level items + while (curNode->parentNode() && !curNode->isTopLevel()) { + result.prepend(curNode->parentNode()->nodeName() + "/"); + curNode = curNode->parentNode(); } - docGroupItem->setGroupName(newFolderName); - int trashIndex = mDocumentUI->documentTreeWidget->indexOfTopLevelItem(mTrashTi); + if (result.endsWith("/")) { + result.truncate(result.count() - 1); + } - mDocumentUI->documentTreeWidget->insertTopLevelItem(trashIndex, docGroupItem); - mDocumentUI->documentTreeWidget->setCurrentItem(docGroupItem); - mDocumentUI->documentTreeWidget->expandItem(docGroupItem); + return result; } - -UBDocumentProxy* UBDocumentController::selectedDocumentProxy() +UBDocumentTreeNode::~UBDocumentTreeNode() { - UBDocumentProxyTreeItem* proxyItem = selectedDocumentProxyTreeItem(); - return proxyItem ? proxyItem->proxy() : 0; + foreach (UBDocumentTreeNode *curChildren, mChildren) { + delete(curChildren); + curChildren = 0; + } + if (mProxy) + delete mProxy; } - -UBDocumentProxyTreeItem* UBDocumentController::selectedDocumentProxyTreeItem() +//issue 1629 - NNE - 20131105 +bool UBDocumentTreeNode::findNode(UBDocumentTreeNode *node) { - if (mDocumentUI && mDocumentUI->documentTreeWidget) - { - QList selectedItems = mDocumentUI->documentTreeWidget->selectedItems(); + UBDocumentTreeNode *parent = node->parentNode(); - foreach (QTreeWidgetItem * item, selectedItems) - { - UBDocumentProxyTreeItem* proxyItem = dynamic_cast(item); + bool hasFound = false; - if (proxyItem) - { - return proxyItem; - } + while(parent){ + if(parent == this){ + hasFound = true; + break; } + + parent = parent->parentNode(); } - return 0; + return hasFound; } - -UBDocumentGroupTreeItem* UBDocumentController::selectedDocumentGroupTreeItem() +UBDocumentTreeNode *UBDocumentTreeNode::nextSibling() { - QList selectedItems = mDocumentUI->documentTreeWidget->selectedItems(); + UBDocumentTreeNode *parent = this->parentNode(); + UBDocumentTreeNode *nextSibling = NULL; - foreach (QTreeWidgetItem * item, selectedItems) - { - UBDocumentGroupTreeItem* groupItem = dynamic_cast(item); + int myIndex = parent->children().indexOf(this); + int indexOfNextSibling = myIndex + 1; - if (groupItem) - { - return groupItem; - } - else - { - UBDocumentGroupTreeItem* parent = dynamic_cast(item->parent()); - if (parent) - { - return parent; - } - } + if(indexOfNextSibling < parent->children().size()){ + nextSibling = parent->children().at(indexOfNextSibling); } - return 0; + return nextSibling; } - -void UBDocumentController::itemSelectionChanged() +UBDocumentTreeNode *UBDocumentTreeNode::previousSibling() { - updateCurrentSelection(); + UBDocumentTreeNode *parent = this->parentNode(); + UBDocumentTreeNode *previousSibling = NULL; - emit documentThumbnailsUpdated(this); + int myIndex = parent->children().indexOf(this); + int indexOfPreviousSibling = myIndex - 1; - if (multipleSelection()) - mSelectionType = Multiple; - else if (selectedDocumentProxy()) - mSelectionType = Document; - else if (selectedDocumentGroupTreeItem()) - mSelectionType = Folder; - else - mSelectionType = None; + if(indexOfPreviousSibling >= 0){ + previousSibling = parent->children().at(indexOfPreviousSibling); + } - selectionChanged(); + return previousSibling; } +//issue 1629 - NNE - 20131105 : END -void UBDocumentController::setupViews() +UBDocumentTreeModel::UBDocumentTreeModel(QObject *parent) : + QAbstractItemModel(parent) + , mRootNode(0) { + UBDocumentTreeNode *rootNode = new UBDocumentTreeNode(UBDocumentTreeNode::Catalog, "root"); - if (!mDocumentWidget) - { - mDocumentWidget = new QWidget(mMainWindow->centralWidget()); - mMainWindow->addDocumentsWidget(mDocumentWidget); + QString trashName = UBSettings::trashedDocumentGroupNamePrefix; - mDocumentUI = new Ui::documents(); + UBDocumentTreeNode *myDocsNode = new UBDocumentTreeNode(UBDocumentTreeNode::Catalog, UBPersistenceManager::myDocumentsName, tr("My documents")); + rootNode->addChild(myDocsNode); + UBDocumentTreeNode *modelsNode = new UBDocumentTreeNode(UBDocumentTreeNode::Catalog, UBPersistenceManager::modelsName, tr("Models")); + rootNode->addChild(modelsNode); + UBDocumentTreeNode *trashNode = new UBDocumentTreeNode(UBDocumentTreeNode::Catalog, trashName, tr("Trash")); + rootNode->addChild(trashNode); + UBDocumentTreeNode *untitledDocumentsNode = new UBDocumentTreeNode(UBDocumentTreeNode::Catalog, UBPersistenceManager::untitledDocumentsName, tr("Untitled documents")); + myDocsNode->addChild(untitledDocumentsNode); - mDocumentUI->setupUi(mDocumentWidget); + setRootNode(rootNode); - int thumbWidth = UBSettings::settings()->documentThumbnailWidth->get().toInt(); + mRoot = index(0, 0, QModelIndex()); + mMyDocuments = index(0, 0, QModelIndex()); + mModels = index(1, 0, QModelIndex()); + mTrash = index(2, 0, QModelIndex()); + mUntitledDocuments = index(0, 0, mMyDocuments); + mAscendingOrder = true; +} - mDocumentUI->documentZoomSlider->setValue(thumbWidth); - mDocumentUI->thumbnailWidget->setThumbnailWidth(thumbWidth); +QModelIndex UBDocumentTreeModel::index(int row, int column, const QModelIndex &parent) const +{ + if (!mRootNode || row < 0 || column < 0) { + return QModelIndex(); + } - connect(mDocumentUI->documentZoomSlider, SIGNAL(valueChanged(int)), this, - SLOT(documentZoomSliderValueChanged(int))); + UBDocumentTreeNode *nodeParent = nodeFromIndex(parent); + if (!nodeParent || row > nodeParent->children().count() - 1) { + return QModelIndex(); + } - connect(mMainWindow->actionOpen, SIGNAL(triggered()), this, SLOT(openSelectedItem())); - connect(mMainWindow->actionNewFolder, SIGNAL(triggered()), this, SLOT(createNewDocumentGroup())); - connect(mMainWindow->actionNewDocument, SIGNAL(triggered()), this, SLOT(createNewDocument())); + UBDocumentTreeNode *requiredNode = nodeParent->children().at(row); + if(!requiredNode) { + return QModelIndex(); + } - connect(mMainWindow->actionImport, SIGNAL(triggered(bool)), this, SLOT(importFile())); + QModelIndex resIndex = createIndex(row, column, requiredNode); - QMenu* addMenu = new QMenu(mDocumentWidget); - mAddFolderOfImagesAction = addMenu->addAction(tr("Add Folder of Images")); - mAddImagesAction = addMenu->addAction(tr("Add Images")); - mAddFileToDocumentAction = addMenu->addAction(tr("Add Pages from File")); + return resIndex; +} - connect(mAddFolderOfImagesAction, SIGNAL(triggered(bool)), this, SLOT(addFolderOfImages())); - connect(mAddFileToDocumentAction, SIGNAL(triggered(bool)), this, SLOT(addFileToDocument())); - connect(mAddImagesAction, SIGNAL(triggered(bool)), this, SLOT(addImages())); +QModelIndex UBDocumentTreeModel::parent(const QModelIndex &child) const +{ + UBDocumentTreeNode *nodeChild = nodeFromIndex(child); + if (!nodeChild) { + return QModelIndex(); + } - foreach (QWidget* menuWidget, mMainWindow->actionDocumentAdd->associatedWidgets()) - { - QToolButton *tb = qobject_cast(menuWidget); + UBDocumentTreeNode *nodeParent = nodeChild->parentNode(); + if (!nodeParent) { + return QModelIndex(); + } - if (tb && !tb->menu()) - { - tb->setObjectName("ubButtonMenu"); - tb->setPopupMode(QToolButton::InstantPopup); + UBDocumentTreeNode *nodePreParent = nodeParent->parentNode(); + if (!nodePreParent) { + return QModelIndex(); + } - QMenu* menu = new QMenu(mDocumentWidget); + int row = nodePreParent->children().indexOf(nodeParent); - menu->addAction(mAddFolderOfImagesAction); - menu->addAction(mAddImagesAction); - menu->addAction(mAddFileToDocumentAction); + QModelIndex resIndex = createIndex(row, 0, nodeParent); - tb->setMenu(menu); - } - } + return resIndex; +} - QMenu* exportMenu = new QMenu(mDocumentWidget); +int UBDocumentTreeModel::rowCount(const QModelIndex &parent) const +{ + if (parent.column() > 0) { + return 0; + } - UBDocumentManager *documentManager = UBDocumentManager::documentManager(); - for (int i = 0; i < documentManager->supportedExportAdaptors().length(); i++) - { - UBExportAdaptor* adaptor = documentManager->supportedExportAdaptors()[i]; - QAction *currentExportAction = exportMenu->addAction(adaptor->exportName()); - currentExportAction->setData(i); - connect(currentExportAction, SIGNAL(triggered (bool)), this, SLOT(exportDocument())); - exportMenu->addAction(currentExportAction); - } + UBDocumentTreeNode *nodeParent = nodeFromIndex(parent); + if (!nodeParent) { + return 0; + } - foreach (QWidget* menuWidget, mMainWindow->actionExport->associatedWidgets()) - { - QToolButton *tb = qobject_cast(menuWidget); + return nodeParent->children().count(); +} - if (tb && !tb->menu()) - { - tb->setObjectName("ubButtonMenu"); - tb->setPopupMode(QToolButton::InstantPopup); +int UBDocumentTreeModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) - tb->setMenu(exportMenu); - } - } + //N/C - NNE - 20140403 + return 3; +} -#ifdef Q_OS_OSX - mMainWindow->actionDelete->setShortcut(QKeySequence(Qt::Key_Backspace)); -#else - mMainWindow->actionDelete->setShortcut(QKeySequence(Qt::Key_Delete)); -#endif +QVariant UBDocumentTreeModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + return QVariant(); + } - connect(mMainWindow->actionDelete, SIGNAL(triggered()), this, SLOT(deleteSelectedItem())); - connect(mMainWindow->actionDuplicate, SIGNAL(triggered()), this, SLOT(duplicateSelectedItem())); - connect(mMainWindow->actionRename, SIGNAL(triggered()), this, SLOT(renameSelectedItem())); - connect(mMainWindow->actionAddToWorkingDocument, SIGNAL(triggered()), this, SLOT(addToDocument())); + UBDocumentTreeNode *dataNode = nodeFromIndex(index); - loadDocumentProxies(); + if (!dataNode) + return QVariant(); - mDocumentUI->documentTreeWidget->setSelectionMode(QAbstractItemView::ExtendedSelection); - mDocumentUI->documentTreeWidget->setDragEnabled(true); - mDocumentUI->documentTreeWidget->viewport()->setAcceptDrops(true); - mDocumentUI->documentTreeWidget->setDropIndicatorShown(true); - mDocumentUI->documentTreeWidget->setIndentation(18); // 1.5 * /resources/style/treeview-branch-closed.png width - mDocumentUI->documentTreeWidget->setDragDropMode(QAbstractItemView::InternalMove); - connect(mDocumentUI->documentTreeWidget, SIGNAL(itemSelectionChanged()), this, SLOT(itemSelectionChanged())); - connect(mDocumentUI->documentTreeWidget, SIGNAL(itemChanged(QTreeWidgetItem *, int)), this, SLOT(itemChanged(QTreeWidgetItem *, int))); + if(role == Qt::DisplayRole){ + if(index.column() == 0){ + return dataNode->displayName(); + }else{ + UBDocumentProxy *proxy = proxyForIndex(index); - connect(mDocumentUI->thumbnailWidget, SIGNAL(sceneDropped(UBDocumentProxy*, int, int)), this, SLOT(moveSceneToIndex ( UBDocumentProxy*, int, int))); - connect(mDocumentUI->thumbnailWidget, SIGNAL(resized()), this, SLOT(thumbnailViewResized())); - connect(mDocumentUI->thumbnailWidget, SIGNAL(mouseDoubleClick(QGraphicsItem*, int)), this, SLOT(pageDoubleClicked(QGraphicsItem*, int))); - connect(mDocumentUI->thumbnailWidget, SIGNAL(mouseClick(QGraphicsItem*, int)), this, SLOT(pageClicked(QGraphicsItem*, int))); + QString displayText = ""; - connect(mDocumentUI->thumbnailWidget->scene(), SIGNAL(selectionChanged()), this, SLOT(pageSelectionChanged())); + if(proxy){ + QDateTime d; - connect(UBPersistenceManager::persistenceManager(), SIGNAL(documentCreated(UBDocumentProxy*)), this, SLOT(addDocumentInTree(UBDocumentProxy*))); + if(index.column() == 1){ + d = proxy->metaData(UBSettings::documentDate).toDateTime(); + }else if(index.column() == 2){ + d = proxy->metaData(UBSettings::documentUpdatedAt).toDateTime(); + } - connect(UBPersistenceManager::persistenceManager(), SIGNAL(documentMetadataChanged(UBDocumentProxy*)), this, SLOT(updateDocumentInTree(UBDocumentProxy*))); + displayText = d.toString("dd/MM/yyyy hh:mm"); + } - connect(UBPersistenceManager::persistenceManager(), SIGNAL(documentSceneCreated(UBDocumentProxy*, int)), this, SLOT(documentSceneChanged(UBDocumentProxy*, int))); + return displayText; - connect(UBPersistenceManager::persistenceManager(), SIGNAL(documentSceneWillBeDeleted(UBDocumentProxy*, int)), this, SLOT(documentSceneChanged(UBDocumentProxy*, int))); + } + } - mDocumentUI->thumbnailWidget->setBackgroundBrush(UBSettings::documentViewLightColor); + if(role == UBDocumentTreeModel::CreationDate){ + return findNodeDate(dataNode, UBSettings::documentDate); + } - #ifdef Q_OS_OSX - mMessageWindow = new UBMessageWindow(NULL); - #else - mMessageWindow = new UBMessageWindow(mDocumentUI->thumbnailWidget); - #endif + if(role == UBDocumentTreeModel::UpdateDate){ + return findNodeDate(dataNode, UBSettings::documentUpdatedAt); + } - mMessageWindow->hide(); + if(role == Qt::BackgroundRole){ + if (isConstant(index) || dataNode->nodeType() == UBDocumentTreeNode::Catalog) { + return QBrush(0xD9DFEB); + } + if (mHighLighted.isValid() && index == mHighLighted) { + return QBrush(0x6682B5); + } } -} + if(role == Qt::UserRole +1){ + return QVariant::fromValue(dataNode); + } -QWidget* UBDocumentController::controlView() -{ - return mDocumentWidget; -} + if (index.column() == 0) { + switch (role) { + case (Qt::DecorationRole) : + if (mCurrentNode && mCurrentNode == dataNode) { + return QIcon(":images/currentDocument.png"); + } else { + if (index == trashIndex()) { + return QIcon(":images/trash.png"); + } else if (isConstant(index)) { + return QIcon(":images/libpalette/ApplicationsCategory.svg"); + } + switch (static_cast(dataNode->nodeType())) { + case UBDocumentTreeNode::Catalog : + return QIcon(":images/folder.png"); + case UBDocumentTreeNode::Document : + return QIcon(":images/toolbar/board.png"); + } + } + break; + case (Qt::FontRole) : + if (isConstant(index)) { + QFont font; + font.setBold(true); + return font; + } + break; + case (Qt::ForegroundRole) : + if (isConstant(index)) { + return QColor(Qt::darkGray); + } + break; + } + } + return QVariant(); +} -void UBDocumentController::setupToolbar() +bool UBDocumentTreeModel::setData(const QModelIndex &index, const QVariant &value, int role) { - UBApplication::app()->insertSpaceToToolbarBeforeAction(mMainWindow->documentToolBar, mMainWindow->actionBoard); - connect(mMainWindow->actionDocumentTools, SIGNAL(triggered()), this, SLOT(toggleDocumentToolsPalette())); + switch (role) { + case Qt::EditRole: + if (!index.isValid() || value.toString().isEmpty()) { + return false; + } + setNewName(index, value.toString()); + return true; + } + return QAbstractItemModel::setData(index, value, role); } -void UBDocumentController::setupPalettes() +Qt::ItemFlags UBDocumentTreeModel::flags (const QModelIndex &index) const { + Qt::ItemFlags resultFlags = QAbstractItemModel::flags(index); + UBDocumentTreeNode *indexNode = nodeFromIndex(index); - mToolsPalette = new UBDocumentToolsPalette(controlView()); - - mToolsPalette->hide(); + if ( index.isValid() ) { + if (!indexNode->isRoot() && !isConstant(index)) { + if (!inTrash(index)) { + resultFlags |= Qt::ItemIsEditable; + } + resultFlags |= Qt::ItemIsDragEnabled; + } + if (indexNode->nodeType() == UBDocumentTreeNode::Catalog) { + resultFlags |= Qt::ItemIsDropEnabled; + } + } - bool showToolsPalette = !mToolsPalette->isEmpty(); - mMainWindow->actionDocumentTools->setVisible(showToolsPalette); + return resultFlags; +} + +//N/C - NNE -20140407 +QDateTime UBDocumentTreeModel::findNodeDate(UBDocumentTreeNode *node, QString type) const +{ + if(type == UBSettings::documentDate){ + return findCatalogCreationDate(node); + }else if(type == UBSettings::documentUpdatedAt){ + return findCatalogUpdatedDate(node); + } + + return QDateTime(); +} + +QDateTime UBDocumentTreeModel::findCatalogUpdatedDate(UBDocumentTreeNode *node) const +{ + UBDocumentProxy *proxy = node->proxyData(); + + if(proxy){ + return proxy->metaData(UBSettings::documentUpdatedAt).toDateTime(); + }else if(node->children().size() > 0){ + QDateTime d = findCatalogUpdatedDate(node->children().at(0)); + + for(int i = 1; i < node->children().size(); i++){ + QDateTime dChild = findCatalogUpdatedDate(node->children().at(i)); + + if(dChild != QDateTime()){ + if(mAscendingOrder){ + d = qMin(d, dChild); + }else{ + d = qMax(d, dChild); + } + } + + } + + return d; + } + + return QDateTime(); +} + +QDateTime UBDocumentTreeModel::findCatalogCreationDate(UBDocumentTreeNode *node) const +{ + UBDocumentProxy *proxy = node->proxyData(); + + if(proxy){ + return proxy->metaData(UBSettings::documentDate).toDateTime(); + }else if(node->children().size() > 0){ + QDateTime d = findCatalogCreationDate(node->children().at(0)); + + for(int i = 1; i < node->children().size(); i++){ + QDateTime dChild = findCatalogCreationDate(node->children().at(i)); + + if(dChild != QDateTime()){ + if(mAscendingOrder){ + d = qMin(d, dChild); + }else{ + d = qMax(d, dChild); + } + } + + } + + return d; + } + + return QDateTime(); +} +//N/C - NNE -20140407 : END + +QStringList UBDocumentTreeModel::mimeTypes() const +{ + QStringList types; + types << "text/uri-list" << "image/png" << "image/tiff" << "image/gif" << "image/jpeg"; + return types; +} + +QMimeData *UBDocumentTreeModel::mimeData (const QModelIndexList &indexes) const +{ + UBDocumentTreeMimeData *mimeData = new UBDocumentTreeMimeData(); + QList indexList; + QList urlList; + + foreach (QModelIndex index, indexes) { + if (index.isValid()) { + indexList.append(index); + urlList.append(QUrl()); + } + } + + mimeData->setUrls(urlList); + mimeData->setIndexes(indexList); + + return mimeData; +} + +bool UBDocumentTreeModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) +{ + if (action == Qt::IgnoreAction) { + return false; + } + + if (data->hasFormat(UBApplication::mimeTypeUniboardPage)) { + UBDocumentTreeNode *curNode = nodeFromIndex(index(row - 1, column, parent)); + UBDocumentProxy *targetDocProxy = curNode->proxyData(); + const UBMimeData *ubMime = qobject_cast (data); + if (!targetDocProxy || !ubMime || !ubMime->items().count()) { + qDebug() << "an error ocured while parsing " << UBApplication::mimeTypeUniboardPage; + return false; + } + +// int count = 0; + int total = ubMime->items().size(); + + QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + + foreach (UBMimeDataItem sourceItem, ubMime->items()) + { + UBDocumentProxy *fromProxy = sourceItem.documentProxy(); + int fromIndex = sourceItem.sceneIndex(); + int toIndex = targetDocProxy->pageCount(); + + UBPersistenceManager::persistenceManager()->copyDocumentScene(fromProxy, fromIndex, + targetDocProxy, toIndex); + } + + QApplication::restoreOverrideCursor(); + + UBApplication::applicationController->showMessage(tr("%1 pages copied", "", total).arg(total), false); + + return true; + } + + const UBDocumentTreeMimeData *mimeData = qobject_cast(data); + if (!mimeData) { + qDebug() << "Incorrect mimeData, only internal one supported"; + return false; + } + + if (!parent.isValid()) { + return false; + } + + UBDocumentTreeNode *newParentNode = nodeFromIndex(parent); + + if (!newParentNode) { + qDebug() << "incorrect incoming parent node;"; + return false; + } + + QList incomingIndexes = mimeData->indexes(); + + foreach (QModelIndex curIndex, incomingIndexes) { +#ifdef Q_WS_MAC + if (inModel(curIndex)) { + return true; + } +#endif + + //Issue N/C - NNE - 20140528 : use just the index on the first column + if(curIndex.column() == 0){ + QModelIndex clonedTopLevel = copyIndexToNewParent(curIndex, parent, action == Qt::MoveAction ? aReference : aContentCopy); + if (nodeFromIndex(curIndex) == mCurrentNode && action == Qt::MoveAction) { + emit currentIndexMoved(clonedTopLevel, curIndex); + } + } + } + + Q_UNUSED(action) + Q_UNUSED(row) + Q_UNUSED(column) + Q_UNUSED(parent) + + return true; +} + +bool UBDocumentTreeModel::removeRows(int row, int count, const QModelIndex &parent) +{ + if (row < 0 || row + count > rowCount(parent)) + return false; + + beginRemoveRows( parent, row, row + count - 1); + + UBDocumentTreeNode *parentNode = nodeFromIndex(parent); + for (int i = row; i < row + count; i++) { + UBDocumentTreeNode *curChildNode = parentNode->children().at(i); + QModelIndex curChildIndex = parent.child(i, 0); + if (curChildNode) { + if (rowCount(curChildIndex)) { + while (rowCount(curChildIndex)) { + removeRows(0, 1, curChildIndex); + } + } + } + mNewDocuments.removeAll(curChildNode->proxyData()); + parentNode->removeChild(i); + + } + + endRemoveRows(); + return true; +} + +QModelIndex UBDocumentTreeModel::indexForNode(UBDocumentTreeNode *pNode) const +{ + if (pNode == 0) { + return QModelIndex(); + } + + return pIndexForNode(QModelIndex(), pNode); +} + +QPersistentModelIndex UBDocumentTreeModel::persistentIndexForNode(UBDocumentTreeNode *pNode) +{ + return QPersistentModelIndex(indexForNode(pNode)); +} + +UBDocumentTreeNode *UBDocumentTreeModel::findProxy(UBDocumentProxy *pSearch, UBDocumentTreeNode *pParent) const +{ + foreach (UBDocumentTreeNode *curNode, pParent->children()) + { + if (UBDocumentTreeNode::Catalog != curNode->nodeType()) + { + if (curNode->proxyData()->theSameDocument(pSearch)) + return curNode; + } + else if (curNode->children().count()) + { + UBDocumentTreeNode *recursiveDescendResult = findProxy(pSearch, curNode); + if (recursiveDescendResult) + return findProxy(pSearch, curNode); + } + } + + return 0; +} + +QModelIndex UBDocumentTreeModel::pIndexForNode(const QModelIndex &parent, UBDocumentTreeNode *pNode) const +{ + for (int i = 0; i < rowCount(parent); i++) { + QModelIndex curIndex = index(i, 0, parent); + if (curIndex.internalPointer() == pNode) { + return curIndex; + } else if (rowCount(curIndex) > 0) { + QModelIndex recursiveDescendIndex = pIndexForNode(curIndex, pNode); + if (recursiveDescendIndex.isValid()) { + return recursiveDescendIndex; + } + } + } + return QModelIndex(); +} + +//N/C - NNE - 20140411 +void UBDocumentTreeModel::copyIndexToNewParent(const QModelIndexList &list, const QModelIndex &newParent, eCopyMode pMode) +{ + for(int i = 0; i < list.size(); i++){ + if(list.at(i).column() == 0){ + copyIndexToNewParent(list.at(i), newParent, pMode); + } + } +} +//N/C - NNE - 20140411 : END + +QPersistentModelIndex UBDocumentTreeModel::copyIndexToNewParent(const QModelIndex &source, const QModelIndex &newParent, eCopyMode pMode) +{ + UBDocumentTreeNode *nodeParent = nodeFromIndex(newParent); + UBDocumentTreeNode *nodeSource = nodeFromIndex(source); + + if (!nodeParent || !nodeSource) { + return QModelIndex(); + } + + //beginInsertRows(newParent, rowCount(newParent), rowCount(newParent)); + + UBDocumentTreeNode *clonedNodeSource = 0; + switch (static_cast(pMode)) { + case aReference: + clonedNodeSource = nodeSource->clone(); + if (mNewDocuments.contains(nodeSource->proxyData())) { //update references for session documents + mNewDocuments << clonedNodeSource->proxyData(); + + UBPersistenceManager::persistenceManager()->reassignDocProxy(clonedNodeSource->proxyData(), nodeSource->proxyData()); + } + break; + + case aContentCopy: + UBDocumentProxy* duplicatedProxy = 0; + if (nodeSource->nodeType() == UBDocumentTreeNode::Document && nodeSource->proxyData()) { + duplicatedProxy = UBPersistenceManager::persistenceManager()->duplicateDocument(nodeSource->proxyData()); + duplicatedProxy->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(QDateTime::currentDateTime())); + UBMetadataDcSubsetAdaptor::persist(duplicatedProxy); + } + clonedNodeSource = new UBDocumentTreeNode(nodeSource->nodeType() + , nodeSource->nodeName() + , nodeSource->displayName() + , duplicatedProxy); + break; + } + + // Determine whether to provide a name with postfix if the name in current level already exists + QString newName = clonedNodeSource->nodeName(); + if ((source.parent() != newParent + || pMode != aReference) + && (newParent != trashIndex() || !inTrash(newParent))) { + newName = adjustNameForParentIndex(newName, newParent); + clonedNodeSource->setNodeName(newName); + } + + if (clonedNodeSource->proxyData()) { + clonedNodeSource->proxyData()->setMetaData(UBSettings::documentGroupName, virtualPathForIndex(newParent)); + clonedNodeSource->proxyData()->setMetaData(UBSettings::documentName, newName); + UBPersistenceManager::persistenceManager()->persistDocumentMetadata(clonedNodeSource->proxyData()); + } + + addNode(clonedNodeSource, newParent); + //endInsertRows(); + + QPersistentModelIndex newParentIndex = createIndex(rowCount(newParent), 0, clonedNodeSource); + + if (rowCount(source)) { + for (int i = 0; i < rowCount(source); i++) { + QModelIndex curNewParentIndexChild = source.child(i, 0); + copyIndexToNewParent(curNewParentIndexChild, newParentIndex, pMode); + } + } + + return newParentIndex; +} + +//N/C - NNE - 20140409 +void UBDocumentTreeModel::moveIndexes(const QModelIndexList &source, const QModelIndex &destination) +{ + //Issue N/C - NNE - 20140528 + QModelIndex destinationParent = destination; + while(!isCatalog(destinationParent)){ + destinationParent = destinationParent.parent(); + } + + UBDocumentTreeNode *newParentNode = nodeFromIndex(destinationParent); + + bool hasOneInsertion = false; + + for(int i = 0; i < source.size(); i++){ + UBDocumentTreeNode *sourceNode = nodeFromIndex(source.at(i)); + QModelIndex s = source.at(i); + + if(newParentNode == sourceNode->parentNode() || sourceNode->findNode(newParentNode)) + continue; + + if(s.internalId() != destinationParent.internalId()){ + int sourceIndex = source.at(i).row(); + int destIndex = positionForParent(sourceNode, newParentNode); + + beginMoveRows(s.parent(), sourceIndex, sourceIndex, destinationParent, destIndex); + fixNodeName(s, destinationParent); + sourceNode->parentNode()->moveChild(sourceNode, destIndex, newParentNode); + updateIndexNameBindings(sourceNode); + + hasOneInsertion = true; + } + } + + if(hasOneInsertion) + endMoveRows(); +} + +//N/C - NNE - 20140409 + +void UBDocumentTreeModel::moveIndex(const QModelIndex &what, const QModelIndex &destination) +{ + QModelIndexList list; + list.push_back(what); + moveIndexes(list, destination); +} + +void UBDocumentTreeModel::setCurrentDocument(UBDocumentProxy *pDocument) +{ + UBDocumentTreeNode *testCurNode = findProxy(pDocument, mRootNode); + + if (testCurNode) { + setCurrentNode(testCurNode); + } +} + +QModelIndex UBDocumentTreeModel::indexForProxy(UBDocumentProxy *pSearch) const +{ + UBDocumentTreeNode *proxy = findProxy(pSearch, mRootNode); + if (!proxy) { + return QModelIndex(); + } + + return indexForNode(proxy); +} + +void UBDocumentTreeModel::setRootNode(UBDocumentTreeNode *pRoot) +{ + mRootNode = pRoot; + //reset(); +} + +UBDocumentProxy *UBDocumentTreeModel::proxyForIndex(const QModelIndex &pIndex) const +{ + UBDocumentTreeNode *node = nodeFromIndex(pIndex); + if (!node) { + return 0; + } + + return node->proxyData(); +} + +QString UBDocumentTreeModel::virtualDirForIndex(const QModelIndex &pIndex) const +{ + QString result; + UBDocumentTreeNode *curNode = nodeFromIndex(pIndex); + //protect the 2nd level items + while (curNode->parentNode() && !curNode->isTopLevel()) { + result.prepend(curNode->parentNode()->nodeName() + "/"); + curNode = curNode->parentNode(); + } + + if (result.endsWith("/")) { + result.truncate(result.count() - 1); + } + + return result; +} + +QString UBDocumentTreeModel::virtualPathForIndex(const QModelIndex &pIndex) const +{ + UBDocumentTreeNode *curNode = nodeFromIndex(pIndex); + Q_ASSERT(curNode); + + return virtualDirForIndex(pIndex) + "/" + curNode->nodeName(); +} + +QStringList UBDocumentTreeModel::nodeNameList(const QModelIndex &pIndex) const +{ + QStringList result; + + UBDocumentTreeNode *catalog = nodeFromIndex(pIndex); + if (catalog->nodeType() != UBDocumentTreeNode::Catalog) { + return QStringList(); + } + + foreach (UBDocumentTreeNode *curNode, catalog->children()) { + result << curNode->nodeName(); + } + + return result; +} + +bool UBDocumentTreeModel::newNodeAllowed(const QModelIndex &pSelectedIndex) const +{ + if (!pSelectedIndex.isValid()) { + return false; + } + + if (inTrash(pSelectedIndex) || pSelectedIndex == trashIndex()) { + return false; + } + + return true; +} + +QModelIndex UBDocumentTreeModel::goTo(const QString &dir) +{ + QStringList pathList = dir.split("/", QString::SkipEmptyParts); + + if (pathList.isEmpty()) { + return untitledDocumentsIndex(); + } + + if (pathList.first() != UBPersistenceManager::myDocumentsName + && pathList.first() != UBSettings::trashedDocumentGroupNamePrefix + && pathList.first() != UBPersistenceManager::modelsName) { + pathList.prepend(UBPersistenceManager::myDocumentsName); + } + + QModelIndex parentIndex; + + bool searchingNode = true; + while (!pathList.isEmpty()) + { + QString curLevelName = pathList.takeFirst(); + if (searchingNode) { + searchingNode = false; + for (int i = 0; i < rowCount(parentIndex); ++i) { + QModelIndex curChildIndex = index(i, 0, parentIndex); + if (nodeFromIndex(curChildIndex)->nodeName() == curLevelName) { + searchingNode = true; + parentIndex = curChildIndex; + break; + } + } + } + + if (!searchingNode) { + UBDocumentTreeNode *newChild = new UBDocumentTreeNode(UBDocumentTreeNode::Catalog, curLevelName); + parentIndex = addNode(newChild, parentIndex); + } + } + + return parentIndex; +} + +bool UBDocumentTreeModel::inTrash(const QModelIndex &index) const +{ + return isDescendantOf(index, trashIndex()); +} + +bool UBDocumentTreeModel::inModel(const QModelIndex &index) const +{ + return isDescendantOf(index, modelsIndex()); +} + +bool UBDocumentTreeModel::inUntitledDocuments(const QModelIndex &index) const +{ + return isDescendantOf(index, untitledDocumentsIndex()); +} + +//N/C - NNE - 20140408 +bool UBDocumentTreeModel::inMyDocuments(const QModelIndex &index) const +{ + return isDescendantOf(index, myDocumentsIndex()); +} +//N/C - NNE - 20140408 : END + +void UBDocumentTreeModel::addDocument(UBDocumentProxy *pProxyData, const QModelIndex &pParent) +{ + if (!pProxyData) { + return; + } + QString docName = pProxyData->metaData(UBSettings::documentName).toString(); + QString docGroupName = pProxyData->metaData(UBSettings::documentGroupName).toString(); + + if (docName.isEmpty()) { + return; + } + + QModelIndex lParent = pParent; + UBDocumentTreeNode *freeNode = new UBDocumentTreeNode(UBDocumentTreeNode::Document + , docName + , QString() + , pProxyData); + if (!pParent.isValid()) { + lParent = goTo(docGroupName); + } + + addNode(freeNode, lParent); +} + +void UBDocumentTreeModel::addNewDocument(UBDocumentProxy *pProxyData, const QModelIndex &pParent) +{ + addDocument(pProxyData, pParent); + mNewDocuments << pProxyData; +} + +void UBDocumentTreeModel::addCatalog(const QString &pName, const QModelIndex &pParent) +{ + if (pName.isEmpty() || !pParent.isValid()) { + return; + } + + UBDocumentTreeNode *catalogNode = new UBDocumentTreeNode(UBDocumentTreeNode::Catalog, pName); + addNode(catalogNode, pParent); +} + +void UBDocumentTreeModel::setNewName(const QModelIndex &index, const QString &newName) +{ + if (!index.isValid()) { + return; + } + + UBDocumentTreeNode *indexNode = nodeFromIndex(index); + + QString magicSeparator = "+!##s"; + if (isCatalog(index)) { + QString fullNewName = newName; + if (!newName.contains(magicSeparator)) { + indexNode->setNodeName(newName); + QString virtualDir = virtualDirForIndex(index); + fullNewName.prepend(virtualDir.isEmpty() ? "" : virtualDir + magicSeparator); + } + for (int i = 0; i < rowCount(index); i++) { + QModelIndex subIndex = this->index(i, 0, index); + setNewName(subIndex, fullNewName + magicSeparator + subIndex.data().toString()); + } + + } else if (isDocument(index)) { + Q_ASSERT(indexNode->proxyData()); + + int prefixIndex = newName.lastIndexOf(magicSeparator); + if (prefixIndex != -1) { + QString newDocumentGroupName = newName.left(prefixIndex).replace(magicSeparator, "/"); + indexNode->proxyData()->setMetaData(UBSettings::documentGroupName, newDocumentGroupName); + } else { + indexNode->setNodeName(newName); + indexNode->proxyData()->setMetaData(UBSettings::documentName, newName); + } + + UBPersistenceManager::persistenceManager()->persistDocumentMetadata(indexNode->proxyData()); + } +} + +QString UBDocumentTreeModel::adjustNameForParentIndex(const QString &pName, const QModelIndex &pIndex) +{ + int i = 0; + QString newName = pName; + QStringList siblingNames = nodeNameList(pIndex); + while (siblingNames.contains(newName)) { + newName = pName + " " + QVariant(++i).toString(); + } + + return newName; +} + +void UBDocumentTreeModel::fixNodeName(const QModelIndex &source, const QModelIndex &dest) +{ + // Determine whether to provide a name with postfix if the name in current level allready exists + UBDocumentTreeNode *srcNode = nodeFromIndex(source); + Q_ASSERT(srcNode); + + QString newName = srcNode->nodeName(); + if (source.parent() != dest + && (dest != trashIndex() + || !inTrash(dest))) { + newName = adjustNameForParentIndex(newName, dest); + srcNode->setNodeName(newName); + nodeFromIndex(source)->setNodeName(newName); + } +} + +void UBDocumentTreeModel::updateIndexNameBindings(UBDocumentTreeNode *nd) +{ + Q_ASSERT(nd); + + if (nd->nodeType() == UBDocumentTreeNode::Catalog) { + foreach (UBDocumentTreeNode *lnd, nd->children()) { + updateIndexNameBindings(lnd); + } + } else if (nd->proxyData()) { + nd->proxyData()->setMetaData(UBSettings::documentGroupName, virtualPathForIndex(indexForNode(nd->parentNode()))); + nd->proxyData()->setMetaData(UBSettings::documentName, nd->nodeName()); + UBPersistenceManager::persistenceManager()->persistDocumentMetadata(nd->proxyData()); + } +} + +bool UBDocumentTreeModel::isDescendantOf(const QModelIndex &pPossibleDescendant, const QModelIndex &pPossibleAncestor) const +{ + if (!pPossibleDescendant.isValid()) { + return false; + } + + QModelIndex ancestor = pPossibleDescendant; + while (ancestor.parent().isValid()) { + ancestor = ancestor.parent(); + if (ancestor == pPossibleAncestor) { + return true; + } + } + + return false; +} + +QModelIndex UBDocumentTreeModel::addNode(UBDocumentTreeNode *pFreeNode, const QModelIndex &pParent, eAddItemMode pMode) +{ + UBDocumentTreeNode *tstParent = nodeFromIndex(pParent); + + if (!pParent.isValid() || tstParent->nodeType() != UBDocumentTreeNode::Catalog) { + return QModelIndex(); + } + int newIndex = pMode == aDetectPosition ? positionForParent(pFreeNode, tstParent): tstParent->children().size(); + beginInsertRows(pParent, newIndex, newIndex); + tstParent->insertChild(newIndex, pFreeNode); + endInsertRows(); + + return createIndex(newIndex, 0, pFreeNode); +} + +int UBDocumentTreeModel::positionForParent(UBDocumentTreeNode *pFreeNode, UBDocumentTreeNode *pParentNode) +{ + Q_ASSERT(pFreeNode); + Q_ASSERT(pParentNode); + Q_ASSERT(pParentNode->nodeType() == UBDocumentTreeNode::Catalog); + + int c = -1; + int childCount = pParentNode->children().count(); + while (c <= childCount) { + if (++c == childCount || lessThan(pFreeNode, pParentNode->children().at(c))) { + break; + } + } + return c == -1 ? childCount : c; +} + +UBDocumentTreeNode *UBDocumentTreeModel::nodeFromIndex(const QModelIndex &pIndex) const +{ + if (pIndex.isValid()) { + return static_cast(pIndex.internalPointer()); + } else { + return mRootNode; + } +} + +bool UBDocumentTreeModel::nodeLessThan(const UBDocumentTreeNode *firstIndex, const UBDocumentTreeNode *secondIndex) +{ + return firstIndex->nodeName() < secondIndex->nodeName(); +} + +UBDocumentTreeModel::~UBDocumentTreeModel() +{ + delete mRootNode; +} + +UBDocumentTreeView::UBDocumentTreeView(QWidget *parent) : QTreeView(parent) +{ + setObjectName("UBDocumentTreeView"); + setRootIsDecorated(true); +} + +void UBDocumentTreeView::setSelectedAndExpanded(const QModelIndex &pIndex, bool pExpand) +{ + if (!pIndex.isValid()) { + return; + } + + QModelIndex indexCurrentDoc = pIndex; + clearSelection(); + + UBSortFilterProxyModel *proxy = dynamic_cast(model()); + + QItemSelectionModel::SelectionFlags sel = pExpand + ? QItemSelectionModel::Select + : QItemSelectionModel::Deselect; + + selectionModel()->select(proxy->mapFromSource(indexCurrentDoc), QItemSelectionModel::Rows | sel); + + setCurrentIndex(pExpand + ? indexCurrentDoc + : QModelIndex()); + + while (indexCurrentDoc.parent().isValid()) { + setExpanded(indexCurrentDoc.parent(), pExpand); + indexCurrentDoc = indexCurrentDoc.parent(); + } + + scrollTo(proxy->mapFromSource(pIndex), QAbstractItemView::PositionAtCenter); +} + +void UBDocumentTreeView::onModelIndexChanged(const QModelIndex &pNewIndex, const QModelIndex &pOldIndex) +{ + Q_UNUSED(pOldIndex) + + //N/C - NNE - 20140407 + QModelIndex indexSource = mapIndexToSource(pNewIndex); + + setSelectedAndExpanded(indexSource, true); +} + +void UBDocumentTreeView::hSliderRangeChanged(int min, int max) +{ + Q_UNUSED(min); + Q_UNUSED(max); + + QScrollBar *hScroller = horizontalScrollBar(); + if (hScroller) + { + hScroller->triggerAction(QAbstractSlider::SliderToMaximum); + } +} + +void UBDocumentTreeView::dragEnterEvent(QDragEnterEvent *event) +{ + QTreeView::dragEnterEvent(event); + event->accept(); + event->acceptProposedAction(); +} + +void UBDocumentTreeView::dragLeaveEvent(QDragLeaveEvent *event) +{ + Q_UNUSED(event); + + UBDocumentTreeModel *docModel = 0; + + UBSortFilterProxyModel *proxy = dynamic_cast(model()); + + if(proxy){ + docModel = dynamic_cast(proxy->sourceModel()); + }else{ + docModel = dynamic_cast(model()); + } + + docModel->setHighLighted(QModelIndex()); + update(); +} + +void UBDocumentTreeView::dragMoveEvent(QDragMoveEvent *event) +{ + bool acceptIt = isAcceptable(selectedIndexes().first(), indexAt(event->pos())); + + if (event->mimeData()->hasFormat(UBApplication::mimeTypeUniboardPage)) { + UBSortFilterProxyModel *proxy = dynamic_cast(model()); + + UBDocumentTreeModel *docModel = 0; + + if(proxy){ + docModel = dynamic_cast(proxy->sourceModel()); + }else{ + docModel = dynamic_cast(model()); + } + + QModelIndex targetIndex = mapIndexToSource(indexAt(event->pos())); + + if (!docModel || !docModel->isDocument(targetIndex) || docModel->inTrash(targetIndex)) { + event->ignore(); + event->setDropAction(Qt::IgnoreAction); + docModel->setHighLighted(QModelIndex()); + acceptIt = false; + } else { + docModel->setHighLighted(targetIndex); + acceptIt = true; + } + updateIndexEnvirons(indexAt(event->pos())); + } + QTreeView::dragMoveEvent(event); + + event->setAccepted(acceptIt); +} + +void UBDocumentTreeView::dropEvent(QDropEvent *event) +{ + event->ignore(); + event->setDropAction(Qt::IgnoreAction); + UBDocumentTreeModel *docModel = 0; + + //N/C - NNE - 20140408 + UBSortFilterProxyModel *proxy = dynamic_cast(model()); + if(proxy){ + docModel = dynamic_cast(proxy->sourceModel()); + } + + QModelIndex targetIndex = mapIndexToSource(indexAt(event->pos())); + QModelIndexList dropIndex = mapIndexesToSource(selectedIndexes()); + + //clear the selection right after + selectionModel()->clearSelection(); + + bool isUBPage = event->mimeData()->hasFormat(UBApplication::mimeTypeUniboardPage); + + bool inModel = docModel->inModel(targetIndex) || targetIndex == docModel->modelsIndex(); + + //just check the first index, because the selection is exclusive between + //myDocuments, Model and Tash + bool isSourceAModel = docModel->inModel(dropIndex.first()); + + //issue 1629 - NNE - 20131212 + bool targetIsInTrash = docModel->inTrash(targetIndex) || docModel->trashIndex() == targetIndex; + + if (isUBPage) { + UBDocumentProxy *targetDocProxy = docModel->proxyData(targetIndex); + const UBMimeData *ubMime = qobject_cast (event->mimeData()); + if (!targetDocProxy || !ubMime || !ubMime->items().count()) { + qDebug() << "an error ocured while parsing " << UBApplication::mimeTypeUniboardPage; + QTreeView::dropEvent(event); + return; + } + + int total = ubMime->items().size(); + QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + + foreach (UBMimeDataItem sourceItem, ubMime->items()) + { + UBDocumentProxy *fromProxy = sourceItem.documentProxy(); + int fromIndex = sourceItem.sceneIndex(); + int toIndex = targetDocProxy->pageCount(); + + UBPersistenceManager::persistenceManager()->copyDocumentScene(fromProxy, fromIndex, + targetDocProxy, toIndex); + } + + QApplication::restoreOverrideCursor(); + UBApplication::applicationController->showMessage(tr("%1 pages copied", "", total).arg(total), false); + + docModel->setHighLighted(QModelIndex()); + } + else if(isSourceAModel){ + QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + + if(targetIsInTrash){ + //issue 1629 - NNE - 20131212 : If the source is a model and we want to delete it + UBApplication::documentController->moveIndexesToTrash(dropIndex, docModel); + }else{ + UBDocumentTreeNode* node = docModel->nodeFromIndex(targetIndex); + QModelIndex targetParentIndex; + if(node->nodeType() == UBDocumentTreeNode::Catalog) + targetParentIndex = docModel->indexForNode(node); + else + targetParentIndex = docModel->indexForNode(node->parentNode()); + + docModel->copyIndexToNewParent(dropIndex, targetParentIndex,UBDocumentTreeModel::aContentCopy); + docModel->setHighLighted(QModelIndex()); + } + + QApplication::restoreOverrideCursor(); + } + else if(!inModel){ + //issue 1632 - NNE - 20131212 + if(targetIsInTrash){ + UBApplication::documentController->moveIndexesToTrash(dropIndex, docModel); + }else{ + docModel->moveIndexes(dropIndex, targetIndex); + } + + expand(proxy->mapFromSource(targetIndex)); + + //issue 1632 - NNE - 20131212 : END + }else if(inModel){ + event->setDropAction(Qt::CopyAction); + } + + QTreeView::dropEvent(event); +} + +void UBDocumentTreeView::paintEvent(QPaintEvent *event) +{ + QTreeView::paintEvent(event); +} + +void UBDocumentTreeView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) +{ + QTreeView::rowsAboutToBeRemoved(parent, start, end); +} + +bool UBDocumentTreeView::isAcceptable(const QModelIndex &dragIndex, const QModelIndex &atIndex) +{ + QModelIndex dragIndexSource = mapIndexToSource(dragIndex); + QModelIndex atIndexSource = mapIndexToSource(atIndex); + + if (!dragIndexSource.isValid()) { + return false; + } + + if (fullModel() && fullModel()->inModel(dragIndexSource)) { + if (atIndexSource == fullModel()->modelsIndex() || fullModel()->inModel(atIndexSource)) { + return false; //do not accept drop from model to itself + } + } + + return true; +} + +Qt::DropAction UBDocumentTreeView::acceptableAction(const QModelIndex &dragIndex, const QModelIndex &atIndex) +{ + if (fullModel()->inModel(dragIndex)) { + if (atIndex == fullModel()->trashIndex() || fullModel()->inTrash(atIndex)) { + return Qt::MoveAction; //do not accept drop from model to trash, only "delete" command accepted + } + return Qt::CopyAction; + } else { + return Qt::MoveAction; + } + + return Qt::IgnoreAction; +} + +void UBDocumentTreeView::updateIndexEnvirons(const QModelIndex &index) +{ + QRect updateRect = visualRect(index); + const int multipler = 3; + updateRect.adjust(0, -updateRect.height() * multipler, 0, updateRect.height() * multipler); + update(updateRect); +} + +//N/C - NNE - 20140404 +QModelIndex UBDocumentTreeView::mapIndexToSource(const QModelIndex &index) +{ + UBSortFilterProxyModel *proxy = dynamic_cast(model()); + + if(proxy){ + return proxy->mapToSource(index); + } + + return index; +} + +QModelIndexList UBDocumentTreeView::mapIndexesToSource(const QModelIndexList &indexes) +{ + UBSortFilterProxyModel *proxy = dynamic_cast(model()); + + if(proxy){ + QModelIndexList list; + + for(int i = 0; i < indexes.size(); i++){ + list.push_back(proxy->mapToSource(indexes.at(i))); + } + + return list; + } + + return indexes; +} +//N/C - NNE - 20140404 : END + +UBDocumentTreeItemDelegate::UBDocumentTreeItemDelegate(QObject *parent) + : QStyledItemDelegate(parent) +{ + +} + +void UBDocumentTreeItemDelegate::commitAndCloseEditor() +{ + QLineEdit *lineEditor = qobject_cast(sender()); + if (lineEditor) { + emit commitData(lineEditor); + emit closeEditor(lineEditor); + } +} + +void UBDocumentTreeItemDelegate::processChangedText(const QString &str) const +{ + QLineEdit *editor = qobject_cast(sender()); + if (!editor) { + return; + } + + if (!validateString(str)) { + editor->setStyleSheet("background-color: #FFB3C8;"); + } else { + editor->setStyleSheet("background-color: #FFFFFF;"); + } +} + +bool UBDocumentTreeItemDelegate::validateString(const QString &str) const +{ + return QRegExp("[^\\/\\:\\?\\*\\|\\<\\>\\\"]{1,}").exactMatch(str) + && !mExistingFileNames.contains(str); + +} + +QWidget *UBDocumentTreeItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + Q_UNUSED(option); + + //N/C - NNE - 20140407 : Add the test for the index column. + if(index.column() == 0){ + mExistingFileNames.clear(); + const UBDocumentTreeModel *indexModel = qobject_cast(index.model()); + if (indexModel) { + mExistingFileNames = indexModel->nodeNameList(index.parent()); + mExistingFileNames.removeOne(index.data().toString()); + } + + QLineEdit *nameEditor = new QLineEdit(parent); + connect(nameEditor, SIGNAL(editingFinished()), this, SLOT(commitAndCloseEditor())); + connect(nameEditor, SIGNAL(textChanged(QString)), this, SLOT(processChangedText(QString))); + return nameEditor; + } + + //N/C - NNe - 20140407 : the other column are not editable. + return 0; +} + +void UBDocumentTreeItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const +{ + if (index.column() == 0) { + QLineEdit *lineEditor = qobject_cast(editor); + lineEditor->setText(index.data().toString()); + lineEditor->selectAll(); + } +} + +void UBDocumentTreeItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const +{ + QLineEdit *lineEditor = qobject_cast(editor); + if (validateString(lineEditor->text())) { + model->setData(index, lineEditor->text()); + } +} + +void UBDocumentTreeItemDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex &index) const +{ + QStyledItemDelegate::paint(painter, option, index); +} + +UBDocumentController::UBDocumentController(UBMainWindow* mainWindow) + : UBDocumentContainer(mainWindow->centralWidget()) + , mSelectionType(None) + , mParentWidget(mainWindow->centralWidget()) + , mBoardController(UBApplication::boardController) + , mDocumentUI(0) + , mMainWindow(mainWindow) + , mDocumentWidget(0) + , mIsClosing(false) + , mToolsPalette(0) + , mToolsPalettePositionned(false) + , mTrashTi(0) + , mDocumentTrashGroupName(tr("Trash")) + , mDefaultDocumentGroupName(tr("Untitled Documents")) + , mCurrentTreeDocument(0) + , mCurrentIndexMoved(false) +{ + + setupViews(); + setupToolbar(); + connect(this, SIGNAL(exportDone()), mMainWindow, SLOT(onExportDone())); + connect(this, SIGNAL(documentThumbnailsUpdated(UBDocumentContainer*)), this, SLOT(refreshDocumentThumbnailsView(UBDocumentContainer*))); + + mUserHasChangedSortOrder = false; +} + +UBDocumentController::~UBDocumentController() +{ + if (mDocumentUI) + delete mDocumentUI; +} + +void UBDocumentController::createNewDocument() +{ + UBPersistenceManager *pManager = UBPersistenceManager::persistenceManager(); + UBDocumentTreeModel *docModel = pManager->mDocumentTreeStructureModel; + QModelIndex selectedIndex = firstSelectedTreeIndex(); + if (selectedIndex.isValid()) { + QString groupName = docModel->isCatalog(selectedIndex) + ? docModel->virtualPathForIndex(selectedIndex) + : docModel->virtualDirForIndex(selectedIndex); + + UBDocumentProxy *document = pManager->createDocument(groupName); + selectDocument(document); + + if (document) + pManager->mDocumentTreeStructureModel->markDocumentAsNew(document); + } +} + +void UBDocumentController::selectDocument(UBDocumentProxy* proxy, bool setAsCurrentDocument, const bool onImport) +{ + if (proxy==NULL) + { + setDocument(NULL); + return; + } + + if (setAsCurrentDocument) { + UBPersistenceManager::persistenceManager()->mDocumentTreeStructureModel->setCurrentDocument(proxy); + QModelIndex indexCurrentDoc = UBPersistenceManager::persistenceManager()->mDocumentTreeStructureModel->indexForProxy(proxy); + mDocumentUI->documentTreeView->setSelectedAndExpanded(indexCurrentDoc, true); + + if (proxy != mBoardController->selectedDocument()) // only if wanted Document is different from document actually on Board, // ALTI/AOU - 20140217 + { + //issue 1629 - NNE - 20131105 : When set a current document, change in the board controller + mBoardController->setActiveDocumentScene(proxy, 0, false); + } + } + + mSelectionType = Document; + setDocument(proxy); +} + +void UBDocumentController::createNewDocumentGroup() +{ + UBPersistenceManager *pManager = UBPersistenceManager::persistenceManager(); + UBDocumentTreeModel *docModel = pManager->mDocumentTreeStructureModel; + QModelIndex selectedIndex = firstSelectedTreeIndex(); + if (!selectedIndex.isValid()) { + return; + } + QModelIndex parentIndex = docModel->isCatalog(selectedIndex) + ? selectedIndex + : selectedIndex.parent(); + + QString newFolderName = docModel->adjustNameForParentIndex(tr("New Folder"), parentIndex); + + docModel->addCatalog(newFolderName, parentIndex); +} + + +UBDocumentProxy* UBDocumentController::selectedDocumentProxy() +{ + return UBPersistenceManager::persistenceManager()->mDocumentTreeStructureModel->proxyForIndex(firstSelectedTreeIndex()); +} + +QList UBDocumentController::selectedProxies() +{ + QList result; + + foreach (QModelIndex curIndex, mapIndexesToSource(mDocumentUI->documentTreeView->selectionModel()->selectedIndexes())) { + result << UBPersistenceManager::persistenceManager()->mDocumentTreeStructureModel->proxyForIndex(curIndex); + } + + return result; +} + +QModelIndexList UBDocumentController::selectedTreeIndexes() +{ + return mapIndexesToSource(mDocumentUI->documentTreeView->selectionModel()->selectedRows(0)); +} + +UBDocumentProxy* UBDocumentController::firstSelectedTreeProxy() +{ + return selectedProxies().count() ? selectedProxies().first() : 0; +} + +void UBDocumentController::TreeViewSelectionChanged(const QModelIndex ¤t, const QModelIndex &previous) +{ + Q_UNUSED(previous) + + UBDocumentTreeModel *docModel = UBPersistenceManager::persistenceManager()->mDocumentTreeStructureModel; + + QModelIndex current_index = mapIndexToSource(current); + + //N/C - NNE - 20140414 + //if the selection contains more than one object, don't show the thumbnail. + //We have just to pass a null proxy to disable the display of thumbnail + UBDocumentProxy *currentDocumentProxy = 0; + + if(current_index.isValid() && mDocumentUI->documentTreeView->selectionModel()->selectedRows(0).size() == 1){ + currentDocumentProxy = docModel->proxyData(current_index); + setDocument(currentDocumentProxy, false); + } + //N/C - NNE - 20140414 : END + + + if (mCurrentIndexMoved) { + if (docModel->isDocument(current_index)) { + docModel->setCurrentDocument(currentDocumentProxy); + } else if (docModel->isCatalog(current_index)) { + docModel->setCurrentDocument(0); + } + mCurrentIndexMoved = false; + } + + itemSelectionChanged(docModel->isCatalog(current_index) ? Folder : Document); +} + +//N/C - NNE - 20140402 : workaround for using a proxy model +QModelIndex UBDocumentController::mapIndexToSource(const QModelIndex &index) +{ + UBSortFilterProxyModel *proxy = dynamic_cast(mDocumentUI->documentTreeView->model()); + + if(proxy){ + return proxy->mapToSource(index); + } + + return index; +} + +QModelIndexList UBDocumentController::mapIndexesToSource(const QModelIndexList &indexes) +{ + UBSortFilterProxyModel *proxy = dynamic_cast(mDocumentUI->documentTreeView->model()); + + if(proxy){ + QModelIndexList list; + + for(int i = 0; i < indexes.size(); i++){ + list.push_back(proxy->mapToSource(indexes.at(i))); + } + + return list; + } + + return indexes; +} + +//N/C - NNE - 20140402 : END + +void UBDocumentController::TreeViewSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected) +{ + Q_UNUSED(deselected) + + int nbIndexes = selected.indexes().count(); + + if (nbIndexes) { + //N/C - NNE - 20140408 + + QModelIndexList list = mDocumentUI->documentTreeView->selectionModel()->selectedRows(); + + //if multi-selection + if(list.count() > 1){ + //check if the selection is in the same top-level folder + QModelIndex sourceIndex1 = mapIndexToSource(list.at(list.count()-1)); + QModelIndex sourceIndex2 = mapIndexToSource(list.at(list.count()-2)); + + UBDocumentTreeModel *model = UBPersistenceManager::persistenceManager()->mDocumentTreeStructureModel; + + bool sameFolder = (model->inModel(sourceIndex1) && model->inModel(sourceIndex2)); + + sameFolder |= (model->inTrash(sourceIndex1) && model->inTrash(sourceIndex2)); + + sameFolder |= (model->inMyDocuments(sourceIndex1) && model->inMyDocuments(sourceIndex2)); + + if(!sameFolder){ + mDocumentUI->documentTreeView->clearSelection(); + mDocumentUI->documentTreeView->selectionModel()->select(list.at(list.count()-1), QItemSelectionModel::Select | QItemSelectionModel::Rows); + } + } + //N/C - NNE - 20140408 : END + + //if the parent has been already select, don't select its children + QModelIndex newSelectedRow = selected.indexes().first(); + QModelIndex parent = newSelectedRow.parent(); + bool isParentIsSelected = false; + while(parent.isValid()){ + isParentIsSelected |= (list.indexOf(parent) != -1); + + if(isParentIsSelected) + break; + + parent = parent.parent(); + } + + if(!isParentIsSelected) + TreeViewSelectionChanged(newSelectedRow, QModelIndex()); + else + mDocumentUI->documentTreeView->selectionModel()->select(newSelectedRow, QItemSelectionModel::Deselect | QItemSelectionModel::Rows); + + //if he newSelectedRow have children deselect them + /* + UBDocumentTreeModel *model = UBPersistenceManager::persistenceManager()->mDocumentTreeStructureModel; + + if(model->rowCount(newSelectedRow) > 0){ + QModelIndexList children; + QModelIndex parent = newSelectedRow; + + //init children list + for(int i = 0; i < model->rowCount(parent); i++){ + children.push_back(parent.child(i, 0)); + } + + for(int i = 0; i < children.size(); i++){ + //deselect the element + mDocumentUI->documentTreeView->selectionModel()->select(children.at(0), QItemSelectionModel::Deselect); + + //add its children if any + for(int k = 0; k < model->rowCount(children.at(i)); k++){ + children.push_back(children.at(i).child(k, 0)); + } + } + } + */ + } + +} + +void UBDocumentController::itemSelectionChanged(LastSelectedElementType newSelection) +{ + mSelectionType = newSelection; + updateActions(); +} + + +void UBDocumentController::setupViews() +{ + + if (!mDocumentWidget) + { + mDocumentWidget = new QWidget(mMainWindow->centralWidget()); + mMainWindow->addDocumentsWidget(mDocumentWidget); + + mDocumentUI = new Ui::documents(); + + mDocumentUI->setupUi(mDocumentWidget); + + int thumbWidth = UBSettings::settings()->documentThumbnailWidth->get().toInt(); + + mDocumentUI->documentZoomSlider->setValue(thumbWidth); + mDocumentUI->thumbnailWidget->setThumbnailWidth(thumbWidth); + + connect(mDocumentUI->documentZoomSlider, SIGNAL(valueChanged(int)), this, + SLOT(documentZoomSliderValueChanged(int))); + + connect(mMainWindow->actionOpen, SIGNAL(triggered()), this, SLOT(openSelectedItem())); + connect(mMainWindow->actionNewFolder, SIGNAL(triggered()), this, SLOT(createNewDocumentGroup())); + connect(mMainWindow->actionNewDocument, SIGNAL(triggered()), this, SLOT(createNewDocument())); + + connect(mMainWindow->actionImport, SIGNAL(triggered(bool)), this, SLOT(importFile())); + + QMenu* addMenu = new QMenu(mDocumentWidget); + mAddFolderOfImagesAction = addMenu->addAction(tr("Add Folder of Images")); + mAddImagesAction = addMenu->addAction(tr("Add Images")); + mAddFileToDocumentAction = addMenu->addAction(tr("Add Pages from File")); + + connect(mAddFolderOfImagesAction, SIGNAL(triggered(bool)), this, SLOT(addFolderOfImages())); + connect(mAddFileToDocumentAction, SIGNAL(triggered(bool)), this, SLOT(addFileToDocument())); + connect(mAddImagesAction, SIGNAL(triggered(bool)), this, SLOT(addImages())); + + foreach (QWidget* menuWidget, mMainWindow->actionDocumentAdd->associatedWidgets()) + { + QToolButton *tb = qobject_cast(menuWidget); + + if (tb && !tb->menu()) + { + tb->setObjectName("ubButtonMenu"); + tb->setPopupMode(QToolButton::InstantPopup); + + QMenu* menu = new QMenu(mDocumentWidget); + + menu->addAction(mAddFolderOfImagesAction); + menu->addAction(mAddImagesAction); + menu->addAction(mAddFileToDocumentAction); + + tb->setMenu(menu); + } + } + + QMenu* exportMenu = new QMenu(mDocumentWidget); + + UBDocumentManager *documentManager = UBDocumentManager::documentManager(); + for (int i = 0; i < documentManager->supportedExportAdaptors().length(); i++) + { + UBExportAdaptor* adaptor = documentManager->supportedExportAdaptors()[i]; + QAction *currentExportAction = exportMenu->addAction(adaptor->exportName()); + currentExportAction->setData(i); + connect(currentExportAction, SIGNAL(triggered (bool)), this, SLOT(exportDocument())); + exportMenu->addAction(currentExportAction); + adaptor->setAssociatedAction(currentExportAction); + } + + foreach (QWidget* menuWidget, mMainWindow->actionExport->associatedWidgets()) + { + QToolButton *tb = qobject_cast(menuWidget); + + if (tb && !tb->menu()) + { + tb->setObjectName("ubButtonMenu"); + tb->setPopupMode(QToolButton::InstantPopup); + + tb->setMenu(exportMenu); + } + } + +#ifdef Q_WS_MAC + mMainWindow->actionDelete->setShortcut(QKeySequence(Qt::Key_Backspace)); +#endif + + connect(mMainWindow->actionDelete, SIGNAL(triggered()), this, SLOT(deleteSelectedItem())); + connect(mMainWindow->actionDuplicate, SIGNAL(triggered()), this, SLOT(duplicateSelectedItem())); + connect(mMainWindow->actionRename, SIGNAL(triggered()), this, SLOT(renameSelectedItem())); + connect(mMainWindow->actionAddToWorkingDocument, SIGNAL(triggered()), this, SLOT(addToDocument())); + + UBDocumentTreeModel *model = UBPersistenceManager::persistenceManager()->mDocumentTreeStructureModel; + + mSortFilterProxyModel = new UBSortFilterProxyModel(); + + mSortFilterProxyModel->setSourceModel(model); + + sortDocuments(UBDocumentController::Alphabetical, UBDocumentController::ASC); + + mDocumentUI->documentTreeView->setModel(mSortFilterProxyModel); + + mDocumentUI->documentTreeView->setItemDelegate(new UBDocumentTreeItemDelegate(this)); + mDocumentUI->documentTreeView->setDragEnabled(true); + mDocumentUI->documentTreeView->setAcceptDrops(true); + mDocumentUI->documentTreeView->viewport()->setAcceptDrops(true); + mDocumentUI->documentTreeView->setDropIndicatorShown(true); + mDocumentUI->documentTreeView->header()->setStretchLastSection(false); + mDocumentUI->documentTreeView->header()->setSectionResizeMode(0, QHeaderView::Stretch); + mDocumentUI->documentTreeView->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + mDocumentUI->documentTreeView->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents); + + mDocumentUI->documentTreeView->hideColumn(1); + mDocumentUI->documentTreeView->hideColumn(2); + + mDocumentUI->sortOrder->hide(); + + connect(mDocumentUI->sortKind, SIGNAL(activated(int)), this, SLOT(onSortKindChanged(int))); + connect(mDocumentUI->sortOrder, SIGNAL(activated(int)), this, SLOT(onSortOrderChanged(int))); + + connect(mDocumentUI->collapseAll, SIGNAL(clicked()), this, SLOT(collapseAll())); + + connect(mDocumentUI->expandAll, SIGNAL(clicked()), this, SLOT(expandAll())); + + connect(mDocumentUI->documentTreeView->itemDelegate(), SIGNAL(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint) ), mDocumentUI->documentTreeView, SLOT(adjustSize())); + connect(mDocumentUI->documentTreeView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(TreeViewSelectionChanged(QItemSelection,QItemSelection))); + connect(UBPersistenceManager::persistenceManager()->mDocumentTreeStructureModel, SIGNAL(indexChanged(QModelIndex,QModelIndex)) + ,mDocumentUI->documentTreeView, SLOT(onModelIndexChanged(QModelIndex,QModelIndex))); + connect(UBPersistenceManager::persistenceManager()->mDocumentTreeStructureModel, SIGNAL(currentIndexMoved(QModelIndex,QModelIndex)) + ,this, SLOT(currentIndexMoved(QModelIndex,QModelIndex))); + + connect(mDocumentUI->thumbnailWidget, SIGNAL(sceneDropped(UBDocumentProxy*, int, int)), this, SLOT(moveSceneToIndex ( UBDocumentProxy*, int, int))); + connect(mDocumentUI->thumbnailWidget, SIGNAL(resized()), this, SLOT(thumbnailViewResized())); + connect(mDocumentUI->thumbnailWidget, SIGNAL(mouseDoubleClick(QGraphicsItem*,int)), this, SLOT(thumbnailPageDoubleClicked(QGraphicsItem*,int))); + connect(mDocumentUI->thumbnailWidget, SIGNAL(mouseClick(QGraphicsItem*, int)), this, SLOT(pageClicked(QGraphicsItem*, int))); + + connect(mDocumentUI->thumbnailWidget->scene(), SIGNAL(selectionChanged()), this, SLOT(pageSelectionChanged())); + + connect(UBPersistenceManager::persistenceManager(), SIGNAL(documentSceneCreated(UBDocumentProxy*, int)), this, SLOT(documentSceneChanged(UBDocumentProxy*, int))); + connect(UBPersistenceManager::persistenceManager(), SIGNAL(documentSceneWillBeDeleted(UBDocumentProxy*, int)), this, SLOT(documentSceneChanged(UBDocumentProxy*, int))); + + mDocumentUI->thumbnailWidget->setBackgroundBrush(UBSettings::documentViewLightColor); + + #ifdef Q_WS_MACX + mMessageWindow = new UBMessageWindow(NULL); + #else + mMessageWindow = new UBMessageWindow(mDocumentUI->thumbnailWidget); + #endif + + mMessageWindow->setCustomPosition(true); + mMessageWindow->hide(); + } +} + +//N/C - NNE - 20140403 +void UBDocumentController::sortDocuments(int kind, int order) +{ + Qt::SortOrder sortOrder = Qt::AscendingOrder; + + //if the user hasn't change the sort + //set order to its default value according to + //the kind of sort + if(!mUserHasChangedSortOrder){ + if(kind == UBDocumentController::CreationDate || kind == UBDocumentController::UpdateDate){ + sortOrder = Qt::DescendingOrder; + mDocumentUI->sortOrder->setCurrentIndex(1); + }else{ + mDocumentUI->sortOrder->setCurrentIndex(0); + } + }else if(order == UBDocumentController::DESC){ + sortOrder = Qt::DescendingOrder; + } + + if(kind == UBDocumentController::CreationDate){ + mSortFilterProxyModel->setSortRole(UBDocumentTreeModel::CreationDate); + mSortFilterProxyModel->sort(1, sortOrder); + + mDocumentUI->documentTreeView->showColumn(1); + mDocumentUI->documentTreeView->hideColumn(2); + }else if(kind == UBDocumentController::UpdateDate){ + mSortFilterProxyModel->setSortRole(UBDocumentTreeModel::UpdateDate); + mSortFilterProxyModel->sort(2, sortOrder); + + mDocumentUI->documentTreeView->hideColumn(1); + mDocumentUI->documentTreeView->showColumn(2); + }else{ + mSortFilterProxyModel->setSortRole(Qt::DisplayRole); + mSortFilterProxyModel->sort(0, sortOrder); + + //alphabetical order or nothing + mDocumentUI->documentTreeView->hideColumn(1); + mDocumentUI->documentTreeView->hideColumn(2); + } +} + +void UBDocumentController::onSortOrderChanged(int index) +{ + int kindIndex = mDocumentUI->sortKind->currentIndex(); + + mUserHasChangedSortOrder = true; + + sortDocuments(kindIndex, index); +} + +void UBDocumentController::onSortKindChanged(int index) +{ + int orderIndex = mDocumentUI->sortOrder->currentIndex(); + + if(index == -1 || index == 0) + mDocumentUI->sortOrder->hide(); + else + mDocumentUI->sortOrder->show(); + + sortDocuments(index, orderIndex); +} +//N/C - NNE - 20140403 : END + +QWidget* UBDocumentController::controlView() +{ + return mDocumentWidget; +} + + +void UBDocumentController::setupToolbar() +{ + UBApplication::app()->insertSpaceToToolbarBeforeAction(mMainWindow->documentToolBar, mMainWindow->actionBoard); + connect(mMainWindow->actionDocumentTools, SIGNAL(triggered()), this, SLOT(toggleDocumentToolsPalette())); +} + +void UBDocumentController::setupPalettes() +{ + + mToolsPalette = new UBDocumentToolsPalette(controlView()); + + mToolsPalette->hide(); + + bool showToolsPalette = !mToolsPalette->isEmpty(); + mMainWindow->actionDocumentTools->setVisible(showToolsPalette); if (showToolsPalette) { @@ -442,12 +2152,11 @@ void UBDocumentController::setupPalettes() } } - void UBDocumentController::show() { selectDocument(mBoardController->selectedDocument()); - selectionChanged(); + updateActions(); if(!mToolsPalette) setupPalettes(); @@ -476,6 +2185,7 @@ void UBDocumentController::openSelectedItem() if (proxy && isOKToOpenDocument(proxy)) { + mBoardController->setActiveDocumentScene(proxy, thumb->sceneIndex()); UBApplication::applicationController->showBoard(); } } @@ -522,568 +2232,353 @@ void UBDocumentController::duplicateSelectedItem() duplicatePages(selectedSceneIndexes); emit documentThumbnailsUpdated(this); selectedDocument()->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(QDateTime::currentDateTime())); - mDocumentUI->thumbnailWidget->selectItemAt(selectedSceneIndexes.last() + selectedSceneIndexes.size()); + UBMetadataDcSubsetAdaptor::persist(selectedDocument()); + int selectedThumbnail = selectedSceneIndexes.last() + selectedSceneIndexes.size(); + mDocumentUI->thumbnailWidget->selectItemAt(selectedThumbnail); + int sceneCount = selectedSceneIndexes.count(); + showMessage(tr("duplicated %1 page","duplicated %1 pages",sceneCount).arg(sceneCount), false); + + mBoardController->setActiveDocumentScene(selectedThumbnail); + mBoardController->reloadThumbnails(); } } else { - UBDocumentProxy* source = selectedDocumentProxy(); - UBDocumentGroupTreeItem* group = selectedDocumentGroupTreeItem(); - - if (source && group) - { - QString docName = source->metaData(UBSettings::documentName).toString(); - - showMessage(tr("Duplicating Document %1").arg(docName), true); + UBDocumentTreeModel *docModel = UBPersistenceManager::persistenceManager()->mDocumentTreeStructureModel; + QModelIndex selectedIndex = firstSelectedTreeIndex(); - UBDocumentProxy* duplicatedDoc = UBPersistenceManager::persistenceManager()->duplicateDocument(source); - duplicatedDoc->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(QDateTime::currentDateTime())); - - selectDocument(duplicatedDoc, false); - - showMessage(tr("Document %1 copied").arg(docName), false); - } - } -} + Q_ASSERT(!docModel->isConstant(selectedIndex) && !docModel->inTrash(selectedIndex)); -/** - * @brief Set the first document in the list as current document - * - * If there are no documents, a new one is created. - */ -void UBDocumentController::selectFirstDocumentInList() -{ - // Loop through all folders until we find one that is not the trash and not empty, - // and select the first document in that folder + showMessage(tr("Duplicating Document %1").arg(""), true); - for (int i(0); i < mDocumentUI->documentTreeWidget->topLevelItemCount(); ++i) { - QTreeWidgetItem* item = mDocumentUI->documentTreeWidget->topLevelItem(i); - UBDocumentGroupTreeItem* groupItem = dynamic_cast(item); + docModel->copyIndexToNewParent(selectedIndex, selectedIndex.parent(), UBDocumentTreeModel::aContentCopy); - if (!groupItem->isTrashFolder() && groupItem->childCount() > 0) { - selectDocument(((UBDocumentProxyTreeItem*)groupItem->child(0))->proxy(), true); - groupItem->child(0)->setSelected(true); - return; - } + showMessage(tr("Document %1 copied").arg(""), false); } - - // No document found => create a new one - UBDocumentGroupTreeItem* topFolder = dynamic_cast(mDocumentUI->documentTreeWidget->topLevelItem(0)); - - UBDocumentProxy* document = UBPersistenceManager::persistenceManager()->createDocument(topFolder->groupName()); - selectDocument(document, true); } -/** - * @brief Find the current document, and select it in the list - * - * If selectNewCurrentDocument is true, the first document in the list is selected and set as - * current document. - */ -void UBDocumentController::selectATreeItemOnMultipleTrashing(bool selectNewCurrentDocument) +void UBDocumentController::deleteSelectedItem() { - mCurrentSelection.clear(); - - if (selectNewCurrentDocument) { - selectFirstDocumentInList(); - return; - } + UBDocumentTreeModel *docModel = UBPersistenceManager::persistenceManager()->mDocumentTreeStructureModel; - // Find the currently selected document, and select its corresponding tree item - // If it isn't found, then the first document in the list is selected and set as current document + QModelIndexList indexes = selectedTreeIndexes(); - for (int i(0); i < mDocumentUI->documentTreeWidget->topLevelItemCount(); i++) { - QTreeWidgetItem* item = mDocumentUI->documentTreeWidget->topLevelItem(i); - UBDocumentGroupTreeItem* groupItem = dynamic_cast(item); + //we iterate in backward because if we iterate in normal order, + //indexes are invalid after the first deletion + for(int i = indexes.size()-1; i >= 0; i--){ + QModelIndex currentIndex = indexes.at(i); + DeletionType deletionForSelection = deletionTypeForSelection(mSelectionType, currentIndex, docModel); - if (!groupItem->isTrashFolder()) { - for (int j(0); j < groupItem->childCount(); j++) { - if (((UBDocumentProxyTreeItem*)groupItem->child(j))->proxy() == mBoardController->selectedDocument()) { - ((UBDocumentProxyTreeItem*)groupItem->child(j))->setSelected(true); - return; - } - } + //N/C - NNE - 20140408 : if parent is selected, continue, because it will be deleted when the parent will be deleted + if(mDocumentUI->documentTreeView->selectionModel()->isSelected(currentIndex.parent())){ + continue; } - } - - selectFirstDocumentInList(); -} - -void UBDocumentController::selectADocumentOnTrashingSelectedOne(UBDocumentGroupTreeItem* groupTi,UBDocumentProxyTreeItem *proxyTi) -{ - int index = proxyTi->parent()->indexOfChild(proxyTi); - index --; + //N/C - NNE - 20140408 : END - // Select the previous document in the current folder, if there is one - if (index >= 0) - { - if (proxyTi->proxy() == mBoardController->selectedDocument()) - { - selectDocument(((UBDocumentProxyTreeItem*)proxyTi->parent()->child(index))->proxy(), true); - } - else - proxyTi->parent()->child(index)->setSelected(true); - } - // If the deleted document is at the top of its folder, try to select the second top-most one - else if (proxyTi->parent()->childCount() > 1) - { - if (proxyTi->proxy() == mBoardController->selectedDocument()) + switch (deletionForSelection) { + case DeletePage: { - selectDocument(((UBDocumentProxyTreeItem*)proxyTi->parent()->child(1))->proxy(), true); + deletePages(mDocumentUI->thumbnailWidget->selectedItems()); + break; } - else - proxyTi->parent()->child(1)->setSelected(true); - } - // Otherwise, go through the other folders - else - { - if (proxyTi->proxy() == mBoardController->selectedDocument()) + case MoveToTrash: { - bool documentFound = false; - for (int i = 0; i < mDocumentUI->documentTreeWidget->topLevelItemCount(); i++) - { - QTreeWidgetItem* item = mDocumentUI->documentTreeWidget->topLevelItem(i); - UBDocumentGroupTreeItem* groupItem = dynamic_cast(item); - if (!groupItem->isTrashFolder()) - { - for(int j=0; jchildCount(); j++) - { - if (((UBDocumentProxyTreeItem*)groupItem->child(j))->proxy() != mBoardController->selectedDocument()) - { - selectDocument(((UBDocumentProxyTreeItem*)groupItem->child(0))->proxy(), true); - documentFound = true; - break; - } - } - } - if (documentFound) - break; - } - if (!documentFound) - { - UBDocumentProxy *document = UBPersistenceManager::persistenceManager()->createDocument(groupTi->groupName()); - selectDocument(document, true); - } - } - else - proxyTi->parent()->setSelected(true); - } -} - -void UBDocumentController::moveDocumentToTrash(UBDocumentGroupTreeItem* groupTi, UBDocumentProxyTreeItem *proxyTi, bool selectNewDocument) -{ - qDebug() << "moving doc to trash. selection type: " << mSelectionType; - - if (selectNewDocument) - selectADocumentOnTrashingSelectedOne(groupTi,proxyTi); - - QString oldGroupName = proxyTi->proxy()->metaData(UBSettings::documentGroupName).toString(); - proxyTi->proxy()->setMetaData(UBSettings::documentGroupName, UBSettings::trashedDocumentGroupNamePrefix + oldGroupName); - UBPersistenceManager::persistenceManager()->persistDocumentMetadata(proxyTi->proxy()); + moveToTrash(currentIndex, docModel); - proxyTi->parent()->removeChild(proxyTi); - mTrashTi->addChild(proxyTi); - proxyTi->setFlags(proxyTi->flags() ^ Qt::ItemIsEditable); -} - -void UBDocumentController::moveFolderToTrash(UBDocumentGroupTreeItem* groupTi) -{ - bool changeCurrentDocument = false; - for (int i = 0; i < groupTi->childCount(); i++) - { - UBDocumentProxyTreeItem* proxyTi = dynamic_cast(groupTi->child(i)); - if (proxyTi && proxyTi->proxy() && proxyTi->proxy() == mBoardController->selectedDocument()) - { - changeCurrentDocument = true; break; } - } - - QList toBeDeleted; - - - for (int i = 0; i < groupTi->childCount(); i++) - { - UBDocumentProxyTreeItem* proxyTi = dynamic_cast(groupTi->child(i)); - if (proxyTi && proxyTi->proxy()) - toBeDeleted << proxyTi; - } - - for (int i = 0; i < toBeDeleted.count(); i++) - { - UBDocumentProxyTreeItem* proxyTi = toBeDeleted.at(i); - - showMessage(QString("Deleting %1").arg(proxyTi->proxy()->metaData(UBSettings::documentName).toString())); - // Move document to trash - QString oldGroupName = proxyTi->proxy()->metaData(UBSettings::documentGroupName).toString(); - proxyTi->proxy()->setMetaData(UBSettings::documentGroupName, UBSettings::trashedDocumentGroupNamePrefix + oldGroupName); - UBPersistenceManager::persistenceManager()->persistDocumentMetadata(proxyTi->proxy()); - - groupTi->removeChild(proxyTi); - mTrashTi->addChild(proxyTi); - proxyTi->setFlags(proxyTi->flags() ^ Qt::ItemIsEditable); - - showMessage(QString("%1 deleted").arg(groupTi->groupName())); - } - - // dont remove default group - if (!groupTi->isDefaultFolder()) - { - int index = mDocumentUI->documentTreeWidget->indexOfTopLevelItem(groupTi); - - if (index >= 0) + case CompleteDelete: { - mDocumentUI->documentTreeWidget->takeTopLevelItem(index); + deleteIndexAndAssociatedData(currentIndex); + break; } - } - - if (changeCurrentDocument) - { - bool documentFound = false; - for (int i = 0; i < mDocumentUI->documentTreeWidget->topLevelItemCount(); i++) + case EmptyFolder: { - QTreeWidgetItem* item = mDocumentUI->documentTreeWidget->topLevelItem(i); - UBDocumentGroupTreeItem* groupItem = dynamic_cast(item); - if (!groupItem->isTrashFolder() && groupItem != groupTi) - { - for(int j=0; jchildCount(); j++) - { - if (((UBDocumentProxyTreeItem*)groupItem->child(j))->proxy() != mBoardController->selectedDocument()) - { - selectDocument(((UBDocumentProxyTreeItem*)groupItem->child(0))->proxy(), true); - documentFound = true; + if (currentIndex == docModel->myDocumentsIndex()) { //Emptying "My documents". Keeping Untitled Documents + int startInd = 0; + while (docModel->rowCount(currentIndex)) { + QModelIndex testSubINdecurrentIndex = docModel->index(startInd, 0, currentIndex); + if (testSubINdecurrentIndex == docModel->untitledDocumentsIndex()) { + emptyFolder(testSubINdecurrentIndex, MoveToTrash); + startInd++; + continue; + } + if (!testSubINdecurrentIndex.isValid()) { break; } + docModel->moveIndex(testSubINdecurrentIndex, docModel->trashIndex()); } + //issue 1629 - NNE - 20131105 + //Here, we are sure that the current scene has been deleted + createNewDocumentInUntitledFolder(); + } else { + //issue 1629 - NNE - 20131105 + //Check if we will delete the current scene + UBDocumentTreeNode *currentNode = docModel->nodeFromIndex(currentIndex); + bool deleteCurrentScene = currentNode->findNode(docModel->nodeFromIndex(docModel->currentIndex())); + + emptyFolder(currentIndex, MoveToTrash); //Empty constant folder + + if(deleteCurrentScene) createNewDocumentInUntitledFolder(); } - if (documentFound) - break; + + break; } - if (!documentFound) + case EmptyTrash: { - UBDocumentProxy *document = UBPersistenceManager::persistenceManager()->createDocument( mDefaultDocumentGroupName ); - selectDocument(document, true); + emptyFolder(currentIndex, CompleteDelete); // Empty trash folder + break; + } } } - reloadThumbnails(); } -/** - * @brief Empty the trash folder, deleting all contents permanently. - * @param showConfirmationDialog If set to true, prompts confirmation from the user - */ -void UBDocumentController::emptyTrash(bool showConfirmationDialog) +//N/C - NNE - 20140410 +void UBDocumentController::moveIndexesToTrash(const QModelIndexList &list, UBDocumentTreeModel *docModel) { - if (showConfirmationDialog && - !UBApplication::mainWindow->yesNoQuestion(tr("Empty Trash"), tr("Are you sure you want to empty trash?"))) - return; + QModelIndex currentScene = docModel->indexForNode(docModel->currentNode()); - QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); - QList toBeDeleted; + //check if the current scene is selected + QItemSelectionModel *selectionModel = mDocumentUI->documentTreeView->selectionModel(); + bool deleteCurrentScene = selectionModel->isSelected(mSortFilterProxyModel->mapFromSource(currentScene)); + + //check if the current scene is in the hierarchy + if(!deleteCurrentScene){ + for(int i = 0; i < list.size(); i++){ + deleteCurrentScene = docModel->isDescendantOf(currentScene, list.at(i)); - for (int i(0); i < mTrashTi->childCount(); ++i) { - UBDocumentProxyTreeItem* proxyTi = dynamic_cast(mTrashTi->child(i)); - if (proxyTi && proxyTi->proxy()){ - if(proxyTi->proxy() == mBoardController->selectedDocument()){ - selectADocumentOnTrashingSelectedOne(dynamic_cast(mDocumentUI->documentTreeWidget),proxyTi); + if(deleteCurrentScene){ + break; } - toBeDeleted << proxyTi; } + } - showMessage(tr("Emptying trash")); + QModelIndex proxyMapCurentScene = mSortFilterProxyModel->mapFromSource(currentScene); - for (int i(0); i < toBeDeleted.count(); ++i) { - UBDocumentProxyTreeItem* proxyTi = toBeDeleted.at(i); + if(deleteCurrentScene){ + QModelIndex sibling = findPreviousSiblingNotSelected(proxyMapCurentScene, selectionModel); - proxyTi->parent()->removeChild(proxyTi); - UBPersistenceManager::persistenceManager()->deleteDocument(proxyTi->proxy()); - } + if(sibling.isValid()){ + QModelIndex sourceSibling = mSortFilterProxyModel->mapToSource(sibling); - showMessage(tr("Emptied trash")); + UBDocumentProxy *proxy = docModel->proxyForIndex(sourceSibling); - QApplication::restoreOverrideCursor(); - mMainWindow->actionDelete->setEnabled(false); -} + docModel->setCurrentDocument(proxy); -/** - * @brief Delete an item (document or folder) from the document list - * @param item The document or folder to delete - * @param showConfirmationDialog If set to true, the user will be asked for confirmation - * @param selectNewDocument If set to true, a new document will be selected immediately - * - * If the item passed as parameter is a document that is in the trash, then it is deleted - * permanently. If the trash folder is passed, then all its contents are deleted. - * Finally, if a folder is passed, all its contents are moved to trash. - */ -void UBDocumentController::deleteTreeItem(QTreeWidgetItem * item, bool showConfirmationDialog, bool selectNewDocument) -{ - UBDocumentProxyTreeItem * document = dynamic_cast(item); - UBDocumentGroupTreeItem * folder = dynamic_cast(item); + selectionModel->select(sibling, QItemSelectionModel::ClearAndSelect); - if (document) { - if (showConfirmationDialog && - !UBApplication::mainWindow->yesNoQuestion(tr("Remove Document"), - tr("Are you sure you want to remove the document '%1'?").arg(document->proxy()->metaData(UBSettings::documentName).toString()))) - return; + deleteCurrentScene = false; + }else{ + sibling = findNextSiblingNotSelected(proxyMapCurentScene, selectionModel); - if (!document->isInTrash()) - moveDocumentToTrash(dynamic_cast(document->parent()), document, selectNewDocument); + if(sibling.isValid()){ + QModelIndex sourceSibling = mSortFilterProxyModel->mapToSource(sibling); - else { - document->parent()->removeChild(document); - UBPersistenceManager::persistenceManager()->deleteDocument(document->proxy()); + UBDocumentProxy *proxy = docModel->proxyForIndex(sourceSibling); - if (selectNewDocument) { - if (mTrashTi->childCount()==0) - selectATreeItemOnMultipleTrashing(false); - else - selectDocument(((UBDocumentProxyTreeItem*)mTrashTi->child(0))->proxy(), false); - } + docModel->setCurrentDocument(proxy); - reloadThumbnails(); + selectionModel->select(sibling, QItemSelectionModel::ClearAndSelect); + + deleteCurrentScene = false; + } } - } - else if (folder) { - if (folder == mTrashTi) - emptyTrash(showConfirmationDialog); + } - else { - if (showConfirmationDialog && - !UBApplication::mainWindow->yesNoQuestion(tr("Remove Folder"), - tr("Are you sure you want to remove the folder '%1' and all its content?").arg(folder->groupName()))) - return; + docModel->moveIndexes(list, docModel->trashIndex()); - QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); - moveFolderToTrash(folder); - QApplication::restoreOverrideCursor(); - } + if(deleteCurrentScene){ + createNewDocumentInUntitledFolder(); } -} + selectionModel->clearSelection(); +} +//N/C - NNE - 20140410 : END -void UBDocumentController::deleteSelectedItem() +QModelIndex UBDocumentController::findPreviousSiblingNotSelected(const QModelIndex &index, QItemSelectionModel *selectionModel) { - if (mSelectionType == Page) { - QList selectedItems = mDocumentUI->thumbnailWidget->selectedItems(); - deletePages(selectedItems); - } - - else if (mSelectionType == Multiple) { - if (!UBApplication::mainWindow->yesNoQuestion(tr("Remove multiple documents"), - tr("Are you sure you want to remove all selected documents?"))) - return; + QModelIndex sibling = index.sibling(index.row() - 1, 0); - QList foldersToDelete; + if(sibling.isValid()){ + //if sibling is not selected and it is a document + //else keep searching + if(!parentIsSelected(sibling, selectionModel) + && !selectionModel->isSelected(sibling) + && !sibling.model()->hasChildren(sibling)){ + return sibling; + }else{ + return findPreviousSiblingNotSelected(sibling, selectionModel); + } + }else{ + //if the parent exist keep searching, else stop the search + QModelIndex parent = index.model()->parent(index); + + if(parent.isValid()){ + return findPreviousSiblingNotSelected(parent, selectionModel); + }else{ + return QModelIndex(); + } + } +} - bool currentDocumentDeleted = false; +QModelIndex UBDocumentController::findNextSiblingNotSelected(const QModelIndex &index, QItemSelectionModel *selectionModel) +{ + QModelIndex sibling = index.sibling(index.row() + 1, 0); - foreach (QTreeWidgetItem * item, mCurrentSelection) { - LastSelectedElementType type = itemType(item); - if (type == Document) { - deleteTreeItem(item, false, false); + if(sibling.isValid()){ + //if sibling is not selected and it is a document and its parent are not selected + //else keep searching + if(!parentIsSelected(sibling, selectionModel) + && !selectionModel->isSelected(sibling) + && !sibling.model()->hasChildren(sibling)){ + QModelIndex model = mSortFilterProxyModel->mapToSource(index); - UBDocumentProxyTreeItem* proxyItem = dynamic_cast(item); - if (proxyItem && proxyItem->proxy() && (proxyItem->proxy() == mBoardController->selectedDocument())) - currentDocumentDeleted = true; + if(UBPersistenceManager::persistenceManager()->mDocumentTreeStructureModel->isCatalog(model)){ + return QModelIndex(); + }else{ + return sibling; } - - else if (type == Folder) - // Delete folders later, to avoid deleting a document twice - foldersToDelete << item; + }else{ + return findNextSiblingNotSelected(sibling, selectionModel); } - - foreach (QTreeWidgetItem * item, foldersToDelete) { - deleteTreeItem(item, false, false); + }else{ + //if the parent exist keep searching, else stop the search + QModelIndex parent = index.parent(); + + if(parent.isValid()){ + return findNextSiblingNotSelected(parent, selectionModel); + }else{ + return QModelIndex(); } - - selectATreeItemOnMultipleTrashing(currentDocumentDeleted); - } - - else if (mSelectionType == Document || mSelectionType == Folder) { - QTreeWidgetItem * item = mCurrentSelection.first(); - if (item) - deleteTreeItem(item, true, true); } } - -void UBDocumentController::exportDocument() +bool UBDocumentController::parentIsSelected(const QModelIndex& child, QItemSelectionModel *selectionModel) { - QAction *currentExportAction = qobject_cast(sender()); - QVariant actionData = currentExportAction->data(); - UBExportAdaptor* selectedExportAdaptor = UBDocumentManager::documentManager()->supportedExportAdaptors()[actionData.toInt()]; + QModelIndex parent = child.parent(); - UBDocumentProxy* proxy = selectedDocumentProxy(); + while(parent.isValid()){ + if(selectionModel->isSelected(parent)){ + return true; + } - if (proxy) - { - selectedExportAdaptor->persist(proxy); - emit exportDone(); - } - else - { - showMessage(tr("No document selected!")); + parent = parent.parent(); } -} + return false; +} -void UBDocumentController::documentZoomSliderValueChanged (int value) +//issue 1629 - NNE - 20131212 +void UBDocumentController::moveToTrash(QModelIndex &index, UBDocumentTreeModel* docModel) { - mDocumentUI->thumbnailWidget->setThumbnailWidth(value); - - UBSettings::settings()->documentThumbnailWidth->set(value); + QModelIndexList list; + list.push_back(index); + moveIndexesToTrash(list, docModel); } +//issue 1629 - NNE - 20131212 : END - -void UBDocumentController::loadDocumentProxies() +void UBDocumentController::emptyFolder(const QModelIndex &index, DeletionType pDeletionType) { - QList > proxies = UBPersistenceManager::persistenceManager()->documentProxies; - - QStringList emptyGroupNames = UBSettings::settings()->value("Document/EmptyGroupNames", QStringList()).toStringList(); - mDocumentUI->documentTreeWidget->clear(); - - QMap groupNamesMap; - - UBDocumentGroupTreeItem* emptyGroupNameTi = 0; - - mTrashTi = new UBDocumentGroupTreeItem(0, false); // deleted by the tree widget - mTrashTi->setGroupName(mDocumentTrashGroupName); - mTrashTi->setIcon(0, QIcon(":/images/trash.png")); - - foreach (QPointer proxy, proxies) - { - if (proxy) - { - QString docGroup = proxy->metaData(UBSettings::documentGroupName).toString(); - + // Issue NC - CFA - 20131029 : ajout d'une popup de confirmation pour la suppression definitive + if(pDeletionType == CompleteDelete && !UBApplication::mainWindow->yesNoQuestion(tr("Empty the trash"),tr("You're about to empty the trash.") +"\n\n" + tr("Are you sure ?"))) + return; - bool isEmptyGroupName = false; - bool isInTrash = false; + QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); - if (docGroup.isEmpty()) // #see https://trac.assembla.com/uniboard/ticket/426 - { - docGroup = mDefaultDocumentGroupName; - isEmptyGroupName = true; - } - else if (docGroup.startsWith(UBSettings::trashedDocumentGroupNamePrefix)) - { - isInTrash = true; + UBDocumentTreeModel *docModel = UBPersistenceManager::persistenceManager()->mDocumentTreeStructureModel; + if (!docModel->isCatalog(index)) { + return; } - QString docName = proxy->metaData(UBSettings::documentName).toString(); + while (docModel->rowCount(index)) { + QModelIndex subIndex = docModel->index(0, 0, index); + switch (pDeletionType) { + case MoveToTrash : + docModel->moveIndex(subIndex, docModel->trashIndex()); + break; - if (emptyGroupNames.contains(docGroup)) - emptyGroupNames.removeAll(docGroup); + case CompleteDelete : + deleteIndexAndAssociatedData(subIndex); + break; + default: + break; + } - if (!groupNamesMap.contains(docGroup) && !isInTrash) - { - UBDocumentGroupTreeItem* docGroupItem = new UBDocumentGroupTreeItem(0, !isEmptyGroupName); // deleted by the tree widget - groupNamesMap.insert(docGroup, docGroupItem); - docGroupItem->setGroupName(docGroup); + } - if (isEmptyGroupName) - emptyGroupNameTi = docGroupItem; + QApplication::restoreOverrideCursor(); + // Fin issue NC - CFA - 20131029 } - UBDocumentGroupTreeItem* docGroupItem; - if (isInTrash) - docGroupItem = mTrashTi; - else - docGroupItem = groupNamesMap.value(docGroup); +void UBDocumentController::deleteIndexAndAssociatedData(const QModelIndex &pIndex) +{ + UBDocumentTreeModel *docModel = UBPersistenceManager::persistenceManager()->mDocumentTreeStructureModel; + while (docModel->rowCount(pIndex)) { + QModelIndex subIndex = docModel->index(0, 0, pIndex); + deleteIndexAndAssociatedData(subIndex); + } - QTreeWidgetItem* docItem = new UBDocumentProxyTreeItem(docGroupItem, proxy, !isInTrash); - docItem->setText(0, docName); + //N/C - NNE - 20140408 + if(pIndex.column() == 0){ + if (docModel->isDocument(pIndex)) { + UBDocumentProxy *proxyData = docModel->proxyData(pIndex); - if (mBoardController->selectedDocument() == proxy) -{ - mDocumentUI->documentTreeWidget->expandItem(docGroupItem); - mDocumentUI->documentTreeWidget->setCurrentItem(docGroupItem); + if (proxyData) { + UBPersistenceManager::persistenceManager()->deleteDocument(proxyData); } } } - foreach (const QString emptyGroupName, emptyGroupNames) - { - UBDocumentGroupTreeItem* docGroupItem = new UBDocumentGroupTreeItem(0); // deleted by the tree widget - groupNamesMap.insert(emptyGroupName, docGroupItem); - docGroupItem->setGroupName(emptyGroupName); + docModel->removeRow(pIndex.row(), pIndex.parent()); } - QList groupNamesList = groupNamesMap.keys(); - qSort(groupNamesList); - foreach (const QString groupName, groupNamesList) +void UBDocumentController::exportDocument() { - UBDocumentGroupTreeItem* ti = groupNamesMap.value(groupName); + QAction *currentExportAction = qobject_cast(sender()); + QVariant actionData = currentExportAction->data(); + UBExportAdaptor* selectedExportAdaptor = UBDocumentManager::documentManager()->supportedExportAdaptors()[actionData.toInt()]; - if (ti != emptyGroupNameTi) - mDocumentUI->documentTreeWidget->addTopLevelItem(ti); - } - if (emptyGroupNameTi) - mDocumentUI->documentTreeWidget->addTopLevelItem(emptyGroupNameTi); + UBDocumentProxy* proxy = firstSelectedTreeProxy(); + + selectedExportAdaptor->persist(proxy); + emit exportDone(); - mDocumentUI->documentTreeWidget->addTopLevelItem(mTrashTi); } -void UBDocumentController::itemChanged(QTreeWidgetItem * item, int column) +void UBDocumentController::exportDocumentSet() { - UBDocumentProxyTreeItem* proxyItem = dynamic_cast(item); - - disconnect(UBPersistenceManager::persistenceManager(), SIGNAL(documentMetadataChanged(UBDocumentProxy*)) - , this, SLOT(updateDocumentInTree(UBDocumentProxy*))); - if (proxyItem) { - if (proxyItem->proxy()->metaData(UBSettings::documentName).toString() != item->text(column)) { - // The item was renamed, we should persist it immediately - proxyItem->proxy()->setMetaData(UBSettings::documentName, item->text(column)); - UBPersistenceManager::persistenceManager()->persistDocumentMetadata(proxyItem->proxy()); - } - } - else - { - // it is a group - UBDocumentGroupTreeItem* editedGroup = dynamic_cast(item); - if (editedGroup) - { - for (int i = 0; i < item->childCount(); i++) - { - UBDocumentProxyTreeItem* childItem = dynamic_cast(item->child(i)); +} - if (childItem) - { - if (0 != (item->flags() & Qt::ItemIsEditable)) - { - childItem->proxy()->setMetaData(UBSettings::documentGroupName, item->text(column)); - UBPersistenceManager::persistenceManager()->persistDocumentMetadata(childItem->proxy()); - } - } - } - } - } +void UBDocumentController::documentZoomSliderValueChanged (int value) +{ + mDocumentUI->thumbnailWidget->setThumbnailWidth(value); - connect(UBPersistenceManager::persistenceManager(), SIGNAL(documentMetadataChanged(UBDocumentProxy*)), - this, SLOT(updateDocumentInTree(UBDocumentProxy*))); + UBSettings::settings()->documentThumbnailWidth->set(value); } - void UBDocumentController::importFile() { - UBDocumentGroupTreeItem* group = selectedDocumentGroupTreeItem(); UBDocumentManager *docManager = UBDocumentManager::documentManager(); - if (group) - { - QString defaultPath = UBSettings::settings()->lastImportFilePath->get().toString(); - if(defaultPath.isDetached()) - defaultPath = UBSettings::settings()->userDocumentDirectory(); - QString filePath = QFileDialog::getOpenFileName(mParentWidget, tr("Open Supported File"), defaultPath, docManager->importFileFilter()); + QString defaultPath = UBSettings::settings()->lastImportFilePath->get().toString(); + if(defaultPath.isDetached()) + defaultPath = UBSettings::settings()->userDocumentDirectory(); + QString filePath = QFileDialog::getOpenFileName(mParentWidget, tr("Open Supported File"), + defaultPath, docManager->importFileFilter()); - QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); - QApplication::processEvents(); - QFileInfo fileInfo(filePath); + QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + QApplication::processEvents(); + QFileInfo fileInfo(filePath); + + if (fileInfo.suffix().toLower() == "ubx") { + UBPersistenceManager::persistenceManager()->createDocumentProxiesStructure(docManager->importUbx(filePath, UBSettings::userDocumentDirectory()), true); + + } else { UBSettings::settings()->lastImportFilePath->set(QVariant(fileInfo.absolutePath())); if (filePath.length() > 0) @@ -1092,27 +2587,26 @@ void UBDocumentController::importFile() QApplication::processEvents(); QFile selectedFile(filePath); - QString groupName = group->groupName(); + QString groupName = UBPersistenceManager::myDocumentsName; - if (groupName == mDefaultDocumentGroupName || fileInfo.suffix() == "ubz") - groupName = ""; + groupName = ""; showMessage(tr("Importing file %1...").arg(fileInfo.baseName()), true); createdDocument = docManager->importFile(selectedFile, groupName); - if (createdDocument) - { - selectDocument(createdDocument, false); - } - else - { + + if (createdDocument) { + selectDocument(createdDocument, true, true); + + } else { showMessage(tr("Failed to import file ... ")); } } - - QApplication::restoreOverrideCursor(); } + + QApplication::restoreOverrideCursor(); + } void UBDocumentController::addFolderOfImages() @@ -1144,6 +2638,7 @@ void UBDocumentController::addFolderOfImages() else { document->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(QDateTime::currentDateTime())); + UBMetadataDcSubsetAdaptor::persist(document); reloadThumbnails(); } } @@ -1166,304 +2661,127 @@ void UBDocumentController::addFileToDocument() bool UBDocumentController::addFileToDocument(UBDocumentProxy* document) { QString defaultPath = UBSettings::settings()->lastImportFilePath->get().toString(); - QString filePath = QFileDialog::getOpenFileName(mParentWidget, tr("Open Supported File"), defaultPath, UBDocumentManager::documentManager()->importFileFilter()); + QString filePath = QFileDialog::getOpenFileName(mParentWidget, tr("Open Supported File"), defaultPath, UBDocumentManager::documentManager()->importFileFilter(true)); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); QApplication::processEvents(); - QFileInfo fileInfo(filePath); - UBSettings::settings()->lastImportFilePath->set(QVariant(fileInfo.absolutePath())); - - bool success = false; - - if (filePath.length() > 0) - { - QApplication::processEvents(); // NOTE: We performed this just a few lines before. Is it really necessary to do it again here?? - - showMessage(tr("Importing file %1...").arg(fileInfo.baseName()), true); - - QStringList fileNames; - fileNames << filePath; - success = UBDocumentManager::documentManager()->addFilesToDocument(document, fileNames); - - if (success) - { - document->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(QDateTime::currentDateTime())); - } - else - { - showMessage(tr("Failed to import file ... ")); - } - } - - QApplication::restoreOverrideCursor(); - - return success; -} - - -void UBDocumentController::moveSceneToIndex(UBDocumentProxy* proxy, int source, int target) -{ - if (UBDocumentContainer::movePageToIndex(source, target)) - { - proxy->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(QDateTime::currentDateTime())); - - mDocumentUI->thumbnailWidget->hightlightItem(target); - } -} - - -void UBDocumentController::thumbnailViewResized() -{ - int maxWidth = qMin(UBSettings::maxThumbnailWidth, mDocumentUI->thumbnailWidget->width()); - - mDocumentUI->documentZoomSlider->setMaximum(maxWidth); -} - - -void UBDocumentController::pageSelectionChanged() -{ - if (mIsClosing) - return; - - bool pageSelected = mDocumentUI->thumbnailWidget->selectedItems().count() > 0; - - if (pageSelected) - mSelectionType = Page; - else - mSelectionType = None; - - selectionChanged(); -} - -bool UBDocumentController::isCurrentSelectionInTrash() -{ - bool selectionIsInTrash = false; - foreach (QTreeWidgetItem* item, mCurrentSelection) { - // Find the first valid element; no need to check all of them - UBDocumentProxyTreeItem * document = dynamic_cast(item); - if (document) { - selectionIsInTrash = document->isInTrash(); - break; - } - } - - return selectionIsInTrash; -} - -/** - * @brief Set the currently selected items, after checking the selection is valid - * - * This method compares the current selection with the previous one, and deselects - * the "incorrectly" selected items if necessary. For example, it shouldn't be possible - * to select items both in the trash and out; and the Trash folder shouldn't be - * included in multiple selections. - */ -void UBDocumentController::updateCurrentSelection() -{ - QList newSelection = mDocumentUI->documentTreeWidget->selectedItems(); - - // If the selection is of size 1 or 0, we don't need to do any checking, we just accept it. - if (newSelection.size() <= 1) { - mCurrentSelection = newSelection; - return; - } - - // We don't allow the Trash folder in multiple selections - // If it is currently selected, we deselect all the newly selected items - if (mCurrentSelection.size() == 1) { - UBDocumentGroupTreeItem* folder = dynamic_cast(mCurrentSelection.first()); - - if (folder && folder->isTrashFolder()) { - foreach (QTreeWidgetItem* item, newSelection) { - if (item != folder) - item->setSelected(false); - } - - return; - } - } - - // Find the elements of the new selection that aren't in the old one - QSet newItems = newSelection.toSet().subtract(mCurrentSelection.toSet()); - - bool selectionIsInTrash = isCurrentSelectionInTrash(); - - foreach (QTreeWidgetItem* item, newItems) { - bool addToSelection = true; - UBDocumentProxyTreeItem * document = dynamic_cast(item); - if (document) { - // No mix between trashed and non-trashed items - if (document->isInTrash() != selectionIsInTrash) - addToSelection = false; - } - - UBDocumentGroupTreeItem * folder = dynamic_cast(item); - if (folder) { - // Trash folder is not allowed in multiple selections - if (folder->isTrashFolder()) - addToSelection = false; - - // Don't add a folder when trash items are selected - if (selectionIsInTrash) - addToSelection = false; - } - if (!folder && !document) - addToSelection = false; - - if (addToSelection) { - if (!mCurrentSelection.contains(item)) { - // the .subtract() above doesn't seem to work all the time... - mCurrentSelection.append(item); - } - } - else - item->setSelected(false); - } -} - -void UBDocumentController::selectionChanged() -{ - if (mIsClosing) - return; - - int pageCount = -1; - - UBDocumentProxyTreeItem* proxyTi = selectedDocumentProxyTreeItem(); - - if (proxyTi && proxyTi->proxy()) - pageCount = proxyTi->proxy()->pageCount(); - - bool pageSelected = (mSelectionType == Page); - bool groupSelected = (mSelectionType == Folder); - bool docSelected = (mSelectionType == Document); - bool multipleSelected = (mSelectionType == Multiple); - bool selectedItemsAreInTrash = isCurrentSelectionInTrash(); - - - bool trashSelected = false; // set to true if selected items are in trash or if trash folder is selected - if (groupSelected && selectedDocumentGroupTreeItem()) - trashSelected = selectedDocumentGroupTreeItem()->isTrashFolder(); - - if ((docSelected || pageSelected) && proxyTi) - trashSelected = dynamic_cast(proxyTi->parent())->isTrashFolder(); - - if (multipleSelected) - trashSelected = selectedItemsAreInTrash; - - bool defaultGroupSelected = false; - if (groupSelected && selectedDocumentGroupTreeItem()) - defaultGroupSelected = selectedDocumentGroupTreeItem()->isDefaultFolder(); - - mMainWindow->actionNewDocument->setEnabled((groupSelected || docSelected || pageSelected) && !trashSelected); - mMainWindow->actionExport->setEnabled((docSelected || pageSelected) && !trashSelected); - bool firstSceneSelected = false; - if(docSelected) - mMainWindow->actionDuplicate->setEnabled(!trashSelected); - else if(pageSelected){ - QList selection = mDocumentUI->thumbnailWidget->selectedItems(); - if(pageCount == 1) - mMainWindow->actionDuplicate->setEnabled(!trashSelected); - else{ - for(int i = 0; i < selection.count() && !firstSceneSelected; i += 1){ - if(dynamic_cast(selection.at(i))->sceneIndex() == 0){ - mMainWindow->actionDuplicate->setEnabled(!trashSelected); - firstSceneSelected = true; - } - } - if(!firstSceneSelected) - mMainWindow->actionDuplicate->setEnabled(!trashSelected); - } - } - else - mMainWindow->actionDuplicate->setEnabled(false); - - mMainWindow->actionOpen->setEnabled((docSelected || pageSelected) && !trashSelected); - mMainWindow->actionRename->setEnabled((groupSelected || docSelected) && !trashSelected && !defaultGroupSelected); - - mMainWindow->actionAddToWorkingDocument->setEnabled(pageSelected - && !(selectedDocumentProxy() == mBoardController->selectedDocument()) && !trashSelected); + QFileInfo fileInfo(filePath); + UBSettings::settings()->lastImportFilePath->set(QVariant(fileInfo.absolutePath())); - bool deleteEnabled = false; - if (trashSelected) - { - if (docSelected || multipleSelected) - deleteEnabled = true; - else if (groupSelected && selectedDocumentGroupTreeItem()) - { - if (selectedDocumentGroupTreeItem()->childCount() > 0) - deleteEnabled = true; - } - } - else - { - deleteEnabled = groupSelected || docSelected || pageSelected || multipleSelected; // TODO: clean up. this is weirdly done - } + bool success = false; - if (pageSelected && (pageCount == mDocumentUI->thumbnailWidget->selectedItems().count())) + if (filePath.length() > 0) { - deleteEnabled = false; - } + QApplication::processEvents(); // NOTE: We performed this just a few lines before. Is it really necessary to do it again here?? + QFile selectedFile(filePath); - if(pageSelected && pageCount == 1) - deleteEnabled = false; + showMessage(tr("Importing file %1...").arg(fileInfo.baseName()), true); - mMainWindow->actionDelete->setEnabled(deleteEnabled); + QStringList fileNames; + fileNames << filePath; + success = UBDocumentManager::documentManager()->addFilesToDocument(document, fileNames); - if (trashSelected) - { - if (docSelected || multipleSelected) // TODO: clean this up. have only "selectedItemsAreInTrash" + if (success) { - mMainWindow->actionDelete->setIcon(QIcon(":/images/toolbar/deleteDocument.png")); - mMainWindow->actionDelete->setText(tr("Delete")); + document->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(QDateTime::currentDateTime())); + UBMetadataDcSubsetAdaptor::persist(document); } else { - mMainWindow->actionDelete->setIcon(QIcon(":/images/trash.png")); - mMainWindow->actionDelete->setText(tr("Empty")); + showMessage(tr("Failed to import file ... ")); } } - else + + QApplication::restoreOverrideCursor(); + + return success; +} + + +void UBDocumentController::moveSceneToIndex(UBDocumentProxy* proxy, int source, int target) +{ + if (UBDocumentContainer::movePageToIndex(source, target)) { - mMainWindow->actionDelete->setIcon(QIcon(":/images/trash.png")); - mMainWindow->actionDelete->setText(tr("Trash")); + proxy->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(QDateTime::currentDateTime())); + UBMetadataDcSubsetAdaptor::persist(proxy); + + mDocumentUI->thumbnailWidget->hightlightItem(target); + + mBoardController->setActiveDocumentScene(target); + mBoardController->reloadThumbnails(); } +} + - mMainWindow->actionDocumentAdd->setEnabled((docSelected || pageSelected) && !trashSelected && !multipleSelected); - mMainWindow->actionImport->setEnabled(!trashSelected && !multipleSelected); +void UBDocumentController::thumbnailViewResized() +{ + int maxWidth = qMin(UBSettings::maxThumbnailWidth, mDocumentUI->thumbnailWidget->width()); + mDocumentUI->documentZoomSlider->setMaximum(maxWidth); } +void UBDocumentController::pageSelectionChanged() +{ + if (mIsClosing) + return; + + bool pageSelected = mDocumentUI->thumbnailWidget->selectedItems().count() > 0; + + if (pageSelected) + itemSelectionChanged(Page); + else + itemSelectionChanged(None); + + updateActions(); +} + void UBDocumentController::documentSceneChanged(UBDocumentProxy* proxy, int pSceneIndex) { Q_UNUSED(pSceneIndex); if (proxy == selectedDocumentProxy()) { - emit documentThumbnailsUpdated(this); + reloadThumbnails(); + } + + QModelIndexList sel = mDocumentUI->documentTreeView->selectionModel()->selectedRows(0); + + QModelIndex selection; + if(sel.count() > 0){ + selection = sel.first(); } -} + TreeViewSelectionChanged(selection, QModelIndex()); +} -void UBDocumentController::pageDoubleClicked(QGraphicsItem* item, int index) +void UBDocumentController::thumbnailPageDoubleClicked(QGraphicsItem* item, int index) { - Q_UNUSED(item); - Q_UNUSED(index); + UBDocumentTreeModel *docModel = UBPersistenceManager::persistenceManager()->mDocumentTreeStructureModel; + QModelIndex selectedIndex = firstSelectedTreeIndex(); + + if (selectedIndex.isValid()) { + if (docModel->inModel(selectedIndex)) { + UBApplication::showMessage(tr("The model documents are not editable. Copy it to \"My documents\" to be able to work with")); + return; + } else if (docModel->inTrash(selectedIndex)) { + return; + } + } - bool pageSelected = (mSelectionType == Page); - bool groupSelected = (mSelectionType == Folder); - bool docSelected = (mSelectionType == Document); + QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + UBSceneThumbnailPixmap* thumb = qgraphicsitem_cast (item); - bool trashSelected = false; - if (groupSelected && selectedDocumentGroupTreeItem()) - trashSelected = selectedDocumentGroupTreeItem()->isTrashFolder(); - UBDocumentProxyTreeItem* proxyTi = selectedDocumentProxyTreeItem(); - if ((docSelected || pageSelected) && proxyTi) - trashSelected = dynamic_cast(proxyTi->parent())->isTrashFolder(); - if (trashSelected) return; + if (thumb) { + UBDocumentProxy* proxy = thumb->proxy(); + if (proxy && isOKToOpenDocument(proxy)) { + mBoardController->setActiveDocumentScene(proxy, index); + UBApplication::applicationController->showBoard(); + } + } - openSelectedItem(); + QApplication::restoreOverrideCursor(); } @@ -1476,32 +2794,6 @@ void UBDocumentController::pageClicked(QGraphicsItem* item, int index) } -void UBDocumentController::closing() -{ - mIsClosing = true; - - QStringList emptyGroups; - - for (int i = 0; i < mDocumentUI->documentTreeWidget->topLevelItemCount(); i++) - { - QTreeWidgetItem* item = mDocumentUI->documentTreeWidget->topLevelItem(i); - - if (item->childCount() == 0) - { - UBDocumentGroupTreeItem* groupItem = dynamic_cast(item); - if (groupItem) - { - QString groupName = groupItem->groupName(); - if (!emptyGroups.contains(groupName) && groupName != mDocumentTrashGroupName) - emptyGroups << groupName; - } - } - } - - UBSettings::settings()->setValue("Document/EmptyGroupNames", emptyGroups); - -} - void UBDocumentController::addToDocument() { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); @@ -1527,102 +2819,33 @@ void UBDocumentController::addToDocument() for (int i = 0; i < pageInfoList.length(); i++) { - mBoardController->addScene(pageInfoList.at(i).first, pageInfoList.at(i).second, true); + mBoardController->addScene(pageInfoList.at(i).first, pageInfoList.at(i).second, false); } int newActiveSceneIndex = selectedItems.count() == mBoardController->selectedDocument()->pageCount() ? 0 : oldActiveSceneIndex + 1; mDocumentUI->thumbnailWidget->selectItemAt(newActiveSceneIndex, false); selectDocument(mBoardController->selectedDocument()); mBoardController->selectedDocument()->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(QDateTime::currentDateTime())); + UBMetadataDcSubsetAdaptor::persist(mBoardController->selectedDocument()); + mBoardController->reloadThumbnails(); + //UBApplication::boardController->documentNavigator()->generateThumbnails(this); UBApplication::applicationController->showBoard(); - } - - QApplication::restoreOverrideCursor(); -} - -void UBDocumentController::addDocumentInTree(UBDocumentProxy* pDocument) -{ - QString documentName = pDocument->name(); - QString documentGroup = pDocument->groupName(); - - if (documentGroup.isEmpty()) - { - documentGroup = mDefaultDocumentGroupName; - - } - UBDocumentGroupTreeItem* group = 0; - - if (documentGroup.startsWith(UBSettings::trashedDocumentGroupNamePrefix)) - { - group = mTrashTi; - } - else - { - for (int i = 0; i < mDocumentUI->documentTreeWidget->topLevelItemCount(); i++) - { - QTreeWidgetItem* item = mDocumentUI->documentTreeWidget->topLevelItem(i); - UBDocumentGroupTreeItem* groupItem = dynamic_cast(item); - if (groupItem->groupName() == documentGroup) - { - group = groupItem; - break; - } - } - } - - if (group == 0) - { - group = new UBDocumentGroupTreeItem(0); // deleted by the tree widget - group->setGroupName(documentGroup); - mDocumentUI->documentTreeWidget->addTopLevelItem(group); - } - - UBDocumentProxyTreeItem *ti = new UBDocumentProxyTreeItem(group, pDocument, !group->isTrashFolder()); - ti->setText(0, documentName); - mDocumentUI->documentTreeWidget->editItem(ti,0); -} - - -void UBDocumentController::updateDocumentInTree(UBDocumentProxy* pDocument) -{ - QTreeWidgetItemIterator it(mDocumentUI->documentTreeWidget); - while (*it) - { - UBDocumentProxyTreeItem* pi = dynamic_cast((*it)); - - if (pi && pi->proxy() == pDocument) - { - pi->setText(0, pDocument->name()); - break; - } - ++it; - } -} - -QStringList UBDocumentController::allGroupNames() -{ - QStringList result; - - for (int i = 0; i < mDocumentUI->documentTreeWidget->topLevelItemCount(); i++) - { - QTreeWidgetItem* item = mDocumentUI->documentTreeWidget->topLevelItem(i); - UBDocumentGroupTreeItem* groupItem = dynamic_cast(item); - result << groupItem->groupName(); + mBoardController->setActiveDocumentScene(newActiveSceneIndex); } - return result; + QApplication::restoreOverrideCursor(); } - void UBDocumentController::renameSelectedItem() { - if (mDocumentUI->documentTreeWidget->selectedItems().count() > 0) - mDocumentUI->documentTreeWidget->editItem(mDocumentUI->documentTreeWidget->selectedItems().at(0)); + QModelIndex selectedIndex = firstSelectedTreeIndex(); + if (selectedIndex.isValid()) { + mDocumentUI->documentTreeView->edit(selectedIndex); + } } - bool UBDocumentController::isOKToOpenDocument(UBDocumentProxy* proxy) { //check version @@ -1630,14 +2853,14 @@ bool UBDocumentController::isOKToOpenDocument(UBDocumentProxy* proxy) if (docVersion.isEmpty() || docVersion.startsWith("4.1") || docVersion.startsWith("4.2") || docVersion.startsWith("4.3") || docVersion.startsWith("4.4") || docVersion.startsWith("4.5") - || docVersion.startsWith("4.6") || docVersion.startsWith("4.8")) // TODO UB 4.7 update if necessary + || docVersion.startsWith("4.6") || docVersion.startsWith("4.7")) // TODO UB 4.8 update if necessary { return true; } else { if (UBApplication::mainWindow->yesNoQuestion(tr("Open Document"), - tr("The document '%1' has been generated with a newer version of OpenBoard (%2). By opening it, you may lose some information. Do you want to proceed?") + tr("The document '%1' has been generated with a newer version of Sankore (%2). By opening it, you may lose some information. Do you want to proceed?") .arg(proxy->metaData(UBSettings::documentName).toString()) .arg(docVersion))) { @@ -1659,7 +2882,7 @@ void UBDocumentController::showMessage(const QString& message, bool showSpinning QRect newSize = mDocumentUI->thumbnailWidget->geometry(); - #ifdef Q_OS_OSX + #ifdef Q_WS_MACX QPoint point(newSize.left() + margin, newSize.bottom() - mMessageWindow->height() - margin); mMessageWindow->move(mDocumentUI->thumbnailWidget->mapToGlobal(point)); #else @@ -1714,13 +2937,13 @@ void UBDocumentController::addImages() else { document->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(QDateTime::currentDateTime())); + UBMetadataDcSubsetAdaptor::persist(document); reloadThumbnails(); } } } } - void UBDocumentController::toggleDocumentToolsPalette() { if (!mToolsPalette->isVisible() && !mToolsPalettePositionned) @@ -1761,6 +2984,7 @@ void UBDocumentController::paste() void UBDocumentController::focusChanged(QWidget *old, QWidget *current) { Q_UNUSED(old); + UBDocumentTreeModel *treeModel = UBPersistenceManager::persistenceManager()->mDocumentTreeStructureModel; if (current == mDocumentUI->thumbnailWidget) { @@ -1769,13 +2993,11 @@ void UBDocumentController::focusChanged(QWidget *old, QWidget *current) else mSelectionType = None; } - else if (current == mDocumentUI->documentTreeWidget) + else if (current == mDocumentUI->documentTreeView) { - if (multipleSelection()) - mSelectionType = Multiple; - else if (selectedDocumentProxy()) + if (treeModel->isDocument(firstSelectedTreeIndex())) mSelectionType = Document; - else if (selectedDocumentGroupTreeItem()) + else if (treeModel->isCatalog(firstSelectedTreeIndex())) mSelectionType = Folder; else mSelectionType = None; @@ -1790,14 +3012,158 @@ void UBDocumentController::focusChanged(QWidget *old, QWidget *current) else { if (old != mDocumentUI->thumbnailWidget && - old != mDocumentUI->documentTreeWidget && + old != mDocumentUI->documentTreeView && old != mDocumentUI->documentZoomSlider) { mSelectionType = None; } } +} + +void UBDocumentController::updateActions() +{ + if (mIsClosing) + return; + + UBDocumentTreeModel *docModel = UBPersistenceManager::persistenceManager()->mDocumentTreeStructureModel; + + //N/C - NNE - 20140408 + QModelIndexList list = mDocumentUI->documentTreeView->selectionModel()->selectedRows(0); + + //if in multi selection, juste activate the actionDelete + if(list.count() > 1){ + mMainWindow->actionNewDocument->setEnabled(false); + mMainWindow->actionNewFolder->setEnabled(false); + mMainWindow->actionExport->setEnabled(false); + mMainWindow->actionDuplicate->setEnabled(false); + mMainWindow->actionOpen->setEnabled(false); + mMainWindow->actionRename->setEnabled(false); + + mMainWindow->actionAddToWorkingDocument->setEnabled(false); + mMainWindow->actionDocumentAdd->setEnabled(false); + mMainWindow->actionImport->setEnabled(false); + mMainWindow->actionDelete->setEnabled(true); + + return; + } + //N/C - NNE - 20140408 : END + + QModelIndex selectedIndex = firstSelectedTreeIndex(); + UBDocumentProxy *selectedProxy = docModel->proxyData(selectedIndex); + int pageCount = -1; + if (selectedProxy) { + pageCount = selectedProxy->pageCount(); + } + + bool pageSelected = false; + bool groupSelected = false; + bool docSelected = false; + + if (mSelectionType == Page) { + pageSelected = true; + } else { + if (docModel->isDocument(firstSelectedTreeIndex())) { + docSelected = true; + } else if (docModel->isCatalog(firstSelectedTreeIndex())) { + groupSelected = true; + } + } + + bool trashSelected = docModel->inTrash(selectedIndex) || selectedIndex == docModel->trashIndex() ? true : false; + bool modelSelected = docModel->inModel(selectedIndex) || selectedIndex == docModel->modelsIndex() ? true : false; + + mMainWindow->actionNewDocument->setEnabled(docModel->newNodeAllowed(selectedIndex) && !modelSelected); + mMainWindow->actionNewFolder->setEnabled(docModel->newNodeAllowed(selectedIndex)); + mMainWindow->actionExport->setEnabled((docSelected || pageSelected || groupSelected) && !trashSelected); + updateExportSubActions(selectedIndex); + + bool firstSceneSelected = false; + + if (docSelected) { + mMainWindow->actionDuplicate->setEnabled(!trashSelected && !modelSelected); + + } else if (pageSelected) { + QList selection = mDocumentUI->thumbnailWidget->selectedItems(); + if(pageCount == 1) { + mMainWindow->actionDuplicate->setEnabled(!trashSelected && pageCanBeDuplicated(UBDocumentContainer::pageFromSceneIndex(0))); + + } else { + for (int i = 0; i < selection.count() && !firstSceneSelected; i += 1) { + if (qgraphicsitem_cast(selection.at(i))->sceneIndex() == 0) { + mMainWindow->actionDuplicate->setEnabled(!trashSelected && pageCanBeDuplicated(UBDocumentContainer::pageFromSceneIndex(0))); + firstSceneSelected = true; + break; + } + } + if (!firstSceneSelected) { + mMainWindow->actionDuplicate->setEnabled(!trashSelected); + } + } + + } else { + mMainWindow->actionDuplicate->setEnabled(false); + } + + mMainWindow->actionOpen->setEnabled((docSelected || pageSelected) && !trashSelected && !modelSelected); + mMainWindow->actionRename->setEnabled(docModel->isOkToRename(selectedIndex)); + + mMainWindow->actionAddToWorkingDocument->setEnabled(pageSelected + && !(selectedProxy == mBoardController->selectedDocument()) && !trashSelected); + + DeletionType deletionForSelection = deletionTypeForSelection(mSelectionType, selectedIndex, docModel); + mMainWindow->actionDelete->setEnabled(deletionForSelection != NoDeletion); + + switch (static_cast(deletionForSelection)) { + case MoveToTrash : + case DeletePage : + mMainWindow->actionDelete->setIcon(QIcon(":/images/trash.png")); + mMainWindow->actionDelete->setText(tr("Trash")); + break; + case CompleteDelete : + mMainWindow->actionDelete->setIcon(QIcon(":/images/toolbar/deleteDocument.png")); + mMainWindow->actionDelete->setText(tr("Delete")); + break; + case EmptyFolder : + mMainWindow->actionDelete->setIcon(QIcon(":/images/trash.png")); + mMainWindow->actionDelete->setText(tr("Empty")); + break; + case EmptyTrash : + mMainWindow->actionDelete->setIcon(QIcon(":/images/toolbar/deleteDocument.png")); + mMainWindow->actionDelete->setText(tr("Empty")); + break; + } + + mMainWindow->actionDocumentAdd->setEnabled((docSelected || pageSelected) && !trashSelected && !modelSelected); + mMainWindow->actionImport->setEnabled(!trashSelected); + +} + +void UBDocumentController::updateExportSubActions(const QModelIndex &selectedIndex) +{ + UBDocumentManager *documentManager = UBDocumentManager::documentManager(); + for (int i = 0; i < documentManager->supportedExportAdaptors().length(); i++) + { + UBExportAdaptor* adaptor = documentManager->supportedExportAdaptors()[i]; + if (adaptor->associatedAction()) { + adaptor->associatedAction()->setEnabled(adaptor->associatedActionactionAvailableFor(selectedIndex)); + } + } +} + +void UBDocumentController::currentIndexMoved(const QModelIndex &newIndex, const QModelIndex &PreviousIndex) +{ + Q_UNUSED(newIndex); + Q_UNUSED(PreviousIndex); - selectionChanged(); + UBDocumentTreeModel *docModel = UBPersistenceManager::persistenceManager()->mDocumentTreeStructureModel; + UBDocumentProxy *newProxy = docModel->proxyData(newIndex); + if (newProxy) { + UBDocumentProxy *cp = new UBDocumentProxy(*newProxy); // we cannot use newProxy because it will be destroyed later + pureSetDocument(cp); + mBoardController->pureSetDocument(cp); + mBoardController->pureSetDocument(newProxy); + } + mCurrentIndexMoved = true; } void UBDocumentController::deletePages(QList itemsToDelete) @@ -1818,21 +3184,26 @@ void UBDocumentController::deletePages(QList itemsToDelete) { sceneIndexes.append(thumb->sceneIndex()); } + } } - if(UBApplication::mainWindow->yesNoQuestion(tr("Remove Page"), tr("Are you sure you want to remove %n page(s) from the selected document '%1'?", "", sceneIndexes.count()).arg(proxy->metaData(UBSettings::documentName).toString()))) + if(UBApplication::mainWindow->yesNoQuestion(tr("Remove Page"),tr("This is an irreversible action!") +"\n\n" + tr("Are you sure you want to remove %n page(s) from the selected document '%1'?", "", sceneIndexes.count()).arg(proxy->metaData(UBSettings::documentName).toString()))) { UBDocumentContainer::deletePages(sceneIndexes); + //mBoardController->regenerateThumbnails(); proxy->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(QDateTime::currentDateTime())); + UBMetadataDcSubsetAdaptor::persist(proxy); int minIndex = proxy->pageCount() - 1; foreach (int i, sceneIndexes) minIndex = qMin(i, minIndex); mDocumentUI->thumbnailWidget->selectItemAt(minIndex); - UBApplication::boardController->setActiveDocumentScene(minIndex); + + mBoardController->setActiveDocumentScene(minIndex); + mBoardController->reloadThumbnails(); } } } @@ -1849,51 +3220,135 @@ int UBDocumentController::getSelectedItemIndex() else return -1; } +bool UBDocumentController::pageCanBeMovedUp(int page) +{ + return page >= 1; +} + +bool UBDocumentController::pageCanBeMovedDown(int page) +{ + return page < selectedDocument()->pageCount() - 1; +} + +bool UBDocumentController::pageCanBeDuplicated(int page) +{ + return page != 0; +} + +bool UBDocumentController::pageCanBeDeleted(int page) +{ + return page != 0; +} + +void UBDocumentController::setDocument(UBDocumentProxy *document, bool forceReload) +{ + UBDocumentContainer::setDocument(document, forceReload); +} + +QModelIndex UBDocumentController::firstSelectedTreeIndex() +{ + return selectedTreeIndexes().count() ? selectedTreeIndexes().first() : QModelIndex(); +} + +UBDocumentController::DeletionType +UBDocumentController::deletionTypeForSelection(LastSelectedElementType pTypeSelection + , const QModelIndex &selectedIndex + , UBDocumentTreeModel *docModel) const +{ + if (pTypeSelection == Page) { + if (!firstSceneSelected()) { + return DeletePage; + } + } else if (docModel->isConstant(selectedIndex)) { + if (selectedIndex == docModel->trashIndex()) { + return EmptyTrash; + } + return EmptyFolder; + } else if (pTypeSelection != None) { + if (docModel->inTrash(selectedIndex)) { + return CompleteDelete; + } else { + return MoveToTrash; + } + } + + return NoDeletion; +} + +bool UBDocumentController::firstSceneSelected() const +{ + bool firstSceneSelected = false; + QList selection = mDocumentUI->thumbnailWidget->selectedItems(); + for(int i = 0; i < selection.count() && !firstSceneSelected; i += 1){ + if(dynamic_cast(selection.at(i))->sceneIndex() == 0){ + firstSceneSelected = true; + } + } + + return firstSceneSelected; +} + void UBDocumentController::refreshDocumentThumbnailsView(UBDocumentContainer*) { + UBDocumentTreeModel *docModel = UBPersistenceManager::persistenceManager()->mDocumentTreeStructureModel; + UBDocumentProxy *currentDocumentProxy = selectedDocument(); + + QModelIndex current = docModel->indexForProxy(currentDocumentProxy); + + if (!current.isValid()) { + mDocumentUI->thumbnailWidget->setGraphicsItems(QList() + , QList() + , QStringList() + , UBApplication::mimeTypeUniboardPage); + return; + } + + QList thumbs; + + if (currentDocumentProxy) + { + UBThumbnailAdaptor::load(currentDocumentProxy, thumbs); + } + QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); QList items; QList itemsPath; - UBDocumentProxy *proxy = selectedDocumentProxy(); QGraphicsPixmapItem *selection = 0; - // Don't display thumbnails if multiple documents are selected - if (multipleSelection()) - proxy = 0; - QStringList labels; - if (proxy) + if (currentDocumentProxy) { - setDocument(proxy); - initThumbPage(); - for (int i = 0; i < selectedDocument()->pageCount(); i++) + for (int i = 0; i < currentDocumentProxy->pageCount(); i++) { - const QPixmap* pix = pageAt(i); - QGraphicsPixmapItem *pixmapItem = new UBSceneThumbnailPixmap(*pix, proxy, i); // deleted by the tree widget + const QPixmap* pix = thumbs.at(i); + QGraphicsPixmapItem *pixmapItem = new UBSceneThumbnailPixmap(*pix, currentDocumentProxy, i); // deleted by the tree widget - if (proxy == mBoardController->selectedDocument() && mBoardController->activeSceneIndex() == i) + if (currentDocumentProxy == mBoardController->selectedDocument() && mBoardController->activeSceneIndex() == i) { selection = pixmapItem; } items << pixmapItem; int pageIndex = pageFromSceneIndex(i); - labels << tr("Page %1").arg(pageIndex); + if(pageIndex) + labels << tr("Page %1").arg(pageIndex); + else + labels << tr("Title page"); - itemsPath.append(QUrl::fromLocalFile(proxy->persistencePath() + QString("/pages/%1").arg(UBDocumentContainer::pageFromSceneIndex(i)))); + itemsPath.append(QUrl::fromLocalFile(currentDocumentProxy->persistencePath() + QString("/pages/%1").arg(UBDocumentContainer::pageFromSceneIndex(i)))); } } mDocumentUI->thumbnailWidget->setGraphicsItems(items, itemsPath, labels, UBApplication::mimeTypeUniboardPage); - UBDocumentProxyTreeItem* proxyTi = selectedDocumentProxyTreeItem(); - if (proxyTi && (proxyTi->parent() == mTrashTi)) + if (docModel->inTrash(current) || docModel->inModel(current)) { mDocumentUI->thumbnailWidget->setDragEnabled(false); - else + } else { mDocumentUI->thumbnailWidget->setDragEnabled(true); + } mDocumentUI->thumbnailWidget->ensureVisible(0, 0, 10, 10); @@ -1908,21 +3363,50 @@ void UBDocumentController::refreshDocumentThumbnailsView(UBDocumentContainer*) QApplication::restoreOverrideCursor(); } -bool UBDocumentController::multipleSelection() +void UBDocumentController::createNewDocumentInUntitledFolder() { - QList items = mDocumentUI->documentTreeWidget->selectedItems(); - return (items.size() > 1); + UBPersistenceManager *pManager = UBPersistenceManager::persistenceManager(); + UBDocumentTreeModel *docModel = pManager->mDocumentTreeStructureModel; + + QString groupName = docModel->virtualPathForIndex(docModel->untitledDocumentsIndex()); + + UBDocumentProxy *document = pManager->createDocument(groupName); + selectDocument(document); + + if (document) + pManager->mDocumentTreeStructureModel->markDocumentAsNew(document); } -UBDocumentController::LastSelectedElementType UBDocumentController::itemType(QTreeWidgetItem * item) +void UBDocumentController::collapseAll() { - UBDocumentProxyTreeItem * document = dynamic_cast(item); - if (document) - return Document; + //disable the animations because the view port will be in a invalid state + mDocumentUI->documentTreeView->setAnimated(false); + + mDocumentUI->documentTreeView->collapseAll(); + + QPersistentModelIndex untiltedDocumentIndex = UBPersistenceManager::persistenceManager()->mDocumentTreeStructureModel->untitledDocumentsIndex(); + QPersistentModelIndex myDocumentIndex = UBPersistenceManager::persistenceManager()->mDocumentTreeStructureModel->myDocumentsIndex(); + + UBSortFilterProxyModel *proxy = dynamic_cast(mDocumentUI->documentTreeView->model()); + + if(proxy){ + mDocumentUI->documentTreeView->setExpanded(proxy->mapFromSource(myDocumentIndex), true); + mDocumentUI->documentTreeView->setExpanded(proxy->mapFromSource(untiltedDocumentIndex), true); + }else{ + mDocumentUI->documentTreeView->setExpanded(myDocumentIndex, true); + mDocumentUI->documentTreeView->setExpanded(untiltedDocumentIndex, true); + } + + mDocumentUI->documentTreeView->setAnimated(true); +} + +//N/C - NNE - 20140513 +void UBDocumentController::expandAll() +{ + //disable the animations because the view port will be in a invalid state + mDocumentUI->documentTreeView->setAnimated(false); - UBDocumentGroupTreeItem * folder = dynamic_cast(item); - if (folder) - return Folder; + mDocumentUI->documentTreeView->expandAll(); - return None; + mDocumentUI->documentTreeView->setAnimated(true); } diff --git a/src/document/UBDocumentController.h b/src/document/UBDocumentController.h index 62160800..796242c8 100644 --- a/src/document/UBDocumentController.h +++ b/src/document/UBDocumentController.h @@ -1,38 +1,36 @@ /* - * Copyright (C) 2015-2016 Département de l'Instruction Publique (DIP-SEM) + * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA) * - * Copyright (C) 2013 Open Education Foundation + * This file is part of Open-Sankoré. * - * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour - * l'Education Numérique en Afrique (GIP ENA) - * - * This file is part of OpenBoard. - * - * OpenBoard is free software: you can redistribute it and/or modify + * Open-Sankoré is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 of the License, * with a specific linking exception for the OpenSSL project's * "OpenSSL" library (or with modified versions of it that use the * same license as the "OpenSSL" library). * - * OpenBoard is distributed in the hope that it will be useful, + * Open-Sankoré 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 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with OpenBoard. If not, see . + * along with Open-Sankoré. If not, see . */ - #ifndef UBDOCUMENTCONTROLLER_H_ #define UBDOCUMENTCONTROLLER_H_ #include -#include #include "document/UBDocumentContainer.h" +#include "core/UBApplicationController.h" +#include "core/UBApplication.h" + + +#include "document/UBSortFilterProxyModel.h" namespace Ui { @@ -51,13 +49,323 @@ class UBDocumentProxyTreeItem; class UBMainWindow; class UBDocumentToolsPalette; -class UBDocumentController : public UBDocumentContainer + +class UBDocumentReplaceDialog : public QDialog +{ + Q_OBJECT + +public: + UBDocumentReplaceDialog(const QString &pIncommingName, const QStringList &pFileList, QWidget *parent = 0, Qt::WindowFlags pFlags = 0); + void setRegexp(const QRegExp pRegExp); + bool validString(const QString &pStr); + void setFileNameAndList(const QString &fileName, const QStringList &pLst); + QString labelTextWithName(const QString &documentName) const; + QString lineEditText() const {return mLineEdit->text();} + +signals: + void createNewFolder(QString str); + void closeDialog(); + +private slots: + void accept(); + void reject(); + + void reactOnTextChanged(const QString &pStr); + +private: + QLineEdit *mLineEdit; + QRegExpValidator *mValidator; + QStringList mFileNameList; + QString mIncommingName; + QPushButton *acceptButton; + const QString acceptText; + const QString replaceText; + const QString cancelText; + QLabel *mLabelText; +}; + + +class UBDocumentTreeNode +{ +public: + friend class UBDocumentTreeModel; + + enum Type { + Catalog = 0 + , Document + }; + + UBDocumentTreeNode(Type pType, const QString &pName, const QString &pDisplayName = QString(), UBDocumentProxy *pProxy = 0); + UBDocumentTreeNode() : mType(Catalog), mParent(0), mProxy(0) {;} + ~UBDocumentTreeNode(); + + QList children() const {return mChildren;} + UBDocumentTreeNode *parentNode() {return mParent;} + Type nodeType() const {return mType;} + QString nodeName() const {return mName;} + QString displayName() const {return mDisplayName;} + void setNodeName(const QString &str) {mName = str; mDisplayName = str;} + void addChild(UBDocumentTreeNode *pChild); + void insertChild(int pIndex, UBDocumentTreeNode *pChild); + void moveChild(UBDocumentTreeNode *child, int index, UBDocumentTreeNode *newParent); + void removeChild(int index); + UBDocumentProxy *proxyData() const {return mProxy;} + bool isRoot() {return !mParent;} + bool isTopLevel() {return mParent && !mParent->mParent;} + UBDocumentTreeNode *clone(); + QString dirPathInHierarchy(); + + //issue 1629 - NNE - 20131105 : Add some utility methods + bool findNode(UBDocumentTreeNode *node); + UBDocumentTreeNode *nextSibling(); + UBDocumentTreeNode *previousSibling(); + //issue 1629 - NNE - 20131105 : END + +private: + Type mType; + QString mName; + QString mDisplayName; + UBDocumentTreeNode *mParent; + QList mChildren; + QPointer mProxy; +}; +Q_DECLARE_METATYPE(UBDocumentTreeNode*) + +class UBDocumentTreeModel : public QAbstractItemModel { + Q_OBJECT + +public: + enum eAncestors { + aMyDocuments + , aUntitledDocuments + , aModel + , aTrash + }; + + enum eCopyMode { + aReference + , aContentCopy + }; + + enum eAddItemMode { + aEnd = 0 //Add to the end of the children list + , aDetectPosition //Detect the appropriate position (sorting) + }; + + enum eDocumentData{ + DataNode = Qt::UserRole +1, + CreationDate, + UpdateDate + }; + + UBDocumentTreeModel(QObject *parent = 0); + ~UBDocumentTreeModel(); + + QModelIndex index(int row, int column, const QModelIndex &parent) const; + QModelIndex parent(const QModelIndex &child) const; + + int rowCount(const QModelIndex &parent) const; + int columnCount(const QModelIndex &parent) const; + QVariant data(const QModelIndex &index, int role) const; + bool setData(const QModelIndex &index, const QVariant &value, int role); + Qt::ItemFlags flags ( const QModelIndex & index ) const; + Qt::DropActions supportedDropActions() const {return Qt::MoveAction | Qt::CopyAction;} + QStringList mimeTypes() const; + QMimeData *mimeData (const QModelIndexList &indexes) const; + bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); + bool removeRows(int row, int count, const QModelIndex &parent); + + QModelIndex indexForNode(UBDocumentTreeNode *pNode) const; + QPersistentModelIndex persistentIndexForNode(UBDocumentTreeNode *pNode); +// bool insertRow(int row, const QModelIndex &parent); + + QPersistentModelIndex copyIndexToNewParent(const QModelIndex &source, const QModelIndex &newParent, eCopyMode pMode = aReference); + + //N/C - NNE - 20140411 + void copyIndexToNewParent(const QModelIndexList &list, const QModelIndex &newParent, eCopyMode pMode); + + void moveIndex(const QModelIndex &what, const QModelIndex &destination); + UBDocumentTreeNode *currentNode() const {return mCurrentNode;} //work around for sorting model. + void setCurrentNode(UBDocumentTreeNode *pNode) {mCurrentNode = pNode;} + QModelIndex currentIndex() {return indexForNode(mCurrentNode);} //index representing a current document + QModelIndex indexForProxy(UBDocumentProxy *pSearch) const; + void setCurrentDocument(UBDocumentProxy *pDocument); + void setRootNode(UBDocumentTreeNode *pRoot); + UBDocumentTreeNode *rootNode() const {return mRootNode;} + UBDocumentProxy *proxyForIndex(const QModelIndex &pIndex) const; + QString virtualDirForIndex(const QModelIndex &pIndex) const; + QString virtualPathForIndex(const QModelIndex &pIndex) const; + QStringList nodeNameList(const QModelIndex &pIndex) const; + bool newNodeAllowed(const QModelIndex &pSelectedIndex) const; + QModelIndex goTo(const QString &dir); + bool inTrash(const QModelIndex &index) const; + bool inModel(const QModelIndex &index) const; + bool inUntitledDocuments(const QModelIndex &index) const; + bool isCatalog(const QModelIndex &index) const {return nodeFromIndex(index)->nodeType() == UBDocumentTreeNode::Catalog;} + bool isDocument(const QModelIndex &index) const {return nodeFromIndex(index)->nodeType() == UBDocumentTreeNode::Document;} + bool isToplevel(const QModelIndex &index) const {return nodeFromIndex(index) ? nodeFromIndex(index)->isTopLevel() : false;} + bool isConstant(const QModelIndex &index) const {return isToplevel(index) || (index == mUntitledDocuments);} + bool isOkToRename(const QModelIndex &index) const {return flags(index) & Qt::ItemIsEditable;} + UBDocumentProxy *proxyData(const QModelIndex &index) const {return nodeFromIndex(index)->proxyData();} + void addDocument(UBDocumentProxy *pProxyData, const QModelIndex &pParent = QModelIndex()); + void addNewDocument(UBDocumentProxy *pProxyData, const QModelIndex &pParent = QModelIndex()); + void addCatalog(const QString &pName, const QModelIndex &pParent); + QList newDocuments() {return mNewDocuments;} + void markDocumentAsNew(UBDocumentProxy *pDoc) {if (indexForProxy(pDoc).isValid()) mNewDocuments << pDoc;} + void setNewName(const QModelIndex &index, const QString &newName); + QString adjustNameForParentIndex(const QString &pName, const QModelIndex &pIndex); + + QPersistentModelIndex myDocumentsIndex() const {return mMyDocuments;} + QPersistentModelIndex modelsIndex() const {return mModels;} + QPersistentModelIndex trashIndex() const {return mTrash;} + QPersistentModelIndex untitledDocumentsIndex() const {return mUntitledDocuments;} + UBDocumentTreeNode *nodeFromIndex(const QModelIndex &pIndex) const; + static bool nodeLessThan(const UBDocumentTreeNode *firstIndex, const UBDocumentTreeNode *secondIndex); + void setHighLighted(const QModelIndex &newHighLighted) {mHighLighted = newHighLighted;} + QModelIndex highLighted() {return mHighLighted;} + + //N/C - NNE - 20140407 + bool ascendingOrder() const{ return mAscendingOrder; } + + QDateTime findNodeDate(UBDocumentTreeNode *node, QString type) const; + bool inMyDocuments(const QModelIndex &index) const; + void moveIndexes(const QModelIndexList &source, const QModelIndex &destination); + //N/C - NNE - 20140407 : END + bool isDescendantOf(const QModelIndex &pPossibleDescendant, const QModelIndex &pPossibleAncestor) const; + +signals: + void indexChanged(const QModelIndex &newIndex, const QModelIndex &oldIndex); + void currentIndexMoved(const QModelIndex &newIndex, const QModelIndex &previous); /* Be aware that when you got the signal + "previous" index would have allready been deleted. + check it for "valid" first */ + +private: + UBDocumentTreeNode *mRootNode; + UBDocumentTreeNode *mCurrentNode; + + UBDocumentTreeNode *findProxy(UBDocumentProxy *pSearch, UBDocumentTreeNode *pParent) const; + QModelIndex pIndexForNode(const QModelIndex &parent, UBDocumentTreeNode *pNode) const; + QModelIndex addNode(UBDocumentTreeNode *pFreeNode, const QModelIndex &pParent, eAddItemMode pMode = aDetectPosition); + int positionForParent(UBDocumentTreeNode *pFreeNode, UBDocumentTreeNode *pParentNode); + void fixNodeName(const QModelIndex &source, const QModelIndex &dest); + void updateIndexNameBindings(UBDocumentTreeNode *nd); + QPersistentModelIndex mRoot; + QPersistentModelIndex mMyDocuments; + QPersistentModelIndex mModels; + QPersistentModelIndex mTrash; + QPersistentModelIndex mUntitledDocuments; + QList mNewDocuments; + QModelIndex mHighLighted; + + //N/C - NNE - 20140407 + bool mAscendingOrder; + + QDateTime findCatalogUpdatedDate(UBDocumentTreeNode *node) const; + QDateTime findCatalogCreationDate(UBDocumentTreeNode *node) const; + //N/C - NNE - 20140407 : END +}; + +class UBDocumentTreeMimeData : public QMimeData +{ + Q_OBJECT + + public: + QList indexes() const {return mIndexes;} + void setIndexes (const QList &pIndexes) {mIndexes = pIndexes;} + + private: + QList mIndexes; +}; + +class UBDocumentTreeView : public QTreeView { Q_OBJECT - friend class UBDocumentTreeWidget; +public: + UBDocumentTreeView (QWidget *parent = 0); + + //N/C - NNE - 20140404 + QModelIndex mapIndexToSource(const QModelIndex &index); + QModelIndexList mapIndexesToSource(const QModelIndexList &indexes); + +public slots: + void setSelectedAndExpanded(const QModelIndex &pIndex, bool pExpand = true); + void onModelIndexChanged(const QModelIndex &pNewIndex, const QModelIndex &pOldIndex); + void hSliderRangeChanged(int min, int max); + +protected: + void dragEnterEvent(QDragEnterEvent *event); + void dragLeaveEvent(QDragLeaveEvent *event); + void dragMoveEvent(QDragMoveEvent *event); + void dropEvent(QDropEvent *event); + void paintEvent(QPaintEvent *event); + + + UBDocumentTreeModel *fullModel() {return qobject_cast(model());} + void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); + +private: + bool isAcceptable(const QModelIndex &dragIndex, const QModelIndex &atIndex); + Qt::DropAction acceptableAction(const QModelIndex &dragIndex, const QModelIndex &atIndex); + void updateIndexEnvirons(const QModelIndex &index); +}; + +class UBDocumentTreeItemDelegate : public QStyledItemDelegate +{ + Q_OBJECT + +public: + UBDocumentTreeItemDelegate(QObject *parent = 0); + +private slots: + void commitAndCloseEditor(); + void processChangedText(const QString &str) const; + bool validateString(const QString &str) const; + +protected: + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const; + void setEditorData(QWidget *editor, const QModelIndex &index) const; + void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const; + void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex &index) const; + +private: + mutable QStringList mExistingFileNames; +}; + +class UBDocumentController : public UBDocumentContainer +{ + Q_OBJECT public: + + enum DeletionType { + MoveToTrash = 0 + , CompleteDelete + , EmptyFolder + , EmptyTrash + , DeletePage + , NoDeletion + }; + + enum LastSelectedElementType + { + None = 0, Folder, Document, Page + }; + + enum SortOrder + { + ASC = 0, + DESC + }; + + enum SortKind + { + Reset = 0, + CreationDate, + UpdateDate, + Alphabetical + }; + UBDocumentController(UBMainWindow* mainWindow); virtual ~UBDocumentController(); @@ -68,22 +376,64 @@ class UBDocumentController : public UBDocumentContainer void deletePages(QList itemsToDelete); int getSelectedItemIndex(); + + bool pageCanBeMovedUp(int page); + bool pageCanBeMovedDown(int page); + bool pageCanBeDuplicated(int page); + bool pageCanBeDeleted(int page); QString documentTrashGroupName(){ return mDocumentTrashGroupName;} QString defaultDocumentGroupName(){ return mDefaultDocumentGroupName;} + void setDocument(UBDocumentProxy *document, bool forceReload = false); + QModelIndex firstSelectedTreeIndex(); + UBDocumentProxy *firstSelectedTreeProxy(); + inline DeletionType deletionTypeForSelection(LastSelectedElementType pTypeSelection + , const QModelIndex &selectedIndex + , UBDocumentTreeModel *docModel) const; + bool firstSceneSelected() const; + QWidget *mainWidget() const {return mDocumentWidget;} + + //issue 1629 - NNE - 20131212 + /** + * Check if the current view will be deleted. If it' true, + * this function assures a view will be always selected, even if + * no view is availaible by creating a new one. + * + * \param index The index that will be deleted. + * \param docModel The tree model which represents the organisation of the document hierarchy + * + * \return True if the index passed in argument was the current view, false otherwise. + */ + void moveToTrash(QModelIndex &index, UBDocumentTreeModel* docModel); + + QModelIndex mapIndexToSource(const QModelIndex &index); + QModelIndexList mapIndexesToSource(const QModelIndexList &indexes); + + void sortDocuments(int kind, int order); + + void moveIndexesToTrash(const QModelIndexList &list, UBDocumentTreeModel *docModel); + QModelIndex findPreviousSiblingNotSelected(const QModelIndex &index, QItemSelectionModel *selectionModel); + QModelIndex findNextSiblingNotSelected(const QModelIndex &index, QItemSelectionModel *selectionModel); + bool parentIsSelected(const QModelIndex& child, QItemSelectionModel *selectionModel); + signals: void exportDone(); public slots: void createNewDocument(); + //issue 1629 - NNE - 20131105 + void createNewDocumentInUntitledFolder(); + void createNewDocumentGroup(); void deleteSelectedItem(); + void emptyFolder(const QModelIndex &index, DeletionType pDeletionType = MoveToTrash); + void deleteIndexAndAssociatedData(const QModelIndex &pIndex); void renameSelectedItem(); void openSelectedItem(); void duplicateSelectedItem(); void importFile(); void moveSceneToIndex(UBDocumentProxy* proxy, int source, int target); - void selectDocument(UBDocumentProxy* proxy, bool setAsCurrentDocument = true); + void selectDocument(UBDocumentProxy* proxy, bool setAsCurrentDocument = true, const bool onImport = false); void show(); void hide(); void showMessage(const QString& message, bool showSpinningWheel = false); @@ -93,22 +443,28 @@ class UBDocumentController : public UBDocumentContainer void copy(); void paste(); void focusChanged(QWidget *old, QWidget *current); + void updateActions(); + void updateExportSubActions(const QModelIndex &selectedIndex); + void currentIndexMoved(const QModelIndex &newIndex, const QModelIndex &PreviousIndex); + + //N/C - NNE - 20140403 + void onSortKindChanged(int index); + void onSortOrderChanged(int index); + void collapseAll(); + void expandAll(); - protected: +protected: virtual void setupViews(); virtual void setupToolbar(); void setupPalettes(); bool isOKToOpenDocument(UBDocumentProxy* proxy); UBDocumentProxy* selectedDocumentProxy(); + QList selectedProxies(); + QModelIndexList selectedTreeIndexes(); UBDocumentProxyTreeItem* selectedDocumentProxyTreeItem(); UBDocumentGroupTreeItem* selectedDocumentGroupTreeItem(); QStringList allGroupNames(); - enum LastSelectedElementType - { - None = 0, Folder, Document, Page, Multiple - }; - LastSelectedElementType mSelectionType; private: @@ -125,45 +481,45 @@ class UBDocumentController : public UBDocumentContainer UBDocumentToolsPalette *mToolsPalette; bool mToolsPalettePositionned; UBDocumentGroupTreeItem* mTrashTi; - QList mCurrentSelection; + + void moveDocumentToTrash(UBDocumentGroupTreeItem* groupTi, UBDocumentProxyTreeItem *proxyTi); + void moveFolderToTrash(UBDocumentGroupTreeItem* groupTi); QString mDocumentTrashGroupName; QString mDefaultDocumentGroupName; - void selectADocumentOnTrashingSelectedOne(UBDocumentGroupTreeItem* groupTi,UBDocumentProxyTreeItem *proxyTi); - void selectFirstDocumentInList(); - void selectATreeItemOnMultipleTrashing(bool selectNewCurrentDocument = false); - void moveDocumentToTrash(UBDocumentGroupTreeItem* groupTi, UBDocumentProxyTreeItem *proxyTi, bool selectNewDocument); - void moveFolderToTrash(UBDocumentGroupTreeItem* groupTi); - void emptyTrash(bool showConfirmationDialog); - void deleteTreeItem(QTreeWidgetItem * item, bool showConfirmationDialog, bool selectNewDocument); + UBDocumentProxy *mCurrentTreeDocument; + bool mCurrentIndexMoved; - void updateCurrentSelection(); - bool multipleSelection(); - bool isDocumentInTrash(UBDocumentProxyTreeItem * document); - bool isCurrentSelectionInTrash(); - LastSelectedElementType itemType(QTreeWidgetItem * item); + UBSortFilterProxyModel *mSortFilterProxyModel; + + //N/C - NNE - 20140407 + bool mUserHasChangedSortOrder; + + public slots: + void TreeViewSelectionChanged(const QModelIndex ¤t, const QModelIndex &previous); + void TreeViewSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected); - private slots: + private slots: void documentZoomSliderValueChanged (int value); - void loadDocumentProxies(); - void itemSelectionChanged(); + void itemSelectionChanged(LastSelectedElementType newSelection); void exportDocument(); - void itemChanged(QTreeWidgetItem * item, int column); + void exportDocumentSet(); + void thumbnailViewResized(); void pageSelectionChanged(); - void selectionChanged(); + void documentSceneChanged(UBDocumentProxy* proxy, int pSceneIndex); - void pageDoubleClicked(QGraphicsItem* item, int index); + + void thumbnailPageDoubleClicked(QGraphicsItem* item, int index); void pageClicked(QGraphicsItem* item, int index); void addToDocument(); - void addDocumentInTree(UBDocumentProxy* pDocument); - void updateDocumentInTree(UBDocumentProxy* pDocument); + void addFolderOfImages(); void addFileToDocument(); void addImages(); - void refreshDocumentThumbnailsView(UBDocumentContainer* source); }; + #endif /* UBDOCUMENTCONTROLLER_H_ */ diff --git a/src/document/UBDocumentProxy.cpp b/src/document/UBDocumentProxy.cpp index f90e6286..768bae4d 100644 --- a/src/document/UBDocumentProxy.cpp +++ b/src/document/UBDocumentProxy.cpp @@ -46,6 +46,15 @@ UBDocumentProxy::UBDocumentProxy() init(); } +UBDocumentProxy::UBDocumentProxy(const UBDocumentProxy &rValue) : + QObject() +{ + mPersistencePath = rValue.mPersistencePath; + mMetaDatas = rValue.mMetaDatas; + mIsModified = rValue.mIsModified; + mPageCount = rValue.mPageCount; +} + UBDocumentProxy::UBDocumentProxy(const QString& pPersistancePath) : mPageCount(0) @@ -70,6 +79,10 @@ void UBDocumentProxy::init() setDefaultDocumentSize(UBSettings::settings()->pageSize->get().toSize()); } +bool UBDocumentProxy::theSameDocument(UBDocumentProxy *proxy) +{ + return proxy && mPersistencePath == proxy->mPersistencePath; +} UBDocumentProxy::~UBDocumentProxy() { diff --git a/src/document/UBDocumentProxy.h b/src/document/UBDocumentProxy.h index 3012b6a3..16703bc0 100644 --- a/src/document/UBDocumentProxy.h +++ b/src/document/UBDocumentProxy.h @@ -47,11 +47,13 @@ class UBDocumentProxy : public QObject public: UBDocumentProxy(); + UBDocumentProxy(const UBDocumentProxy &rValue); UBDocumentProxy(const QString& pPersistencePath); virtual ~UBDocumentProxy(); UBDocumentProxy * deepCopy() const; + bool theSameDocument(UBDocumentProxy *proxy); QString persistencePath() const; diff --git a/src/document/UBSortFilterProxyModel.cpp b/src/document/UBSortFilterProxyModel.cpp new file mode 100644 index 00000000..33cabf08 --- /dev/null +++ b/src/document/UBSortFilterProxyModel.cpp @@ -0,0 +1,25 @@ +#include "UBSortFilterProxyModel.h" +#include "UBDocumentController.h" + +UBSortFilterProxyModel::UBSortFilterProxyModel(): + QSortFilterProxyModel() +{ + setDynamicSortFilter(true); + setSortCaseSensitivity(Qt::CaseInsensitive); +} + +bool UBSortFilterProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + UBDocumentTreeModel *model = dynamic_cast(sourceModel()); + + if(model){ + //if it's a top level folder + //in other words : myDocuments, models and trash folder + if(model->isToplevel(left) || model->isToplevel(right)) + { + return false; + } + } + + return QSortFilterProxyModel::lessThan(left, right); +} diff --git a/src/document/UBSortFilterProxyModel.h b/src/document/UBSortFilterProxyModel.h new file mode 100644 index 00000000..97afba9f --- /dev/null +++ b/src/document/UBSortFilterProxyModel.h @@ -0,0 +1,15 @@ +#ifndef UBSORTFILTERPROXYMODEL_H +#define UBSORTFILTERPROXYMODEL_H + +#include +#include "core/UBPersistenceManager.h" + +class UBSortFilterProxyModel : public QSortFilterProxyModel +{ +public: + UBSortFilterProxyModel(); + + bool lessThan(const QModelIndex &left, const QModelIndex &right) const; +}; + +#endif // UBSORTFILTERPROXYMODEL_H diff --git a/src/document/document.pri b/src/document/document.pri index 8be38d75..f9a14dee 100644 --- a/src/document/document.pri +++ b/src/document/document.pri @@ -1,6 +1,11 @@ -HEADERS += src/document/UBDocumentController.h \ +HEADERS += \ src/document/UBDocumentContainer.h \ - src/document/UBDocumentProxy.h -SOURCES += src/document/UBDocumentController.cpp \ + src/document/UBDocumentController.h \ + src/document/UBDocumentProxy.h \ + src/document/UBSortFilterProxyModel.h +SOURCES += \ src/document/UBDocumentContainer.cpp \ - src/document/UBDocumentProxy.cpp + src/document/UBDocumentController.cpp \ + src/document/UBDocumentProxy.cpp \ + src/document/UBSortFilterProxyModel.cpp + diff --git a/src/gui/gui.pri b/src/gui/gui.pri index fe59b318..89b2d098 100644 --- a/src/gui/gui.pri +++ b/src/gui/gui.pri @@ -11,7 +11,7 @@ HEADERS += src/gui/UBThumbnailView.h \ src/gui/UBResources.h \ src/gui/UBMessageWindow.h \ src/gui/UBDocumentThumbnailWidget.h \ - src/gui/UBDocumentTreeWidget.h \ +# src/gui/UBDocumentTreeWidget.h \ src/gui/UBMousePressFilter.h \ src/gui/UBBlackoutWidget.h \ src/gui/UBMainWindow.h \ @@ -56,7 +56,7 @@ SOURCES += src/gui/UBThumbnailView.cpp \ src/gui/UBResources.cpp \ src/gui/UBMessageWindow.cpp \ src/gui/UBDocumentThumbnailWidget.cpp \ - src/gui/UBDocumentTreeWidget.cpp \ +# src/gui/UBDocumentTreeWidget.cpp \ src/gui/UBMousePressFilter.cpp \ src/gui/UBBlackoutWidget.cpp \ src/gui/UBMainWindow.cpp \