diff --git a/CMakeLists.txt b/CMakeLists.txt index 33a5599b2..5a1084c27 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,6 +60,7 @@ set(OPENBOARD_FORMS_DIR resources/forms src/web/simplebrowser) set(OPENBOARD_TS_DIR resources/i18n) set(OPENBOARD_FONT_DIR resources/customizations resources/fonts) set(OPENBOARD_ETC_DIR resources/etc) +set(OPENBOARD_BG_DIR resources/etc/background) set(OPENBOARD_LIBRARY_DIR resources/library) set(OPENBOARD_STARTUP_HINTS resources/startupHints) @@ -67,8 +68,8 @@ set(OPENBOARD_ETC_FILE ${OPENBOARD_ETC_DIR}/OpenBoard.config ${OPENBOARD_ETC_DIR}/OpenBoard.css ${OPENBOARD_ETC_DIR}/widgets.md5sum) set(OPENBOARD_TEMPLATE_FILE ${OPENBOARD_ETC_DIR}/asyncAPI.js - ${OPENBOARD_ETC_DIR}/intranet-podcast-metadata.template - ${OPENBOARD_ETC_DIR}/npapi-wrapper.config.xml) + ${OPENBOARD_ETC_DIR}/kwinKeepAbove.js + ${OPENBOARD_ETC_DIR}/intranet-podcast-metadata.template) set(OPENBOARD_ICON_FILE resources/images/ch.openboard.OpenBoard.svg) set(OPENBOARD_DESKTOP_FILE resources/linux/ch.openboard.OpenBoard.desktop) @@ -114,7 +115,7 @@ include(cmake/DependencyPoppler.cmake) include(cmake/DependencyZlib.cmake) include(cmake/DependencyQuaZip.cmake) include(cmake/DependencyFFmpeg.cmake) -include(cmake/DependencyX11.cmake) +include(cmake/DependencyPipewire.cmake) # add source path to include directories target_include_directories(${PROJECT_NAME} PRIVATE @@ -139,7 +140,6 @@ add_subdirectory(src/podcast) add_subdirectory(src/singleapplication) add_subdirectory(src/tools) add_subdirectory(src/web) -add_subdirectory(plugins/cffadaptor/src) add_subdirectory(resources/forms) # statically link singleapplication @@ -218,6 +218,7 @@ install(DIRECTORY ${OPENBOARD_FONT_DIR} DESTINATION ${OPENBOARD_INSTALL_ install(DIRECTORY ${OPENBOARD_LIBRARY_DIR} DESTINATION ${OPENBOARD_INSTALL_DATADIR}) install(DIRECTORY ${OPENBOARD_STARTUP_HINTS} DESTINATION ${OPENBOARD_INSTALL_DATADIR}) install(DIRECTORY ${PROJECT_BINARY_DIR}/i18n DESTINATION ${OPENBOARD_INSTALL_DATADIR}) +install(DIRECTORY ${OPENBOARD_BG_DIR} DESTINATION ${OPENBOARD_INSTALL_DATADIR}/template) install(FILES ${OPENBOARD_ETC_FILE} DESTINATION ${OPENBOARD_INSTALL_SYSCONFDIR}) install(FILES ${OPENBOARD_TEMPLATE_FILE} DESTINATION ${OPENBOARD_INSTALL_DATADIR}/template) install(FILES ${OPENBOARD_ICON_FILE} DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/apps) diff --git a/OpenBoard.pro b/OpenBoard.pro index 73e52d511..c86394d3c 100644 --- a/OpenBoard.pro +++ b/OpenBoard.pro @@ -66,10 +66,6 @@ DEPENDPATH += src/pdf-merger INCLUDEPATH += src/pdf-merger include(src/pdf-merger/pdfMerger.pri) -#plugins -include(plugins/plugins.pri) -INCLUDEPATH += plugins/cffadaptor/src - FORMS += resources/forms/mainWindow.ui \ resources/forms/preferences.ui \ resources/forms/brushProperties.ui \ @@ -459,7 +455,6 @@ linux-g++* { CONFIG += link_prl LIBS += -lcrypto #LIBS += -lprofiler - LIBS += -lX11 greaterThan(QT_MAJOR_VERSION, 5) { LIBS += -lquazip1-qt6 diff --git a/cmake/DependencyPipewire.cmake b/cmake/DependencyPipewire.cmake new file mode 100644 index 000000000..40485c28f --- /dev/null +++ b/cmake/DependencyPipewire.cmake @@ -0,0 +1,12 @@ +# Find Pipewire +# +# Pipewire only supports PkgConfig + +find_package(PkgConfig REQUIRED) +pkg_check_modules(Pipewire REQUIRED IMPORTED_TARGET libpipewire-0.3) + +if (Pipewire_FOUND) + target_link_libraries(${PROJECT_NAME} + PkgConfig::Pipewire + ) +endif() diff --git a/cmake/DependencyX11.cmake b/cmake/DependencyX11.cmake deleted file mode 100644 index f630160b7..000000000 --- a/cmake/DependencyX11.cmake +++ /dev/null @@ -1,18 +0,0 @@ -# Find FFmpeg - -find_package(X11 QUIET) - -if (X11_FOUND) - target_link_libraries(${PROJECT_NAME} - X11 - ) -else() - find_package(PkgConfig REQUIRED) - pkg_check_modules(X11 REQUIRED x11) - - if (X11_FOUND) - target_link_libraries(${PROJECT_NAME} - PkgConfig::X11 - ) - endif() -endif() diff --git a/plugins/cffadaptor/UBCFFAdaptor.pro b/plugins/cffadaptor/UBCFFAdaptor.pro deleted file mode 100644 index f8214d5de..000000000 --- a/plugins/cffadaptor/UBCFFAdaptor.pro +++ /dev/null @@ -1,69 +0,0 @@ -#------------------------------------------------- -# -# 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 deleted file mode 100644 index dfe0fb639..000000000 --- a/plugins/cffadaptor/postScript_mac.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/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 deleted file mode 100644 index 79d27706f..000000000 --- a/plugins/cffadaptor/resources/images/soundOn.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - diff --git a/plugins/cffadaptor/resources/resources.qrc b/plugins/cffadaptor/resources/resources.qrc deleted file mode 100644 index 0a4ec4710..000000000 --- a/plugins/cffadaptor/resources/resources.qrc +++ /dev/null @@ -1,5 +0,0 @@ - - - images/soundOn.svg - - diff --git a/plugins/cffadaptor/src/CMakeLists.txt b/plugins/cffadaptor/src/CMakeLists.txt deleted file mode 100644 index 6109d8fb2..000000000 --- a/plugins/cffadaptor/src/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -target_sources(openboard PRIVATE - UBCFFAdaptor.cpp -) - -target_include_directories(${PROJECT_NAME} PRIVATE - . -) diff --git a/plugins/cffadaptor/src/UBCFFAdaptor.cpp b/plugins/cffadaptor/src/UBCFFAdaptor.cpp deleted file mode 100644 index 8363e4963..000000000 --- a/plugins/cffadaptor/src/UBCFFAdaptor.cpp +++ /dev/null @@ -1,2081 +0,0 @@ -/* - * Copyright (C) 2015-2022 Département de l'Instruction Publique (DIP-SEM) - * - * Copyright (C) 2013 Open Education Foundation - * - * 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 - * 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, - * 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 OpenBoard. If not, see . - */ - - - - -#include "UBCFFAdaptor.h" - -#include -#include -#include -#include -#include -#include - -#include "UBCFFConstants.h" -#include "core/UB.h" - -//THIRD_PARTY_WARNINGS_DISABLE -#ifdef Q_OS_OSX - #include - #include - #include -#else - #include "quazip.h" - #include "quazipfile.h" - #include "quazipfileinfo.h" -#endif -//THIRD_PARTY_WARNINGS_ENABLE - - -UBCFFAdaptor::UBCFFAdaptor() -{} - -bool UBCFFAdaptor::convertUBZToIWB(const QString &from, const QString &to) -{ - qDebug() << "starting conversion from" << from << "to" << to; - - QString source = QString(); - if (QFileInfo(from).isDir() && QFile::exists(from)) { - qDebug() << "File specified is a directory, continuing conversion"; - source = from; - } else { - source = uncompressZip(from); - if (!source.isNull()) qDebug() << "File specified is a zip file. Uncompressed to tmp dir, continuing conversion"; - } - if (source.isNull()) { - qDebug() << "File specified is not a dir nor a zip file, stopping conversion"; - 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 converter 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 directory 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); - - for (const auto &value : std::as_const(pageList)) { - svgPagesetElement.appendChild(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. - for (const auto &value : std::as_const(svgElements)) { - svgElementPart.appendChild(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("/",UB::SplitBehavior::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("/",UB::SplitBehavior::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(".", UB::SplitBehavior::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(".", UB::SplitBehavior::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(",", UB::SplitBehavior::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); - QTransform sceneMatrix = item.sceneTransform(); - - 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(";", UB::SplitBehavior::SkipEmptyParts); - { - for (int i = 0; i < cffAttributes.count(); i++) - { - QString attr = cffAttributes.at(i).trimmed(); - QStringList AttrVal = attr.split(":", UB::SplitBehavior::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; - const auto keys = svgElements.keys(); - for (const auto key : keys) { - layers << key; - } - - std::sort(layers.begin(), layers.end()); - int layer = layers.at(0); - - for (const auto &value : std::as_const(svgElements)) { - svgElementPart.appendChild(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(";", UB::SplitBehavior::SkipEmptyParts); - for (int i = 0; i < commonAttributes.count(); i++) - { - QStringList AttrVal = commonAttributes.at(i).split(":", UB::SplitBehavior::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); - 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); - 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 deleted file mode 100644 index 5de50ec39..000000000 --- a/plugins/cffadaptor/src/UBCFFAdaptor.h +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright (C) 2015-2022 Département de l'Instruction Publique (DIP-SEM) - * - * Copyright (C) 2013 Open Education Foundation - * - * 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 - * 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, - * 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 OpenBoard. 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 deleted file mode 100644 index 3f060b96d..000000000 --- a/plugins/cffadaptor/src/UBCFFAdaptor_global.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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 deleted file mode 100644 index 276eb75a6..000000000 --- a/plugins/cffadaptor/src/UBCFFConstants.h +++ /dev/null @@ -1,397 +0,0 @@ -/* - * 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"); - -#endif // UBCFFCONSTANTS_H diff --git a/plugins/cffadaptor/src/UBGlobals.h b/plugins/cffadaptor/src/UBGlobals.h deleted file mode 100644 index d49c11cfa..000000000 --- a/plugins/cffadaptor/src/UBGlobals.h +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2015-2022 Département de l'Instruction Publique (DIP-SEM) - * - * Copyright (C) 2013 Open Education Foundation - * - * 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 - * 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, - * 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 OpenBoard. 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 deleted file mode 100644 index ddf3113a6..000000000 --- a/plugins/plugins.pri +++ /dev/null @@ -1,6 +0,0 @@ -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 6e817b3e6..fe55f1d62 100644 --- a/resources/OpenBoard.qrc +++ b/resources/OpenBoard.qrc @@ -2,7 +2,6 @@ images/OpenBoard.png images/bigOpenBoard.png - images/close.svg images/increase.svg images/decrease.svg images/resize.svg @@ -34,6 +33,8 @@ images/resizeRuler.svg images/resizeCompass.svg images/closeTool.svg + images/close.svg + images/close-no-bg.png images/hflipTool.svg images/vflipTool.svg images/resetTool.svg @@ -153,22 +154,6 @@ images/stylusPalette/captureAreaOn.png images/stylusPalette/snap.svg images/stylusPalette/snapOn.svg - images/backgroundPalette/background1.svg - images/backgroundPalette/background1On.svg - images/backgroundPalette/background2.svg - images/backgroundPalette/background2On.svg - images/backgroundPalette/background3.svg - images/backgroundPalette/background3On.svg - images/backgroundPalette/background4.svg - images/backgroundPalette/background4On.svg - images/backgroundPalette/background5.svg - images/backgroundPalette/background5On.svg - images/backgroundPalette/background6.svg - images/backgroundPalette/background6On.svg - images/backgroundPalette/background7.svg - images/backgroundPalette/background7On.svg - images/backgroundPalette/background8.svg - images/backgroundPalette/background8On.svg images/toolPalette/podcast.svg images/toolPalette/podcastOn.svg images/toolPalette/rulerTool.png @@ -212,8 +197,6 @@ images/addItemToCurrentPage.svg images/addItemToNewPage.svg images/addItemToLibrary.svg - style/treeview-branch-closed.png - style/treeview-branch-open.png webbrowser/closetab.png webbrowser/loading.gif webbrowser/notfound.html @@ -226,6 +209,8 @@ images/save.svg images/libpalette/social.png images/navig_arrow.png + darkTheme.qss + lightTheme.qss images/flags/ar.png images/flags/bg.png images/flags/ca.png @@ -333,18 +318,14 @@ images/libpalette/FlashCategory.svg images/libpalette/FlashIcon.svg images/toolbar/stylusTab.png - images/library_close.png - images/library_open.png - images/pages_close.png - images/pages_open.png images/cache_close.png images/cache_open.png images/cache_circle.png images/cache_square.png - images/down_arrow.png - images/up_arrow.png - images/left_arrow.png - images/right_arrow.png + images/down_arrow.svg + images/up_arrow.svg + images/left_arrow.svg + images/right_arrow.svg images/moveUp.svg images/moveDown.svg images/moveDownDisabled.svg @@ -353,8 +334,6 @@ images/moveUpDisabled.svg style.qss images/libpalette/WebSearchCategory.svg - images/download_close.png - images/download_open.png images/tab_mask.png images/duplicateDisabled.svg images/roundeRrectangle.svg @@ -400,5 +379,11 @@ images/removeFromFavorites.png images/toolbar/tip.png images/moveTool.svg + images/lightMode.svg + images/darkMode.svg + images/backgroundPalette/bgButtonTemplateDarkOff.svg + images/backgroundPalette/bgButtonTemplateDarkOn.svg + images/backgroundPalette/bgButtonTemplateLightOff.svg + images/backgroundPalette/bgButtonTemplateLightOn.svg diff --git a/resources/customizations/fonts/Marelle-LICENSE.txt b/resources/customizations/fonts/Marelle-LICENSE.txt new file mode 100644 index 000000000..4b8a16531 --- /dev/null +++ b/resources/customizations/fonts/Marelle-LICENSE.txt @@ -0,0 +1,92 @@ +Copyright 2026 Ministère de l’Éducation nationale, de l’Enseignement supérieur et de la Recherche, Laurent Bourcellier, Jonathan Fabreguettes and Rosalie Wagner, with Reserved Font Name "Marelle". + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION AND CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/resources/customizations/fonts/Marelle-Regular.ttf b/resources/customizations/fonts/Marelle-Regular.ttf new file mode 100644 index 000000000..04b4d3662 Binary files /dev/null and b/resources/customizations/fonts/Marelle-Regular.ttf differ diff --git a/resources/customizations/fonts/Marelle2-Regular.ttf b/resources/customizations/fonts/Marelle2-Regular.ttf new file mode 100644 index 000000000..af86177a5 Binary files /dev/null and b/resources/customizations/fonts/Marelle2-Regular.ttf differ diff --git a/resources/customizations/fonts/MarelleBaton-Regular.ttf b/resources/customizations/fonts/MarelleBaton-Regular.ttf new file mode 100644 index 000000000..070aacbeb Binary files /dev/null and b/resources/customizations/fonts/MarelleBaton-Regular.ttf differ diff --git a/resources/customizations/fonts/MarelleBaton2-Regular.ttf b/resources/customizations/fonts/MarelleBaton2-Regular.ttf new file mode 100644 index 000000000..14bf32638 Binary files /dev/null and b/resources/customizations/fonts/MarelleBaton2-Regular.ttf differ diff --git a/resources/customizations/fonts/MarelleLIGNES-Regular.otf b/resources/customizations/fonts/MarelleLIGNES-Regular.otf new file mode 100644 index 000000000..06389f12f Binary files /dev/null and b/resources/customizations/fonts/MarelleLIGNES-Regular.otf differ diff --git a/resources/customizations/fonts/MarelleLIGNES2-Regular.otf b/resources/customizations/fonts/MarelleLIGNES2-Regular.otf new file mode 100644 index 000000000..c9df0b401 Binary files /dev/null and b/resources/customizations/fonts/MarelleLIGNES2-Regular.otf differ diff --git a/resources/customizations/fonts/MarelleLIGNESBaton-Regular.otf b/resources/customizations/fonts/MarelleLIGNESBaton-Regular.otf new file mode 100644 index 000000000..2a66a58a9 Binary files /dev/null and b/resources/customizations/fonts/MarelleLIGNESBaton-Regular.otf differ diff --git a/resources/customizations/fonts/MarelleLIGNESBaton2-Regular.otf b/resources/customizations/fonts/MarelleLIGNESBaton2-Regular.otf new file mode 100644 index 000000000..99c9411e8 Binary files /dev/null and b/resources/customizations/fonts/MarelleLIGNESBaton2-Regular.otf differ diff --git a/resources/darkTheme.qss b/resources/darkTheme.qss new file mode 100644 index 000000000..944b7eeac --- /dev/null +++ b/resources/darkTheme.qss @@ -0,0 +1,1026 @@ +/* + * OpenBoard Dark Theme + */ + +/* Base colors for dark theme */ +QWidget { + background-color: #353535; + color: #ffffff; +} + +QMainWindow { + background-color: #2b2b2b; +} + +/* Dialogs and Windows */ +QDialog, QMessageBox { + background-color: #353535; + color: #ffffff; +} + +/* Text inputs */ +QLineEdit, QTextEdit, QPlainTextEdit { + background-color: #2a2a2a; + color: #ffffff; + border: 1px solid #555555; + border-radius: 3px; + padding: 3px; + selection-background-color: #2a82da; + selection-color: #ffffff; +} + +QLineEdit:focus, QTextEdit:focus, QPlainTextEdit:focus { + border: 1px solid #2a82da; + background-color: #2a2a2a; +} + +QLineEdit:disabled, QTextEdit:disabled, QPlainTextEdit:disabled { + background-color: #404040; + color: #808080; +} + +/* Labels */ +QLabel { + color: #ffffff; + background: transparent; +} + +QLabel:disabled { + color: #808080; +} + +/* Buttons */ +QPushButton { + background-color: #454545; + color: #ffffff; + border: 1px solid #555555; + border-radius: 4px; + padding: 6px 16px; + min-height: 20px; +} + +QPushButton:hover { + background-color: #505050; + border: 1px solid #6a6a6a; +} + +QPushButton:pressed { + background-color: #2a82da; + color: #ffffff; + border: 1px solid #2a82da; +} + +QPushButton:checked { + background-color: #2a82da; + color: #ffffff; +} + +QPushButton:disabled { + background-color: #404040; + color: #808080; + border: 1px solid #404040; +} + +QPushButton:default { + background-color: #2a82da; + color: #ffffff; + border: 2px solid #3d8fd1; +} + +/* Checkboxes and Radio buttons */ +QCheckBox, QRadioButton { + color: #ffffff; + spacing: 5px; +} + +QCheckBox:disabled, QRadioButton:disabled { + color: #808080; +} + +QCheckBox::indicator, QRadioButton::indicator { + width: 16px; + height: 16px; + border: 1px solid #555555; + border-radius: 3px; + background-color: #2a2a2a; +} + +QCheckBox::indicator:hover, QRadioButton::indicator:hover { + border: 1px solid #6a6a6a; +} + +QCheckBox::indicator:checked, QRadioButton::indicator:checked { + background-color: #2a82da; +} + +QRadioButton::indicator { + border-radius: 8px; +} + +/* Combo boxes */ +QComboBox { + background-color: #2a2a2a; + color: #ffffff; + border: 1px solid #555555; + border-radius: 3px; + padding: 4px 8px; + min-height: 20px; +} + +QComboBox:hover { + border: 1px solid #6a6a6a; +} + +QComboBox:focus { + border: 1px solid #2a82da; +} + +QComboBox::drop-down { + border: none; + width: 20px; +} + +QComboBox::down-arrow { + image: url(:/images/down_arrow.svg); + width: 12px; + height: 12px; +} + +QComboBox QAbstractItemView { + background-color: #2a2a2a; + color: #ffffff; + border: 1px solid #555555; + selection-background-color: #2a82da; + selection-color: #ffffff; + outline: none; +} + +QComboBox QAbstractItemView::item { + padding: 4px; + min-height: 20px; +} + +QComboBox QAbstractItemView::item:hover { + background-color: #404040; +} + +#UBPageNavigationWidget { + background-color: #353535; +} + +/* Spin boxes */ +QSpinBox, QDoubleSpinBox { + background-color: #2a2a2a; + color: #ffffff; + border: 1px solid #555555; + border-radius: 3px; + padding: 3px; +} + +QSpinBox:focus, QDoubleSpinBox:focus { + border: 1px solid #2a82da; +} + +QSpinBox::up-button, QDoubleSpinBox::up-button { + background-color: #404040; + border-left: 1px solid #555555; + width: 10px; + border-top-right-radius: 3px; +} + +QSpinBox::down-button, QDoubleSpinBox::down-button { + background-color: #404040; + border-left: 1px solid #555555; + width: 10px; + border-bottom-right-radius: 3px; +} + +QSpinBox::up-button:hover, QDoubleSpinBox::up-button:hover, +QSpinBox::down-button:hover, QDoubleSpinBox::down-button:hover { + background-color: #505050; +} + +QSpinBox::up-arrow, QDoubleSpinBox::up-arrow { + image: url(:/images/up_arrow.svg); + width: 10px; + height: 10px; +} + +QSpinBox::down-arrow, QDoubleSpinBox::down-arrow { + image: url(:/images/down_arrow.svg); + width: 10px; + height: 10px; +} + +/* Sliders */ +QSlider::groove:horizontal { + background-color: #404040; + height: 8px; + border-radius: 4px; + border: 1px solid #2a2a2a; +} + +QSlider::handle:horizontal { + background-color: #2a82da; + border: 1px solid #3d8fd1; + width: 16px; + margin: -5px 0; + border-radius: 8px; +} + +QSlider::handle:horizontal:hover { + background-color: #3d8fd1; +} + +QSlider::groove:vertical { + background-color: #404040; + width: 8px; + border-radius: 4px; + border: 1px solid #2a2a2a; +} + +QSlider::handle:vertical { + background-color: #2a82da; + border: 1px solid #3d8fd1; + height: 16px; + margin: 0 -5px; + border-radius: 8px; +} + +/* Scrollbars */ +QScrollBar:vertical { + background: #2b2b2b; + width: 14px; + margin: 18px 1px 18px 1px; +} + +QScrollBar::handle:vertical { + background: white; + min-height: 30px; + border-radius: 6px; + border: 1px solid #1e5fa0; +} + +QScrollBar::handle:vertical:hover { + background: #3d8fd1; +} + +QScrollBar::handle:vertical:pressed { + background: #1e5fa0; +} + +QScrollBar::add-line:vertical { + background: transparent; + border: none; + height: 0px; +} + +QScrollBar::sub-line:vertical { + background: transparent; + border: none; + height: 0px; +} + +QScrollBar:horizontal { + background: #2a82da; + height: 14px; + margin: 1px 18px 1px 18px; +} + +QScrollBar::handle:horizontal { + background: #2a82da; + min-width: 30px; + border-radius: 6px; + border: 1px solid white; +} + +QScrollBar::handle:horizontal:!hover { + background: #2a82da; + border: 1px solid #1e5fa0; +} + +QScrollBar::handle:horizontal:hover { + background: #3d8fd1; +} + +QScrollBar::handle:horizontal:pressed { + background: #1e5fa0; +} + +QScrollBar::add-line:horizontal { + background: transparent; + border: none; + width: 0px; +} + +QScrollBar::sub-line:horizontal { + background: transparent; + border: none; + width: 0px; +} + +QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical, +QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal { + background: none; +} + +/* Tab widgets */ +QTabWidget::pane { + border: 1px solid #555555; + background-color: #353535; + border-top: none; +} + +QTabBar::tab { + background-color: #2b2b2b; + color: #ffffff; + border: 1px solid #555555; + padding: 6px 12px; + margin-right: 2px; +} + +QTabBar::tab:selected { + background-color: #353535; + border-bottom: none; + color: #ffffff; +} + +QTabBar::tab:hover:!selected { + background-color: #404040; +} + +QTabBar::tab:top { + border-top-left-radius: 4px; + border-top-right-radius: 4px; +} + +/* Group boxes */ +QGroupBox { + color: #ffffff; + border: 1px solid #555555; + border-radius: 5px; + margin-top: 12px; + font-weight: bold; + padding-top: 10px; +} + +QGroupBox::title { + subcontrol-origin: margin; + subcontrol-position: top left; + padding: 0 5px; + color: #ffffff; + background-color: #353535; +} + +/* Tree and List views */ +QTreeView { + background-color: #2a2a2a; + color: #ffffff; + border: 1px solid #555555; + alternate-background-color: #323232; + selection-background-color: #2a82da; + selection-color: #ffffff; + show-decoration-selected: 0; + outline: none; +} + +QListView, QTableView { + background-color: #2a2a2a; + color: #ffffff; + border: 1px solid #555555; + alternate-background-color: #323232; + selection-background-color: #2a82da; + selection-color: #ffffff; + show-decoration-selected: 1; + outline: none; +} + +QTreeView::item, QListView::item, QTableView::item { + padding: 3px; +} + +QTreeView::item:hover, QListView::item:hover, QTableView::item:hover { + background-color: #404040; +} + +QTreeView::item:selected, QListView::item:selected, QTableView::item:selected { + background-color: #2a82da; + color: #ffffff; +} + +QTreeView::item:selected:active, +QListView::item:selected:active, +QTableView::item:selected:active { + background: #2a82da; + color: #ffffff; + border: none; +} + +QTreeView::branch:selected { + background: transparent; + color: inherit; + border: none; +} + +QTreeView QLineEdit, +QListView QLineEdit, +QTableView QLineEdit { + background-color: #2a2a2a; + color: #ffffff; + border: 1px solid #555555; + padding: 1px 6px; + selection-background-color: #2a82da; + selection-color: #ffffff; +} + +QTreeView QLineEdit:focus, +QListView QLineEdit:focus, +QTableView QLineEdit:focus { + background-color: #2a2a2a; + border: 1px solid #2a82da; +} + +QTreeView::branch { + background-color: #2a2a2a; +} + +QTreeView::branch:hover { + background-color: #404040; +} + +QHeaderView::section { + background-color: #404040; + color: #ffffff; + padding: 4px; + border: 1px solid #555555; + font-weight: bold; +} + +QHeaderView::section:hover { + background-color: #505050; +} + +/* Menus */ +QMenuBar { + background-color: #2b2b2b; + color: #ffffff; + border-bottom: 1px solid #555555; +} + +QMenuBar::item { + background-color: transparent; + padding: 4px 8px; +} + +QMenuBar::item:selected { + background-color: #2a82da; +} + +QMenuBar::item:pressed { + background-color: #2a82da; +} + +QMenu { + background-color: #2a2a2a; + color: #ffffff; + border: 1px solid #555555; + padding: 4px; +} + +QMenu::item { + padding: 6px 30px 6px 30px; + border-radius: 3px; +} + +QMenu::item:selected { + background-color: #2a82da; +} + +QMenu::separator { + height: 1px; + background-color: #555555; + margin: 4px 0px; +} + +/* Toolbars */ +QToolBar { + background-color: #2b2b2b; + border: 1px solid #555555; + spacing: 3px; + padding: 5px; +} + +QToolBar::separator { + background-color: #555555; + width: 1px; + margin: 4px; +} + +QToolButton { + background-color: transparent; + color: #ffffff; + border: 1px solid transparent; + border-radius: 4px; + padding: 5px; + margin: 2px; +} + +QToolButton:hover { + background-color: #404040; + border: 1px solid #555555; +} + +QToolButton:pressed { + background-color: #2a82da; + color: #ffffff; + border: 1px solid #2a82da; +} + +QToolButton:checked { + background-color: #2a82da; + color: #ffffff; +} + +QToolButton[popupMode="1"] { /* only for MenuButtonPopup */ + padding-right: 20px; +} + +QToolButton::menu-button { + border: 1px solid #555555; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + width: 16px; +} + +/* Progress bars */ +QProgressBar { + background-color: #2a2a2a; + border: 1px solid #555555; + border-radius: 4px; + text-align: center; + color: #ffffff; + height: 20px; +} + +QProgressBar::chunk { + background-color: #2a82da; + border-radius: 3px; +} + +/* Status bar */ +QStatusBar { + background-color: #2b2b2b; + color: #ffffff; + border-top: 1px solid #555555; +} + +QStatusBar::item { + border: none; +} + +/* Splitter */ +QSplitter::handle { + background-color: #555555; +} + +QSplitter::handle:horizontal { + width: 2px; +} + +QSplitter::handle:vertical { + height: 2px; +} + +QSplitter::handle:hover { + background-color: #2a82da; +} + +/* Dock widgets */ +QDockWidget { + color: #ffffff; + titlebar-close-icon: url(:/images/close.png); + titlebar-normal-icon: url(:/images/undock.png); +} + +QDockWidget::title { + background-color: #404040; + text-align: left; + padding-left: 5px; + padding-top: 3px; + padding-bottom: 3px; +} + +QDockWidget::close-button, QDockWidget::float-button { + background-color: #404040; + border: none; + padding: 0px; +} + +QDockWidget::close-button:hover, QDockWidget::float-button:hover { + background-color: #505050; +} + +/* Tooltips */ +QToolTip { + background-color: #404040; + color: palette(tooltip-text); + border: 1px solid #555555; + padding: 4px; + border-radius: 3px; +} + +/* Scroll areas */ +QScrollArea { + background-color: #353535; + border: none; +} + +#BackgroundPaletteScrollArea { + background-color: #3f3f3f; + border: none; +} +#BackgroundPaletteScrollArea QWidget{ + background-color: transparent; +} + +#BackgroundPalette { + background-color: #3f3f3f; +} +#AddItemPalette { + background-color: #3f3f3f; +} + +#DesktopPropertyPalette { + background-color: #3f3f3f; +} + +#UBMessageWindow { + color: #ffffff; +} + +#UBMessageWindow QLabel#UBMessageWindowLabel { + background: transparent; + border: none; + color: #f8fafc; + font-size: 15px; + font-weight: 600; + padding: 0px 2px 0px 0px; +} + +#UBMessageWindow UBSpinningWheel#UBMessageWindowSpinner { + background: transparent; + color: #5aa5e0; +} + +#BackgroundPalette QLabel { + color: palette(window-text); +} +#BackgroundPalette QToolButton#closeButton, +#AddItemPalette QToolButton#closeButton, +#UBStartupHintsPalette QToolButton#closeButton { + background: transparent; + border: none; + border-radius: 5px; + padding: 0px; + color: #ffffff; + font-size: 20px; + font-weight: bold; + min-width: 16px; + max-width: 16px; + min-height: 16px; + max-height: 16px; +} + +#UBStartupHintsPalette QCheckBox { + background: transparent; + color: #ffffff; +} +#BackgroundPalette QToolButton#closeButton:hover, +#AddItemPalette QToolButton#closeButton:hover, +#UBStartupHintsPalette QToolButton#closeButton:hover { + background: rgba(255, 255, 255, 0.10); +} +#BackgroundPalette QToolButton#closeButton:pressed, +#AddItemPalette QToolButton#closeButton:pressed, +#UBStartupHintsPalette QToolButton#closeButton:pressed { + background: rgba(255, 255, 255, 0.18); +} +#BackgroundPaletteLabel { + color: palette(window-text); +} + +/* Calendar widget */ +QCalendarWidget { + background-color: #2a2a2a; +} + +QCalendarWidget QToolButton { + color: #ffffff; + background-color: #404040; +} + +QCalendarWidget QMenu { + background-color: #2a2a2a; +} + +QCalendarWidget QSpinBox { + background-color: #404040; + color: #ffffff; +} + +QCalendarWidget QWidget#qt_calendar_navigationbar { + background-color: #2b2b2b; +} + +/* Size grip */ +QSizeGrip { + background-color: transparent; +} + +/* Override base button group colors for dark mode */ +/* Only override colors; the shared geometry lives in style.qss. */ +QToolButton#ubButtonGroupLeft, +QToolButton#ubButtonGroupCenter, +QToolButton#ubButtonGroupRight, +QToolButton#desktop-ubButtonGroupLeft, +QToolButton#desktop-ubButtonGroupCenter, +QToolButton#desktop-ubButtonGroupRight +{ + background: qlineargradient(x1: 0, y1: 0.49, x2: 0, y2: 0.5, stop: 0 #505050, stop: 1 #3a3a3a); + color: #ffffff; + border-radius: 0px; /* Reset the global QToolButton border-radius */ + border-left: none; + border-right: none; +} + +QToolButton#ubButtonGroupLeft:hover, +QToolButton#ubButtonGroupCenter:hover, +QToolButton#ubButtonGroupRight:hover, +QToolButton#desktop-ubButtonGroupLeft:hover, +QToolButton#desktop-ubButtonGroupCenter:hover, +QToolButton#desktop-ubButtonGroupRight:hover +{ + background: qlineargradient(x1: 0, y1: 0.49, x2: 0, y2: 0.5, stop: 0 #606060, stop: 1 #4a4a4a); +} + +QToolButton#ubButtonGroupLeft:checked, +QToolButton#ubButtonGroupCenter:checked, +QToolButton#ubButtonGroupRight:checked, +QToolButton#desktop-ubButtonGroupLeft:checked, +QToolButton#desktop-ubButtonGroupCenter:checked, +QToolButton#desktop-ubButtonGroupRight:checked +{ + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #2a82da, stop: 1 #1e5fa0); + color: #ffffff; + border: 1px solid #2a82da; +} + +QToolButton#ubButtonGroupRight[colorPaletteConfigButton="true"], +QToolButton#desktop-ubButtonGroupRight[colorPaletteConfigButton="true"] +{ + background: qlineargradient(x1: 0, y1: 0.49, x2: 0, y2: 0.5, stop: 0 #616161, stop: 1 #4a4a4a); + color: #ffffff; + border-top: none; + border-bottom: none; + border-left: none; + border-right: none; +} + +QToolButton#ubButtonGroupRight[colorPaletteConfigButton="true"]:hover, +QToolButton#desktop-ubButtonGroupRight[colorPaletteConfigButton="true"]:hover +{ + background: qlineargradient(x1: 0, y1: 0.49, x2: 0, y2: 0.5, stop: 0 #6c6c6c, stop: 1 #555555); + color: #ffffff; +} + +QToolButton#ubButtonGroupRight[colorPaletteConfigButton="true"]:pressed, +QToolButton#desktop-ubButtonGroupRight[colorPaletteConfigButton="true"]:pressed +{ + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #747474, stop: 1 #5e5e5e); + color: #ffffff; +} + +QDialog#colorPreferencesDialog { + background-color: #353535; +} + +QDialog#colorPreferencesDialog QWidget#colorPreferencesHeader { + background-color: #2b2b2b; + border: 1px solid #555555; + border-radius: 8px; +} + +QDialog#colorPreferencesDialog QLabel#colorPreferencesHeaderLabel { + color: #f8fafc; + font-size: 14px; + font-weight: 600; + background: transparent; +} + +QDialog#colorPreferencesDialog QSlider#colorPreferencesPaletteSizeSlider { + min-width: 190px; + background: transparent; +} + +QDialog#colorPreferencesDialog QSlider#colorPreferencesPaletteSizeSlider::groove:horizontal { + height: 6px; + background-color: #404040; + border: none; + border-radius: 3px; +} + +QDialog#colorPreferencesDialog QSlider#colorPreferencesPaletteSizeSlider::handle:horizontal { + width: 18px; + margin: -7px 0; + background-color: #2a82da; + border: 1px solid #3d8fd1; + border-radius: 9px; +} + +QDialog#colorPreferencesDialog QSlider#colorPreferencesPaletteSizeSlider::sub-page:horizontal { + background-color: #2a82da; + border-radius: 3px; +} + +QDialog#colorPreferencesDialog QLabel#colorPreferencesPaletteSizeValue { + min-width: 32px; + padding: 4px 8px; + background: #2a2a2a; + color: #f8fafc; + border: 1px solid #555555; + border-radius: 4px; + font-size: 14px; + font-weight: 600; +} + +QDialog#colorPreferencesDialog QWidget#colorPreferencesHint { + background: #4b4324; + border: 1px solid #7b6b33; + border-radius: 6px; +} + +QDialog#colorPreferencesDialog QLabel#colorPreferencesHintIcon { + background: transparent; + min-width: 20px; +} + +QDialog#colorPreferencesDialog QLabel#colorPreferencesHintText { + background: transparent; + color: #f1e3a8; + font-size: 12px; + line-height: 1.3em; +} + +QDialog#colorPreferencesDialog QTabWidget#colorPreferencesTabWidget::pane { + border: none; + background: transparent; + top: -1px; +} + +QDialog#colorPreferencesDialog QTabBar::tab, +QDialog#colorPreferencesDialog QTabBar::tab:top { + background: #2b2b2b; + color: #b7c3d4; + border: 1px solid #555555; + border-radius: 4px; + padding: 8px 18px; + margin-right: 8px; + font-weight: 600; +} + +QDialog#colorPreferencesDialog QTabBar::tab:selected { + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #2a82da, stop: 1 #1e5fa0); + color: #ffffff; + border: 1px solid #2a82da; +} + +QDialog#colorPreferencesDialog QTabBar::tab:hover:!selected { + background: #404040; + color: #ffffff; +} + +QDialog#colorPreferencesDialog QWidget#colorPreferencesSection { + background: #2b2b2b; + border: 1px solid #555555; + border-radius: 8px; +} + +QDialog#colorPreferencesDialog QLabel#colorPreferencesSectionTitle { + background: transparent; + color: #f8fafc; + font-size: 13px; + font-weight: 600; +} + +QDialog#colorPreferencesDialog QFrame#colorPreviewFrame { + border-radius: 6px; +} + +QDialog#colorPreferencesDialog QFrame#colorPreviewFrame[previewMode="light"] { + background: #ffffff; + border: 1px solid #d9e0e8; +} + +QDialog#colorPreferencesDialog QFrame#colorPreviewFrame[previewMode="dark"] { + background: #1f1f1f; + border: 1px solid #555555; +} + +QDialog#colorPreferencesDialog QLabel#colorShortcutLabel { + background: transparent; + color: #94a3b8; + font-size: 11px; + font-weight: 700; +} + +QDialog#colorPreferencesDialog QFrame#colorPreviewFrame[previewMode="dark"] QLabel#colorShortcutLabel { + color: #e2e8f0; +} + +QDialog#colorPreferencesDialog QFrame#colorPreviewFrame QToolButton#ubButtonGroupLeft, +QDialog#colorPreferencesDialog QFrame#colorPreviewFrame QToolButton#ubButtonGroupCenter, +QDialog#colorPreferencesDialog QFrame#colorPreviewFrame QToolButton#ubButtonGroupRight { + background: qlineargradient(x1: 0, y1: 0.49, x2: 0, y2: 0.5, stop: 0 #fdfefe, stop: 1 #e7edf4); + border-top: 1px solid #d9e0e8; + border-bottom: 1px solid #d9e0e8; + border-left: none; + border-right: none; + border-radius: 0px; +} + +QDialog#colorPreferencesDialog QFrame#colorPreviewFrame[previewMode="light"] QToolButton#ubButtonGroupLeft { + border-left: 1px solid #d9e0e8; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; +} + +QDialog#colorPreferencesDialog QFrame#colorPreviewFrame[previewMode="light"] QToolButton#ubButtonGroupRight { + border-right: 1px solid #d9e0e8; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} + +QDialog#colorPreferencesDialog QFrame#colorPreviewFrame[previewMode="dark"] QToolButton#ubButtonGroupLeft, +QDialog#colorPreferencesDialog QFrame#colorPreviewFrame[previewMode="dark"] QToolButton#ubButtonGroupCenter, +QDialog#colorPreferencesDialog QFrame#colorPreviewFrame[previewMode="dark"] QToolButton#ubButtonGroupRight { + background: qlineargradient(x1: 0, y1: 0.49, x2: 0, y2: 0.5, stop: 0 #505050, stop: 1 #3a3a3a); + border-top: 1px solid #555555; + border-bottom: 1px solid #555555; +} + +QDialog#colorPreferencesDialog QFrame#colorPreviewFrame[previewMode="dark"] QToolButton#ubButtonGroupLeft { + border-left: 1px solid #555555; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; +} + +QDialog#colorPreferencesDialog QFrame#colorPreviewFrame[previewMode="dark"] QToolButton#ubButtonGroupRight { + border-right: 1px solid #555555; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} + +QDialog#colorPreferencesDialog QFrame#colorPreviewFrame QToolButton:hover { + background: qlineargradient(x1: 0, y1: 0.49, x2: 0, y2: 0.5, stop: 0 #f4f9ff, stop: 1 #dcebf9); + border-top-color: #c6d9ec; + border-bottom-color: #c6d9ec; +} + +QDialog#colorPreferencesDialog QFrame#colorPreviewFrame[previewMode="dark"] QToolButton:hover { + background: qlineargradient(x1: 0, y1: 0.49, x2: 0, y2: 0.5, stop: 0 #606060, stop: 1 #4a4a4a); + border-top-color: #6a6a6a; + border-bottom-color: #6a6a6a; +} + +QDialog#colorPreferencesDialog QLabel#colorPreferencesOpacityValue { + color: #ffffff; + font-size: 13px; + font-weight: 700; + background: transparent; +} + +QDialog#colorPreferencesDialog QPushButton#colorPreferencesResetButton { + padding: 7px 14px; + background: #454545; + color: #f8fafc; + border: 1px solid #555555; + border-radius: 4px; + font-weight: 600; +} + +QDialog#colorPreferencesDialog QPushButton#colorPreferencesResetButton:hover { + background: #505050; + border: 1px solid #6a6a6a; +} + +QDialog#colorPreferencesDialog QDialogButtonBox#colorPreferencesButtonBox QPushButton { + min-width: 92px; + padding: 8px 18px; + border-radius: 4px; + font-weight: 600; +} + +QWidget#UBFeaturesActionBar QToolButton +{ + padding: 0px; +} diff --git a/resources/etc/OpenBoard.config b/resources/etc/OpenBoard.config index ce77e232e..ffc35de9b 100644 --- a/resources/etc/OpenBoard.config +++ b/resources/etc/OpenBoard.config @@ -104,7 +104,6 @@ PublishToIntranet=false PublishingUrl= [Library] -AnimationsDirectory=./library/animations ApplicationsDirectory=./library/applications AudiosDirectory=./library/audios ImageDirectory=./library/pictures diff --git a/resources/etc/OpenBoard.css b/resources/etc/OpenBoard.css index 92672b44e..b4ec1d09d 100644 --- a/resources/etc/OpenBoard.css +++ b/resources/etc/OpenBoard.css @@ -1,77 +1,89 @@ QWidget:enabled { - color: #3F3F3F; + color: palette(text); } QWidget:disabled { - color: #777777; + color: palette(disabled, text); } QComboBox, QPushButton, QComboBox QAbstractItemView { - background: #dddddd; + background: palette(button); +} + +QPushButton:focus, +QPushButton:pressed, +QPushButton:checked, +QPushButton:default, +QToolButton:focus, +QToolButton:pressed, +QToolButton:checked +{ + color: palette(highlighted-text); } QTextEdit, QLineEdit, QComboBox#DockPaletteWidgetComboBox QAbstractItemView { - selection-background-color: lightgreen; - selection-color: black; + selection-background-color: palette(highlight); + selection-color: palette(highlighted-text); } -QProgressBar:horizontal { - border: 1px solid gray; +QProgressBar:horizontal +{ + border: 1px solid palette(mid); border-radius: 3px; - background: white; + background: palette(base); padding: 1px; } -QProgressBar::chunk:horizontal { - /*background: qlineargradient(x1: 0, y1: 0.5, x2: 1, y2: 0.5, stop: 0 green, stop: 1 lightgreen);*/ - background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #B1B1B1, stop:1 #c4c4c4); +QProgressBar::chunk:horizontal +{ + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 palette(mid), stop:1 palette(button)); } QMainWindow { - background-color: #F1F1F1; + background-color: palette(window); } QDialog { - background-color: #dddddd; + background-color: palette(button); } QMenu { border: none; font-size: 12px; - background-color: #dddddd; + background-color: palette(button); } QMenu::item { - background-color: #b3b3b3; + background-color: palette(midlight); } QMenu::item:selected { - background-color: #9f9f9f; + background-color: palette(mid); } QMenu::separator { - background-color: #b3b3b3; - border: 1px dotted #888888; + background-color: palette(midlight); + border: 1px dotted palette(dark); } QToolBar { spacing: 0px; - background-color: #b3b3b3; + background-color: palette(midlight); border: none; - border-bottom: 1px solid #888888; + border-bottom: 1px solid palette(dark); } QToolBar::handle @@ -82,124 +94,56 @@ QToolBar::handle QToolBar::separator { - border: 1px dotted #888888; border-left: none; margin-left: 5px; margin-right: 5px; width: 1px; -} - -QToolBar QToolButton -{ - - margin: 4px; - margin-left: 0px; - margin-right: 0px; - padding: 0px; - border: none; - height: 58px; -} - -QToolBar QToolButton:pressed -{ - margin: 4px; - margin-left: 0px; - margin-right: 0px; - padding: 0px; - border: none; - height: 58px; - - background: qradialgradient(cx:0.5, cy:0.5, radius: 0.5, - fx:0.5, fy:0.5, stop: 0 #d3d3d3, stop: 1 #b3b3b3); + border: 1px dotted palette(dark); border-left: none; margin-left: 5px; margin-right: 5px; width: 1px; } QDialog QToolButton:pressed { - background: qradialgradient(cx:0.5, cy:0.5, radius: 0.5, - fx:0.5, fy:0.5, stop: 0 #ffffff, stop: 1 #dddddd); + background: qradialgradient(cx:0.5, cy:0.5, radius: 0.5, fx:0.5, fy:0.5, stop: 0 #ffffff, stop: 1 #dddddd); } QToolButton#ubButtonGroupLeft, QToolButton#desktop-ubButtonGroupLeft { - background: qlineargradient(x1: 0, y1: 0.49, x2: 0, y2: 0.5, stop: 0 #d3d3d3, stop: 1 #c4c4c4); margin-top: 1px; margin-right: 0px; padding: 5px; - - border: 1px solid #444444; - border-right: none; - border-top-left-radius : 3px; - border-bottom-left-radius : 3px; height: 14px; } QToolButton#ubButtonGroupCenter, QToolButton#desktop-ubButtonGroupCenter { - background: qlineargradient(x1: 0, y1: 0.49, x2: 0, y2: 0.5, stop: 0 #d3d3d3, stop: 1 #c4c4c4); margin-top: 1px; margin-right: 0px; margin-left: 0px; padding: 5px; - - border: 1px solid #444444; - border-right: none; - border-left: none; height: 14px; } QToolButton#ubButtonGroupRight, QToolButton#desktop-ubButtonGroupRight { - background: qlineargradient(x1: 0, y1: 0.49, x2: 0, y2: 0.5, stop: 0 #d3d3d3, stop: 1 #c4c4c4); margin-top: 1px; margin-left: 0px; padding: 5px; - - border: 1px solid #444444; - border-left: none; - border-top-right-radius : 3px; - border-bottom-right-radius : 3px; height: 14px; } -QToolButton#desktop-ubButtonGroupLeft -QToolButton#desktop-ubButtonGroupCenter, -QToolButton#desktop-ubButtonGroupRight -{ - background: qlineargradient(x1: 0, y1: 0.49, x2: 0, y2: 0.5, stop: 0 #c3c3c3, stop: 1 #ffffff); -} - -QToolButton#desktop-ubButtonGroupLeft:checked, -QToolButton#desktop-ubButtonGroupCenter:checked, -QToolButton#desktop-ubButtonGroupRight:checked -{ - border:1px solid white; - background: qlineargradient(x1: 0, y1: 0.49, x2: 0, y2: 0.5, stop: 0 #c3c3c3, stop: 1 #ffffff); -} - - -QToolButton#ubButtonGroupLeft:checked, -QToolButton#ubButtonGroupCenter:checked, -QToolButton#ubButtonGroupRight:checked -{ - background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #c3c3c3, stop: 1 #b4b4b4); -} QToolButton#ubButtonGroupLeft:checked { - border-right: 1px solid #444444; padding-right: 4px; } QToolButton#ubButtonGroupCenter:checked { - border-left: 1px solid #444444; - border-right: 1px solid #444444; padding-right: 4px; padding-left: 4px; } QToolButton#ubButtonGroupRight:checked { - border-left: 1px solid #444444; padding-left: 4px; } @@ -209,12 +153,6 @@ QToolButton#ubButtonMenu padding-right: 12px; } -QToolButton#ubButtonMenu:pressed, -QToolButton#ubButtonMenu:checked -{ - background-color: #b3b3b3; -} - QToolButton#ubActionPaletteButton { margin: 0px; @@ -237,28 +175,22 @@ QFrame#videoTopLeftFrame, QFrame#toolTopLeftFrame { border: none; - border-right: 1px solid #888888; + border-right: 1px solid palette(dark); } QWidget#topRightFrame { - background-color: #d3d3d3; + background-color: palette(light); } QFrame#toolFrame { - border-top: 1px solid #888888; + border-top: 1px solid palette(dark); } QTreeView { - background-color: rgb(209, 215, 226); -} - -QTreeView::item:selected:active -{ - background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #AAB7C4, stop: 1 #7e8eA0); - border: none; + background-color: palette(base); } QTreeView::item @@ -267,47 +199,18 @@ QTreeView::item padding-bottom: 1.5 px; } -QTreeView::item:selected, -QTreeView::branch:selected -{ - background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #AAB7C4, stop: 1 #7e8eA0); - border: none; -} - -QTreeView::branch:has-siblings:!adjoins-item, -QTreeView::branch:!has-children:!has-siblings:adjoins-item, -QTreeView::branch:has-siblings:adjoins-item -{ - border-image: none; - image: none; -} - -QTreeView::branch:has-children:!has-siblings:closed, -QTreeView::branch:closed:has-children:has-siblings -{ - border-image: none; - image: url(:/style/treeview-branch-closed.png); -} - -QTreeView::branch:open:has-children:!has-siblings, -QTreeView::branch:open:has-children:has-siblings -{ - border-image: none; - image: url(:/style/treeview-branch-open.png); -} - QSlider::groove:horizontal { - border: 1px solid #999999; + border: 1px solid palette(mid); height: 2px; - background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #B1B1B1, stop:1 #c4c4c4); + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 palette(mid), stop:1 palette(button)); margin: 2px 0; } QSlider::handle:horizontal { - background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #aaaaaa, stop: 1 #b8b8b8); - border: 1px solid #5c5c5c; + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 palette(midlight), stop: 1 palette(button)); + border: 1px solid palette(dark); width: 18px; margin: -2px 0; @@ -316,14 +219,14 @@ QSlider::handle:horizontal QTabWidget::pane { - border: 1px solid #888888; + border: 1px solid palette(dark); margin-top: -2px; } QTabWidget::pane#libraryTabWidget { border: none; - border-top: 1px solid #888888; + border-top: 1px solid palette(dark); } QTabWidget::tab-bar @@ -333,15 +236,15 @@ QTabWidget::tab-bar QTabWidget::tab-bar#ubWebBrowserTabWidget { - background: #dddddd; + background: palette(button); alignment: left; } QTabBar::tab { - background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #d3d3d3, stop: 1 #c4c4c4); + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 palette(light), stop: 1 palette(button)); - border: 1px solid #888888; + border: 1px solid palette(dark); border-bottom: none; @@ -359,51 +262,51 @@ QTabBar::tab QTabBar::tab:first { - border-left: 1px solid #888888; + border-left: 1px solid palette(dark); } QTabBar::tab:last { - border-right: 1px solid #888888; + border-right: 1px solid palette(dark); } QTabBar::tab:selected { - border-right: 1px solid #888888; - border-left: 1px solid #888888; + border-right: 1px solid palette(dark); + border-left: 1px solid palette(dark); border-bottom: none; } QTabBar::tab:first:selected { - border-right: 1px solid #888888; + border-right: 1px solid palette(dark); } QTabBar::tab:last:selected { - border-left: 1px solid #888888; + border-left: 1px solid palette(dark); } QTabBar::tab:selected { - background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #aaaaaa, stop: 1 #b8b8b8); + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 palette(midlight), stop: 1 palette(button)); } QTabBar::tab#ubWebBrowserTabBar { - background: #dddddd; + background: palette(button); min-width: 150px; } QTabBar::tab:selected#ubWebBrowserTabBar { - background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #b3b3b3, stop: 1 #dddddd); + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 palette(midlight), stop: 1 palette(button)); } QLineEdit#ubWebBrowserLineEdit { - border: 1px solid #888888; + border: 1px solid palette(dark); border-radius: 3px; padding: 2 2px; - background: white; + background: palette(base); } diff --git a/resources/etc/background/01_blank.xml b/resources/etc/background/01_blank.xml new file mode 100644 index 000000000..d76dc8a1c --- /dev/null +++ b/resources/etc/background/01_blank.xml @@ -0,0 +1,26 @@ + + 84836d5a-1846-4c91-9ff9-f2c4d118364d + Empty + Leer + + + #00000000 + #00000000 + + + 0 + 10 + + 0 + 1 + + + + 90 + 10 + + 0 + 1 + + + diff --git a/resources/etc/background/02_ruled.xml b/resources/etc/background/02_ruled.xml new file mode 100644 index 000000000..6abde93f0 --- /dev/null +++ b/resources/etc/background/02_ruled.xml @@ -0,0 +1,14 @@ + + 0de33238-c30e-4396-8ec4-2bfc53304389 + Ruled + Liniert + + + 0 + 10 + + 0 + 1 + + + diff --git a/resources/etc/background/03_crossed.xml b/resources/etc/background/03_crossed.xml new file mode 100644 index 000000000..0e8cc232d --- /dev/null +++ b/resources/etc/background/03_crossed.xml @@ -0,0 +1,22 @@ + + 7493c8bb-6d98-4be6-966c-fbddf9eff529 + Crossed + Kariert + + + 0 + 10 + + 0 + 1 + + + + 90 + 10 + + 0 + 1 + + + diff --git a/resources/etc/background/04_crossed5.xml b/resources/etc/background/04_crossed5.xml new file mode 100644 index 000000000..5930b73e6 --- /dev/null +++ b/resources/etc/background/04_crossed5.xml @@ -0,0 +1,38 @@ + + 8ed2037b-ab38-4d4f-9b52-c4e36b1c8102 + Crossed 5mm + Kariert 5mm + + + 0 + 10 + + 0 + 1 + + + 5 + 1 + + 0.5 + 0.5 + + + + + 90 + 10 + + 0 + 1 + + + 5 + 1 + + 0.5 + 0.5 + + + + diff --git a/resources/etc/background/05_seyes.xml b/resources/etc/background/05_seyes.xml new file mode 100644 index 000000000..8109fcd7d --- /dev/null +++ b/resources/etc/background/05_seyes.xml @@ -0,0 +1,69 @@ + + 38b45f18-f6a5-4336-ae91-bedba3a2442c + Seyes + + + 0 + 20 + + 0 + 2 + + #8e7cc3 + #8e7cc3 + + + + 5 + 2 + + #996fa8dc + #996fa8dc + + + + 10 + 2 + + #996fa8dc + #996fa8dc + + + + 15 + 2 + + #996fa8dc + #996fa8dc + + + + + 90 + 20 + topleft + + 20 + 2 + + #8e7cc3 + #8e7cc3 + + + + -20 + + + + 90 + topleft + + 20 + 2 + + #ff0000 + #ff0000 + + + + diff --git a/resources/etc/background/06_staveBorder.xml b/resources/etc/background/06_staveBorder.xml new file mode 100644 index 000000000..33cd8df21 --- /dev/null +++ b/resources/etc/background/06_staveBorder.xml @@ -0,0 +1,42 @@ + + 1da51382-220e-40d2-804a-36ca2ab3fbd8 + Stave lines with border + Notenlinien mit Rand + Lignes de notes avec bordure + + 0 + 50 + + 0 + 1 + + + 5 + 1 + + + 10 + 1 + + + 15 + 1 + + + 20 + 1 + + + -5 + -5 + + 0 + 1 + + + + 0 + 0 + + + diff --git a/resources/etc/background/background.xsd b/resources/etc/background/background.xsd new file mode 100644 index 000000000..303b74265 --- /dev/null +++ b/resources/etc/background/background.xsd @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/etc/background/crossed1.xml b/resources/etc/background/crossed1.xml new file mode 100644 index 000000000..8da7bf0f9 --- /dev/null +++ b/resources/etc/background/crossed1.xml @@ -0,0 +1,166 @@ + + 5daad8dd-1c41-4c37-9c14-2ab073c7249c + Crossed 1mm + Millimeterpapier + + + 0 + 10 + + 0 + 1 + + + 1 + 1 + + 0.4 + 0.4 + + + + 2 + 1 + + 0.4 + 0.4 + + + + 3 + 1 + + 0.4 + 0.4 + + + + 4 + 1 + + 0.4 + 0.4 + + + + 5 + 1 + + 0.6 + 0.6 + + + + 6 + 1 + + 0.4 + 0.4 + + + + 7 + 1 + + 0.4 + 0.4 + + + + 8 + 1 + + 0.4 + 0.4 + + + + 9 + 1 + + 0.4 + 0.4 + + + + + 90 + 10 + + 0 + 1 + + + 1 + 1 + + 0.4 + 0.4 + + + + 2 + 1 + + 0.4 + 0.4 + + + + 3 + 1 + + 0.4 + 0.4 + + + + 4 + 1 + + 0.4 + 0.4 + + + + 5 + 1 + + 0.6 + 0.6 + + + + 6 + 1 + + 0.4 + 0.4 + + + + 7 + 1 + + 0.4 + 0.4 + + + + 8 + 1 + + 0.4 + 0.4 + + + + 9 + 1 + + 0.4 + 0.4 + + + + diff --git a/resources/etc/background/crossed5Border.xml b/resources/etc/background/crossed5Border.xml new file mode 100644 index 000000000..b1e009826 --- /dev/null +++ b/resources/etc/background/crossed5Border.xml @@ -0,0 +1,52 @@ + + 9057d897-bbfe-4764-9867-1c75ad4da2e3 + Crossed 5mm with border + Kariert 5mm mit Rand + + + 0 + 10 + + 0 + 1 + + + 5 + 1 + + 0.5 + 0.5 + + + + -30 + + + + 90 + 10 + + 0 + 1 + + + 5 + 1 + + 0.5 + 0.5 + + + + -30 + + + + 90 + topleft + + 30 + 1 + + + diff --git a/resources/etc/background/de-primary1.xml b/resources/etc/background/de-primary1.xml new file mode 100644 index 000000000..d3f9d1c04 --- /dev/null +++ b/resources/etc/background/de-primary1.xml @@ -0,0 +1,33 @@ + + ca8931ff-19b6-4217-88f9-9f64ec366bfa + German elementary school, first grade + Grundschule, erste Klasse + + 0 + 20 + + 0 + 1 + + + 5 + 1 + + + 10 + 1 + + + 15 + 1 + + + -5 + -5 + + 0 + 1 + + + + diff --git a/resources/etc/background/de-primary2.xml b/resources/etc/background/de-primary2.xml new file mode 100644 index 000000000..be103166b --- /dev/null +++ b/resources/etc/background/de-primary2.xml @@ -0,0 +1,33 @@ + + eb53b553-0e73-473c-b4c3-bcd328e27d76 + German elementary school, second grade + Grundschule, zweite Klasse + + 0 + 16 + + 0 + 1 + + + 4 + 1 + + + 8 + 1 + + + 12 + 1 + + + -5 + -5 + + 0 + 1 + + + + diff --git a/resources/etc/background/de-primary3.xml b/resources/etc/background/de-primary3.xml new file mode 100644 index 000000000..fd5622347 --- /dev/null +++ b/resources/etc/background/de-primary3.xml @@ -0,0 +1,25 @@ + + ff386ccf-e7e1-4838-b7fa-255c28208209 + German elementary school, third grade + Grundschule, dritte Klasse + + 0 + 14 + + 0 + 1 + + + 3.5 + 1 + + + -5 + -5 + + 0 + 1 + + + + diff --git a/resources/etc/background/isometric.xml b/resources/etc/background/isometric.xml new file mode 100644 index 000000000..6bac2b023 --- /dev/null +++ b/resources/etc/background/isometric.xml @@ -0,0 +1,30 @@ + + 07187386-b012-492c-b0a8-50faa18c28a0 + Isometric + Isometrisches Gitter + + + 30 + 8.66025403784 + + 0 + 1 + + + + 90 + 8.66025403784 + + 0 + 1 + + + + 150 + 8.66025403784 + + 0 + 1 + + + diff --git a/resources/etc/background/ruled5.xml b/resources/etc/background/ruled5.xml new file mode 100644 index 000000000..a4c15e259 --- /dev/null +++ b/resources/etc/background/ruled5.xml @@ -0,0 +1,22 @@ + + e986ad6c-9a3e-4a63-a97a-1a19c7fa90cc + Ruled 5mm + Liniert 5mm + + + 0 + 10 + + 0 + 1 + + + 5 + 1 + + 0.5 + 0.5 + + + + diff --git a/resources/etc/background/ruledBorder.xml b/resources/etc/background/ruledBorder.xml new file mode 100644 index 000000000..c1021972a --- /dev/null +++ b/resources/etc/background/ruledBorder.xml @@ -0,0 +1,25 @@ + + c155edf1-bb46-4f4c-a665-a5800eeed465 + Ruled with border + Liniert mit Rand + + + 0 + 10 + + 0 + 1 + + + -30 + + + + 90 + topleft + + 30 + 1 + + + diff --git a/resources/etc/background/stave.xml b/resources/etc/background/stave.xml new file mode 100644 index 000000000..2688462c3 --- /dev/null +++ b/resources/etc/background/stave.xml @@ -0,0 +1,30 @@ + + 1dd2b623-950c-4d4d-af74-98d576636099 + Stave lines + Notenlinien + Lignes de notes + + 0 + 50 + + 0 + 1 + + + 5 + 1 + + + 10 + 1 + + + 15 + 1 + + + 20 + 1 + + + diff --git a/resources/etc/kwinKeepAbove.js b/resources/etc/kwinKeepAbove.js new file mode 100644 index 000000000..90c27ab36 --- /dev/null +++ b/resources/etc/kwinKeepAbove.js @@ -0,0 +1,23 @@ +/* + * Set keepAbove for OpenBoard DesktopView + * + * Works with KDE 5 and KDE 6 + */ + +function keepOnTop() { + // KDE 6 property + var allClients = workspace.stackingOrder; + + if (!allClients) { + // KDE 5 function + allClients = workspace.clientList(); + } + for (var i = 0; i < allClients.length; ++i) { + var client = allClients[i]; + if (client.resourceClass == "ch.openboard.OpenBoard" && client.caption == "DesktopView") { + client.keepAbove=true; + } + } +} + +keepOnTop(); diff --git a/resources/etc/npapi-wrapper.application.x-shockwave-flash.swf.htm b/resources/etc/npapi-wrapper.application.x-shockwave-flash.swf.htm deleted file mode 100644 index 909d19d1c..000000000 --- a/resources/etc/npapi-wrapper.application.x-shockwave-flash.swf.htm +++ /dev/null @@ -1,29 +0,0 @@ - - - - Flash Widget - - - - - -

- - - - - -
- - diff --git a/resources/etc/npapi-wrapper.config.xml b/resources/etc/npapi-wrapper.config.xml deleted file mode 100644 index dc3d72114..000000000 --- a/resources/etc/npapi-wrapper.config.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - {in.name} - - - diff --git a/resources/forms/brushProperties.ui b/resources/forms/brushProperties.ui index 5c4de12cf..b26db6a88 100644 --- a/resources/forms/brushProperties.ui +++ b/resources/forms/brushProperties.ui @@ -20,251 +20,7 @@ QFrame::Plain - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - Pen is Pressure Sensitive - - - - - - - Qt::Horizontal - - - - 198 - 20 - - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - - - Qt::Horizontal - - - - 154 - 20 - - - - - - - - Opacity - - - - - - - 20 - - - 100 - - - 50 - - - Qt::Horizontal - - - QSlider::TicksAbove - - - 20 - - - - - - - Qt::Horizontal - - - - 156 - 20 - - - - - - - - - - - Qt::LeftToRight - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - - - On Light Background - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 32 - 32 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - - - On Dark Background - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 32 - 32 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - - - - + @@ -458,20 +214,57 @@ - - - - Qt::Vertical + + + + QFrame::NoFrame - - - 20 - 40 - + + QFrame::Raised - + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Pen is Pressure Sensitive + + + + + + + Qt::Horizontal + + + + 198 + 20 + + + + + + - + QFrame::NoFrame @@ -518,7 +311,7 @@ - + Qt::Horizontal @@ -534,12 +327,6 @@ - - UBColorPicker - QFrame -
gui/UBColorPicker.h
- 1 -
UBCircleFrame QFrame diff --git a/resources/forms/documents.ui b/resources/forms/documents.ui index 921ca08d5..694a050be 100644 --- a/resources/forms/documents.ui +++ b/resources/forms/documents.ui @@ -102,22 +102,6 @@
- - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 10 - 20 - - - - @@ -146,17 +130,14 @@ - - - Qt::Horizontal + + + filter the documents - - - 40 - 20 - + + true - +
diff --git a/resources/forms/mainWindow.ui b/resources/forms/mainWindow.ui index e7c76e60d..09673691f 100644 --- a/resources/forms/mainWindow.ui +++ b/resources/forms/mainWindow.ui @@ -555,6 +555,108 @@ 5 + + + true + + + + :/images/toolbar/color.png:/images/toolbar/color.png + + + Color 6 + + + Color 6 + + + 6 + + + + + true + + + + :/images/toolbar/color.png:/images/toolbar/color.png + + + Color 7 + + + Color 7 + + + 7 + + + + + true + + + + :/images/toolbar/color.png:/images/toolbar/color.png + + + Color 8 + + + Color 8 + + + 8 + + + + + true + + + + :/images/toolbar/color.png:/images/toolbar/color.png + + + Color 9 + + + Color 9 + + + 9 + + + + + true + + + + :/images/toolbar/color.png:/images/toolbar/color.png + + + Color 10 + + + Color 10 + + + 0 + + + + + + :/images/toolbar/settings.png:/images/toolbar/settings.png + + + Color Preferences + + + Open color preferences + + @@ -1186,158 +1288,6 @@ - - - true - - - - :/images/backgroundPalette/background1.svg - :/images/backgroundPalette/background1On.svg:/images/backgroundPalette/background1.svg - - - Plain Light Background - - - Light - - - Plain Light Background - - - - - true - - - - :/images/backgroundPalette/background2.svg - :/images/backgroundPalette/background2On.svg:/images/backgroundPalette/background2.svg - - - Grid Light Background - - - Light - - - Grid Light Background - - - - - true - - - - :/images/backgroundPalette/background5.svg - :/images/backgroundPalette/background5On.svg:/images/backgroundPalette/background5.svg - - - Ruled Light Background - - - Light - - - Ruled Light Background - - - - - true - - - - :/images/backgroundPalette/background7.svg - :/images/backgroundPalette/background7On.svg:/images/backgroundPalette/background7.svg - - - Seyes ruled Light Background - - - Light - - - Seyes ruled Light Background - - - - - true - - - - :/images/backgroundPalette/background3.svg - :/images/backgroundPalette/background3On.svg:/images/backgroundPalette/background3.svg - - - Plain Dark Background - - - Dark - - - Plain Dark Background - - - - - true - - - - :/images/backgroundPalette/background4.svg - :/images/backgroundPalette/background4On.svg:/images/backgroundPalette/background4.svg - - - Grid Dark Background - - - Dark - - - Grid Dark Background - - - - - true - - - - :/images/backgroundPalette/background6.svg - :/images/backgroundPalette/background6On.svg:/images/backgroundPalette/background6.svg - - - Ruled Dark Background - - - Dark - - - Ruled Dark Background - - - - - true - - - - :/images/backgroundPalette/background8.svg - :/images/backgroundPalette/background8On.svg:/images/backgroundPalette/background8.svg - - - Seyes ruled Dark Background - - - Dark - - - Seyes ruled Dark Background - - true @@ -1910,20 +1860,20 @@ Reset grid size - + true - :/images/minus.svg - :/images/save.svg:/images/minus.svg + :/images/lightMode.svg + :/images/darkMode.svg:/images/lightMode.svg - Draw intermediate grid lines + Switch between light and dark background - Draw intermediate grid lines + Switch between light and dark background diff --git a/resources/forms/preferences.ui b/resources/forms/preferences.ui index 45a2fc180..4ec35bd9f 100644 --- a/resources/forms/preferences.ui +++ b/resources/forms/preferences.ui @@ -7,7 +7,7 @@ 0 0 1074 - 566 + 816 @@ -26,9 +26,6 @@ QFrame::NoFrame - - QFrame::Raised - 0 @@ -51,9 +48,6 @@ - - Qt::Horizontal - 40 @@ -82,7 +76,7 @@ - 7 + 4 @@ -98,22 +92,22 @@ 0 - -285 - 1013 - 765 + 0 + 1018 + 1200 783 - 765 + 1200 0 - 416 + 920 1011 111 @@ -148,11 +142,8 @@ - - Qt::Horizontal - - QSizePolicy::Preferred + QSizePolicy::Fixed @@ -164,9 +155,6 @@ - - Qt::Horizontal - 40 @@ -181,7 +169,7 @@ 0 - 320 + 710 1011 101 @@ -219,11 +207,8 @@ - - Qt::Horizontal - - QSizePolicy::Preferred + QSizePolicy::Fixed @@ -235,9 +220,6 @@ - - Qt::Horizontal - 40 @@ -252,9 +234,9 @@ 0 - 620 + 1040 1011 - 101 + 111 @@ -303,9 +285,6 @@ - - Qt::Horizontal - QSizePolicy::Fixed @@ -319,9 +298,6 @@ - - Qt::Horizontal - 40 @@ -336,7 +312,7 @@ 0 - 218 + 600 1011 101 @@ -353,11 +329,8 @@ - - Qt::Horizontal - - QSizePolicy::Expanding + QSizePolicy::Fixed @@ -394,11 +367,8 @@ - - Qt::Horizontal - - QSizePolicy::Preferred + QSizePolicy::Fixed @@ -414,7 +384,7 @@ 0 - 102 + 480 1011 104 @@ -471,9 +441,6 @@ - - Qt::Vertical - 20 @@ -486,11 +453,8 @@ - - Qt::Horizontal - - QSizePolicy::Preferred + QSizePolicy::Fixed @@ -502,11 +466,8 @@ - - Qt::Horizontal - - QSizePolicy::Preferred + QSizePolicy::Fixed @@ -522,9 +483,9 @@ 0 - 540 + 840 1011 - 71 + 111 @@ -565,11 +526,34 @@ + + + + Theme: + + + + + + + + Auto + + + + + Light + + + + + Dark + + + + - - Qt::Horizontal - 40 @@ -582,11 +566,8 @@ - - Qt::Horizontal - - QSizePolicy::Preferred + QSizePolicy::Fixed @@ -604,62 +585,115 @@ 0 0 1011 - 90 + 450 + + + 0 + 0 + + + + + 0 + 450 + + Multi display - - - - - true - - - - - - - List of screens used for Control, Display and Previous pages - - - - - - - Qt::Horizontal - - - QSizePolicy::Preferred - - + + + + - 40 - 20 + 0 + 400 - - - - - - Show internal web page content on secondary screen or projector + + QFrame::NoFrame + + + QFrame::Plain + + + + + Qt::Vertical + + + + 20 + 10 + + + + + + + + + 0 + 0 + + + + + 600 + 300 + + + + + 900 + 800 + + + + + + + + Qt::Vertical + + + + 20 + 10 + + + + + - - - - Qt::Horizontal - - - - 40 - 20 - - - + + + + + + Show internal web page content on secondary screen or projector + + + + + + + List of screens used for Control, Display and Previous pages + + + + + + + true + + + + @@ -673,6 +707,26 @@ Grid + + + + Qt::ScrollBarAlwaysOff + + + true + + + + + 0 + 0 + 1032 + 470 + + + + + @@ -681,18 +735,12 @@ QFrame::NoFrame - - QFrame::Raised - QFrame::NoFrame - - QFrame::Raised - @@ -703,9 +751,6 @@ - - Qt::Horizontal - 40 @@ -723,10 +768,7 @@ - QFrame::StyledPanel - - - QFrame::Raised + QFrame::NoFrame @@ -738,15 +780,9 @@ QFrame::NoFrame - - QFrame::Raised - - - Qt::Horizontal - 154 @@ -777,7 +813,7 @@ Qt::Horizontal - QSlider::TicksAbove + QSlider::NoTicks 20 @@ -786,9 +822,6 @@ - - Qt::Horizontal - 156 @@ -805,9 +838,6 @@ QFrame::NoFrame - - QFrame::Raised - @@ -818,9 +848,6 @@ - - Qt::Horizontal - 40 @@ -838,10 +865,7 @@ - QFrame::StyledPanel - - - QFrame::Raised + QFrame::NoFrame @@ -853,15 +877,9 @@ QFrame::NoFrame - - QFrame::Raised - - - Qt::Horizontal - 154 @@ -892,7 +910,7 @@ Qt::Horizontal - QSlider::TicksAbove + QSlider::NoTicks 20 @@ -901,9 +919,6 @@ - - Qt::Horizontal - 156 @@ -920,9 +935,6 @@ - - Qt::Vertical - 20 @@ -938,14 +950,20 @@ Pen - + + + + 0 + 0 + + + + Qt::DefaultContextMenu + QFrame::NoFrame - - QFrame::Raised - @@ -955,14 +973,11 @@ Marker - + QFrame::NoFrame - - QFrame::Raised - @@ -1063,11 +1078,8 @@ - - Qt::Horizontal - - QSizePolicy::Preferred + QSizePolicy::Fixed @@ -1082,9 +1094,6 @@ - - Qt::Vertical - 20 @@ -1095,6 +1104,211 @@ + + + Shortcut + + + + + + Filter + + + + + + + + 0 + 0 + + + + QScrollArea:disabled {border: 1px solid red;} + + + true + + + + + 0 + 0 + 1032 + 423 + + + + + + + QAbstractItemView::NoEditTriggers + + + false + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + false + + + false + + + true + + + false + + + 25 + + + 25 + + + + + + + + + + + + + Active keyboard shortcuts without pressing Ctrl key + + + + + + + + + false + + + Shortcuts + + + Qt::AlignCenter + + + + + + + + Key Sequence + + + + + + + true + + + 1 + + + + + + + Mouse Button + + + + + + + true + + + + + + + Stylus Button + + + + + + + true + + + + + + + + + color: red; + + + Qt::PlainText + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Reset + + + + + + + Abort + + + + + + + QPushButton:checked {background-color: red} + + + Record + + + true + + + + + + + + + + true @@ -1255,7 +1469,7 @@ */ - Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + Qt::NoTextInteraction @@ -1300,7 +1514,7 @@ Jean-loup Gailly Mark Adler jloup@gzip.org madler@alumni.caltech.edu - Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + Qt::NoTextInteraction @@ -2012,7 +2226,7 @@ Public License instead of this License. But first, please read --8<---------------cut here---------------end--------------->8--- - Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + Qt::NoTextInteraction @@ -2382,7 +2596,7 @@ Public License instead of this License. --8<---------------cut here---------------end--------------->8--- - Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + Qt::NoTextInteraction @@ -3089,7 +3303,7 @@ Public License instead of this License. But first, please read --8<---------------cut here---------------end--------------->8--- - Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + Qt::NoTextInteraction @@ -3114,87 +3328,87 @@ Public License instead of this License. But first, please read <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } hr { height: 1px; border-width: 0; } -</style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:12pt; font-weight:600;">Translations</span><span style=" font-family:'Cantarell'; font-size:10pt;"><br /></span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">A special thanks to:<br /></span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">• Alexander Angelov and Iva Ninova for Bulgarian</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">• Francesc Busquets and Toni Hortal for Catalan</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">• Milo Ivir for Croatian</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">• Jaroslav Krejčí, Janek Wagner for Czech</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">• Geert Kraeye and Derk Klomp for Dutch</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">• Christian Oïhénart for French</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">• Félix Díaz López for Galician </span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">• Hans-Peter Zahno, Klaus Tenner and Yves Kaiser for German</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">• Yannis Kiolalis for Greek</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">• Imre Fekete for Hungarian</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">• Antonello Comi, Marco Menardi, Marco Gregori and Salvatore Cristaldi for Italian</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">• Didier Clerc for Japanese</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">• Faraniaina Domoina Rabarijaona for Malagasy</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">• Patricia Fisch and César Marques for Portuguese</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">• Ilia Ryabokon for Russian</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">• Jaroslav Ryník for Slovak</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">• Dorian Fuentes, Juan José Gutiérrez Aparicio and Félix Díaz López for Spanish</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">• Anki Chen for Traditional Chinese</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">• Ferhat Ozkasgarli and Sabri Ünal for Turkish</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">• Alex Compit and Joonel for Ukrainian</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;"><br /></span><span style=" font-family:'Cantarell'; font-size:12pt; font-weight:600;">Resources added in OpenBoard</span></p> +</style></head><body style=" font-family:'Noto Sans',''; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:12pt; font-weight:600;">Translations</span><span style=" font-family:'Cantarell';"><br /></span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">A special thanks to:<br /></span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">• Alexander Angelov and Iva Ninova for Bulgarian</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">• Francesc Busquets and Toni Hortal for Catalan</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">• Milo Ivir for Croatian</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">• Jaroslav Krejčí, Janek Wagner for Czech</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">• Geert Kraeye and Derk Klomp for Dutch</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">• Christian Oïhénart for French</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">• Félix Díaz López for Galician </span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">• Hans-Peter Zahno, Klaus Tenner and Yves Kaiser for German</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">• Yannis Kiolalis for Greek</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">• Imre Fekete for Hungarian</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">• Antonello Comi, Marco Menardi, Marco Gregori and Salvatore Cristaldi for Italian</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">• Didier Clerc for Japanese</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">• Faraniaina Domoina Rabarijaona for Malagasy</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">• Patricia Fisch and César Marques for Portuguese</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">• Ilia Ryabokon for Russian</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">• Jaroslav Ryník for Slovak</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">• Dorian Fuentes, Juan José Gutiérrez Aparicio and Félix Díaz López for Spanish</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">• Anki Chen for Traditional Chinese</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">• Ferhat Ozkasgarli and Sabri Ünal for Turkish</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">• Alex Compit and Joonel for Ukrainian</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';"><br /></span><span style=" font-family:'Cantarell'; font-size:12pt; font-weight:600;">Resources added in OpenBoard</span></p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:11pt;"><br /></span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">AndBasR.ttf </span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">Sil Open Font License</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://scripts.sil.org/OFL"><span style=" font-family:'Cantarell'; font-size:10pt; text-decoration: underline; color:#0000ff;">http://scripts.sil.org/OFL</span></a></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">(c) 2004-2008, SIL International (</span><a href="http://scripts.sil.org"><span style=" font-family:'Cantarell'; font-size:10pt; text-decoration: underline; color:#0000ff;">http://scripts.sil.org</span></a><span style=" font-family:'Cantarell'; font-size:10pt;">), </span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">with Reserved Font Names 'Andika' and 'SIL'.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;"><br /></span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">ec_cour.ttf </span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">Open Font License</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://scripts.sil.org/OFL"><span style=" font-family:'Cantarell'; font-size:10pt; text-decoration: underline; color:#0000ff;">http://scripts.sil.org/OFL</span></a></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">© Jean-Marie Douteau (</span><a href="mailto:douteau.ecolier@sfr.fr"><span style=" font-family:'Cantarell'; font-size:10pt; text-decoration: underline; color:#0000ff;">douteau.ecolier@sfr.fr</span></a><span style=" font-family:'Cantarell'; font-size:10pt;">)</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">Source : </span><a href="http://douteau.ecolier.perso.sfr.fr/page_ecolier.htm"><span style=" font-family:'Cantarell'; font-size:10pt; text-decoration: underline; color:#0000ff;">http://douteau.ecolier.perso.sfr.fr/page_ecolier.htm</span></a></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;"><br /></span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">ecl_cour.ttf</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">Open Font License</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://scripts.sil.org/OFL"><span style=" font-family:'Cantarell'; font-size:10pt; text-decoration: underline; color:#0000ff;">http://scripts.sil.org/OFL</span></a></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">© Jean-Marie Douteau (</span><a href="mailto:douteau.ecolier@sfr.fr"><span style=" font-family:'Cantarell'; font-size:10pt; text-decoration: underline; color:#0000ff;">douteau.ecolier@sfr.fr</span></a><span style=" font-family:'Cantarell'; font-size:10pt;">)</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">Source : </span><a href="http://douteau.ecolier.perso.sfr.fr/page_ecolier.htm"><span style=" font-family:'Cantarell'; font-size:10pt; text-decoration: underline; color:#0000ff;">http://douteau.ecolier.perso.sfr.fr/page_ecolier.htm</span></a></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">AndBasR.ttf </span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">Sil Open Font License</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://scripts.sil.org/OFL"><span style=" font-family:'Cantarell'; text-decoration: underline; color:#0000ff;">http://scripts.sil.org/OFL</span></a></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">(c) 2004-2008, SIL International (</span><a href="http://scripts.sil.org"><span style=" font-family:'Cantarell'; text-decoration: underline; color:#0000ff;">http://scripts.sil.org</span></a><span style=" font-family:'Cantarell';">), </span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">with Reserved Font Names 'Andika' and 'SIL'.</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';"><br /></span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">ec_cour.ttf </span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">Open Font License</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://scripts.sil.org/OFL"><span style=" font-family:'Cantarell'; text-decoration: underline; color:#0000ff;">http://scripts.sil.org/OFL</span></a></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">© Jean-Marie Douteau (</span><a href="mailto:douteau.ecolier@sfr.fr"><span style=" font-family:'Cantarell'; text-decoration: underline; color:#0000ff;">douteau.ecolier@sfr.fr</span></a><span style=" font-family:'Cantarell';">)</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">Source : </span><a href="http://douteau.ecolier.perso.sfr.fr/page_ecolier.htm"><span style=" font-family:'Cantarell'; text-decoration: underline; color:#0000ff;">http://douteau.ecolier.perso.sfr.fr/page_ecolier.htm</span></a></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';"><br /></span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">ecl_cour.ttf</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">Open Font License</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://scripts.sil.org/OFL"><span style=" font-family:'Cantarell'; text-decoration: underline; color:#0000ff;">http://scripts.sil.org/OFL</span></a></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">© Jean-Marie Douteau (</span><a href="mailto:douteau.ecolier@sfr.fr"><span style=" font-family:'Cantarell'; text-decoration: underline; color:#0000ff;">douteau.ecolier@sfr.fr</span></a><span style=" font-family:'Cantarell';">)</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">Source : </span><a href="http://douteau.ecolier.perso.sfr.fr/page_ecolier.htm"><span style=" font-family:'Cantarell'; text-decoration: underline; color:#0000ff;">http://douteau.ecolier.perso.sfr.fr/page_ecolier.htm</span></a></p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:11pt;"><br /></span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://douteau.ecolier.perso.sfr.fr/page_ecolier.htm"><span style=" font-family:'Cantarell'; font-size:10pt; text-decoration: underline; color:#000000;">EcritureA and EcritureB</span></a></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://creativecommons.org/licenses/by/3.0/"><span style=" font-family:'Cantarell'; font-size:10pt; text-decoration: underline; color:#0000ff;">Creative Commons BY-ND</span></a></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://douteau.ecolier.perso.sfr.fr/page_ecolier.htm"><span style=" font-family:'Cantarell'; font-size:10pt; text-decoration: underline; color:#000000;">DGESCO (</span></a><a href="mailto:degre.numerique@education.gouv.fr"><span style=" font-family:'Cantarell'; font-size:10pt; text-decoration: underline; color:#0000ff;">degre.numerique@education.gouv.fr</span></a><a href="http://douteau.ecolier.perso.sfr.fr/page_ecolier.htm"><span style=" font-family:'Cantarell'; font-size:10pt; text-decoration: underline; color:#000000;">)</span></a></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://douteau.ecolier.perso.sfr.fr/page_ecolier.htm"><span style=" font-family:'Cantarell'; font-size:10pt; text-decoration: underline; color:#000000;">Source: </span></a><a href="http://eduscol.education.fr/cid72979/polices-de-caracteres-cursives-pour-l-enseignement-de-l-ecriture.html"><span style=" font-family:'Cantarell'; font-size:10pt; text-decoration: underline; color:#0000ff;">http://eduscol.education.fr/cid72979/polices-de-caracteres-cursives-pour-l-enseignement-de-l-ecriture.html</span></a></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://douteau.ecolier.perso.sfr.fr/page_ecolier.htm"><span style=" font-family:'Cantarell'; text-decoration: underline; color:#000000;">EcritureA and EcritureB</span></a></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://creativecommons.org/licenses/by/3.0/"><span style=" font-family:'Cantarell'; text-decoration: underline; color:#0000ff;">Creative Commons BY-ND</span></a></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://douteau.ecolier.perso.sfr.fr/page_ecolier.htm"><span style=" font-family:'Cantarell'; text-decoration: underline; color:#000000;">DGESCO (</span></a><a href="mailto:degre.numerique@education.gouv.fr"><span style=" font-family:'Cantarell'; text-decoration: underline; color:#0000ff;">degre.numerique@education.gouv.fr</span></a><a href="http://douteau.ecolier.perso.sfr.fr/page_ecolier.htm"><span style=" font-family:'Cantarell'; text-decoration: underline; color:#000000;">)</span></a></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://douteau.ecolier.perso.sfr.fr/page_ecolier.htm"><span style=" font-family:'Cantarell'; text-decoration: underline; color:#000000;">Source: </span></a><a href="http://eduscol.education.fr/cid72979/polices-de-caracteres-cursives-pour-l-enseignement-de-l-ecriture.html"><span style=" font-family:'Cantarell'; text-decoration: underline; color:#0000ff;">http://eduscol.education.fr/cid72979/polices-de-caracteres-cursives-pour-l-enseignement-de-l-ecriture.html</span></a></p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:11pt;"><br /></span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">GeTypo Libre</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">Creative Commons BY-NC-ND</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://www.ge.ch/sem/cc/by-nc-nd/"><span style=" font-family:'Lucida Grande'; font-size:10pt; text-decoration: underline; color:#0000ff;">http://www.ge.ch/sem/cc/by-nc-nd/</span></a></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">© 2005-2015, Vista Multimedia SA, Droit de diffusion Etat de Genève - DIP</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://edu.ge.ch/sem/node/1294"><span style=" font-family:'Lucida Grande'; font-size:10pt; text-decoration: underline; color:#0000ff;">http://edu.ge.ch/sem/node/1294</span></a></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">GeTypo Libre</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">Creative Commons BY-NC-ND</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://www.ge.ch/sem/cc/by-nc-nd/"><span style=" font-family:'Lucida Grande'; text-decoration: underline; color:#0000ff;">http://www.ge.ch/sem/cc/by-nc-nd/</span></a></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">© 2005-2015, Vista Multimedia SA, Droit de diffusion Etat de Genève - DIP</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://edu.ge.ch/sem/node/1294"><span style=" font-family:'Lucida Grande'; text-decoration: underline; color:#0000ff;">http://edu.ge.ch/sem/node/1294</span></a></p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:11pt;"><br /></span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;">GraphMe Widget 2.1</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;">Yannick Vessaz</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">GraphMe Widget 2.1</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">Yannick Vessaz</span></p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:11pt;"><br /></span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">Papier.wgt 2.5.4</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">Developed by F. Le Cléac’h </span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">Papier.wgt 2.5.4</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">Developed by F. Le Cléac’h </span></p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="https://creativecommons.org/licenses/by-nc-sa/3.0/"><span style=" font-family:'Ubuntu'; font-size:11pt; text-decoration: underline; color:#0000ff;">Attribution-NonCommercial-ShareAlike 3.0 Unported (CC BY-NC-SA 3.0)</span></a></p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="https://www.openedu.fr"><span style=" font-family:'Ubuntu'; font-size:11pt; text-decoration: underline; color:#0000ff;">https://www.openedu.fr</span></a></p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:11pt;"><br /></span></p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:11pt;"><br /></span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;">QR-Code Widget</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;">Basilstotz</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;">Licence: CCO</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">QR-Code Widget</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">Basilstotz</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">Licence: CCO</span></p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:11pt;"><br /></span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt; color:#000000;">Sonata para piano (.mp3)</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt; color:#000000;">Óscar G. Villegas</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://creativecommons.org/licenses/by-nc-sa/3.0/es"><span style=" font-family:'Cantarell'; font-size:10pt; text-decoration: underline; color:#0000ff;">Creative Commons Attribution-NonCommercial-Share Alike 3.0 Unported</span></a></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://recursostic.educacion.es/bancoimagenes/web/"><span style=" font-family:'Cantarell'; font-size:10pt; text-decoration: underline; color:#0000ff;">http://recursostic.educacion.es/bancoimagenes/web/</span></a><span style=" font-family:'Cantarell'; font-size:10pt;"><br /></span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; color:#000000;">Sonata para piano (.mp3)</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; color:#000000;">Óscar G. Villegas</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://creativecommons.org/licenses/by-nc-sa/3.0/es"><span style=" font-family:'Cantarell'; text-decoration: underline; color:#0000ff;">Creative Commons Attribution-NonCommercial-Share Alike 3.0 Unported</span></a></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://recursostic.educacion.es/bancoimagenes/web/"><span style=" font-family:'Cantarell'; text-decoration: underline; color:#0000ff;">http://recursostic.educacion.es/bancoimagenes/web/</span></a><span style=" font-family:'Cantarell';"><br /></span></p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:11pt;"><br /></span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">wannaworktogether (.mp4)</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">Creative Commons</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://creativecommons.org/licenses/by/2.5/"><span style=" font-family:'Cantarell'; font-size:10pt; text-decoration: underline; color:#0000ff;">Creative Commons Attribution</span></a></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://creativecommons.org/videos/wanna-work-together"><span style=" font-family:'Cantarell'; font-size:10pt; text-decoration: underline; color:#0000ff;">http://creativecommons.org/videos/wanna-work-together</span></a></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;"><br /></span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">Worldmap_wdb_combined (.svg)</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">David Eccles (gringer)</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://creativecommons.org/licenses/by-sa/3.0/deed.en"><span style=" font-family:'Cantarell'; font-size:10pt; text-decoration: underline; color:#0000ff;">Creative Commons Attribution-Share Alike 3.0 Unported</span></a></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://commons.wikimedia.org/wiki/File:Worldmap_wdb_combined.svg"><span style=" font-family:'Cantarell'; font-size:10pt; text-decoration: underline; color:#0000ff;">http://commons.wikimedia.org/wiki/File:Worldmap_wdb_combined.svg</span></a></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">wannaworktogether (.mp4)</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">Creative Commons</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://creativecommons.org/licenses/by/2.5/"><span style=" font-family:'Cantarell'; text-decoration: underline; color:#0000ff;">Creative Commons Attribution</span></a></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://creativecommons.org/videos/wanna-work-together"><span style=" font-family:'Cantarell'; text-decoration: underline; color:#0000ff;">http://creativecommons.org/videos/wanna-work-together</span></a></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';"><br /></span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">Worldmap_wdb_combined (.svg)</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">David Eccles (gringer)</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://creativecommons.org/licenses/by-sa/3.0/deed.en"><span style=" font-family:'Cantarell'; text-decoration: underline; color:#0000ff;">Creative Commons Attribution-Share Alike 3.0 Unported</span></a></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://commons.wikimedia.org/wiki/File:Worldmap_wdb_combined.svg"><span style=" font-family:'Cantarell'; text-decoration: underline; color:#0000ff;">http://commons.wikimedia.org/wiki/File:Worldmap_wdb_combined.svg</span></a></p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:11pt;"><br /></span></p></body></html> @@ -3219,23 +3433,23 @@ hr { height: 1px; border-width: 0; } <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } hr { height: 1px; border-width: 0; } -</style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'Noto Sans',''; font-size:10pt; font-weight:400; font-style:normal;"> <table border="0" style="-qt-table-type: root; margin-top:4px; margin-bottom:4px; margin-left:4px; margin-right:4px;"> <tr> <td style="border: none;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">OpenBoard is copyright © 2022. All rights reserved.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Cantarell'; font-size:10pt;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">OpenBoard is derived from Open-Sankoré. Open-Sankoré is copyright © 2010-2015 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA). All right reserved.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Cantarell'; font-size:10pt;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">OpenBoard 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 &quot;OpenSSL&quot; library (or with modified versions of it that use the same license as the &quot;OpenSSL&quot; library). You can find the source code of this software at </span><a href="https://github.com/DIP-SEM/OpenBoard"><span style=" font-family:'.Helvetica Neue DeskInterface'; font-size:10pt; text-decoration: underline; color:#0000ff;">github.com/OpenBoard-org</span></a><span style=" font-family:'Cantarell'; font-size:10pt;">. </span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">OpenBoard 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 below for more details.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Cantarell'; font-size:10pt;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">Contact :</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">Service écoles-médias</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">Rue des Gazomètres 5</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">Case Postale 241</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">1211 Genève 8</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell'; font-size:10pt;">Switzerland</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">OpenBoard is copyright © 2022. All rights reserved.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Cantarell';"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">OpenBoard is derived from Open-Sankoré. Open-Sankoré is copyright © 2010-2015 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA). All right reserved.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Cantarell';"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">OpenBoard 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 &quot;OpenSSL&quot; library (or with modified versions of it that use the same license as the &quot;OpenSSL&quot; library). You can find the source code of this software at </span><a href="https://github.com/DIP-SEM/OpenBoard"><span style=" font-family:'.Helvetica Neue DeskInterface'; text-decoration: underline; color:#0000ff;">github.com/OpenBoard-org</span></a><span style=" font-family:'Cantarell';">. </span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">OpenBoard 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 below for more details.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Cantarell';"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">Contact :</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">Service écoles-médias</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">Rue des Gazomètres 5</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">Case Postale 241</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">1211 Genève 8</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Cantarell';">Switzerland</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://www.openboard.ch"><span style=" font-family:'.Helvetica Neue DeskInterface'; font-size:11pt; text-decoration: underline; color:#0000ff;">openboard.ch</span></a></p></td></tr></table></body></html> @@ -3299,6 +3513,17 @@ hr { height: 1px; border-width: 0; } QLineEdit
core/UBPreferencesController.h
+ + UBPreferredBackgroundWidget + QWidget +
gui/UBPreferredBackgroundWidget.h
+
+ + UBScreenLayoutPreview + QWidget +
core/UBPreferencesController.h
+ 1 +
useExternalBrowserCheckBox diff --git a/resources/i18n/OpenBoard_de.ts b/resources/i18n/OpenBoard_de.ts index 2eb616221..e6f43f5e5 100644 --- a/resources/i18n/OpenBoard_de.ts +++ b/resources/i18n/OpenBoard_de.ts @@ -3007,6 +3007,28 @@ Möchten Sie diese Fehler für diesen Computer ignorieren? Use all available displays Alle verfügbaren Bildschirme nutzen + + Key sequence already in use + Tastenkombination wird bereits benutzt + + + Mouse button already in use + Maustaste wird bereits benutzt + + + Stylus button already in use + Stifttaste wird bereits benutzt + + + Accept + preferencesDialog + Übernehmen + + + Record + preferencesDialog + Aufnehmen + UBSettings @@ -3078,7 +3100,7 @@ Möchten Sie diese Fehler für diesen Computer ignorieren? Reset zoom factor - Zoom zurücksetzen + Zoom-Faktor zurücksetzen @@ -3128,7 +3150,7 @@ Möchten Sie diese Fehler für diesen Computer ignorieren? Command - Kommando + Befehl @@ -3143,12 +3165,12 @@ Möchten Sie diese Fehler für diesen Computer ignorieren? Mouse Button - Mausknopf + Maustaste Tablet Button - Tablet Stiftknopf + Stifttaste @@ -4074,6 +4096,46 @@ p, li { white-space: pre-wrap; } List of screens used for Control, Display and Previous pages Liste der Bildschirme, die für die Hauptansicht, Erweiterte Ansicht und Vorherige Seiten verwendet werden + + Shortcut + Kurzbefehl + + + Filter + Filter + + + Active keyboard shortcuts without pressing Ctrl key + Aktiviere Kurzbefehle auch ohne Strg Taste + + + Shortcuts + Kurzbefehle + + + Abort + Abbrechen + + + Record + Aufnehmen + + + Stylus Button + Stifttaste + + + Mouse Button + Maustaste + + + Reset + Zurücksetzen + + + Key Sequence + Tasten + trapFlashDialog diff --git a/resources/i18n/OpenBoard_it.ts b/resources/i18n/OpenBoard_it.ts index 62dd8ac8f..04c1ff796 100644 --- a/resources/i18n/OpenBoard_it.ts +++ b/resources/i18n/OpenBoard_it.ts @@ -44,9 +44,9 @@ If you wish so, you may continue with an unverified certificate. Accepting an unverified certificate mean you may not be connected with the host you tried to connect to. Do you wish to override the security check and continue ? - Se vuoi, puoi prosseguire con un certificato non attestato. Accettare un certificato non attestato implica che potresti non essere connesso all'host al quale cerchi di accedere. + Se vuoi, puoi proseguire con un certificato non verificato. Accettare un certificato non verificato implica che potresti non essere connesso all'host al quale cerchi di accedere. - Vuoi comunque prosseguire ? + Vuoi comunque proseguire ? @@ -64,7 +64,7 @@ Do you wish to override the security check and continue ? Save as - Salvaguardare sotto + Salva come @@ -84,22 +84,22 @@ Do you wish to override the security check and continue ? %L1 B - %L1 o + %L1 B %L1 KiB - %L1 ko + %L1 KiB %L1 MiB - %L1 Mo + %L1 MiB %L1 GiB - %L1 Go + %L1 GiB @@ -119,7 +119,7 @@ Do you wish to override the security check and continue ? cancelled - %1 downloaded - %2/s - Anullato - %1 downloaded - %2/s + Annullato - %1 downloaded - %2/s @@ -134,7 +134,7 @@ Do you wish to override the security check and continue ? Remove from list - Ritirare dalla lista + Rimuovere dalla lista @@ -1790,12 +1790,12 @@ Do you wish to override the security check and continue ? Complete deletion of %1 documents/folders - Cancellazione definitiva des %1 documenti/folders + Cancellazione definitiva dei %1 documenti/folders You are about to permanantly delete %1 documents and/or folders. Are you sure ? - Stai per cancellare definitivamente %1 documenti et/o folders. Sei sicuro ? + Stai per cancellare definitivamente %1 documenti e/o folders. Sei sicuro ? @@ -2120,7 +2120,7 @@ Dando un nuovo nome si creerà un nuovo documento. Warnings during export was appeared - È apparso un avviso durante l'esportazione + Sono apparsi avvisi durante l'esportazione @@ -2504,12 +2504,12 @@ Dando un nuovo nome si creerà un nuovo documento. Layer up - stratificare + Strato verso l'alto Layer down - strato verso il basso + Strato verso il basso @@ -3034,7 +3034,7 @@ Vuoi ignorare gli errori per questo host? Background - Fondi + Sfondi @@ -3099,17 +3099,17 @@ Vuoi ignorare gli errori per questo host? Scroll page up - Scorri verso l'alto + Scorri la pagina verso l'alto Scroll down - Scorrere verso il basso + Scorri verso il basso Scroll page down - Scorrere verso il basso + Scorri la pagina verso il basso @@ -3129,7 +3129,7 @@ Vuoi ignorare gli errori per questo host? Key Sequence - Sequenza di tastiera + Sequenza di tasti @@ -3175,7 +3175,7 @@ Vuoi ignorare gli errori per questo host? Task MouseButton - Macchia + Compito @@ -3224,7 +3224,7 @@ Vuoi ignorare gli errori per questo host? Generating preview thumbnails ... - Generazione della miniatura di anteprima in corso... + Generazione delle miniature di anteprima in corso... @@ -3366,7 +3366,7 @@ Si prega di riavviare l'applicazione per accedere ai documenti aggiornati.< Open Web Inspector - Aprier l’ispettore Web + Aprire l’ispettore Web @@ -3470,7 +3470,7 @@ Si prega di riavviare l'applicazione per accedere ai documenti aggiornati.< Bottom layer limit reached - Limite del livello inferiore raggiunto + Raggiunto il limite inferiore degli strati @@ -3520,48 +3520,48 @@ Si prega di riavviare l'applicazione per accedere ai documenti aggiornati.< Enter username and password for "%1" at %2 - Entra l’utente e la password per «%1» a %2 + Inserisci l’utente e la password per «%1» a %2 Allow %1 to access your location information? - Autorizzare %1 ad accedere alla tua posizione ? + Autorizza %1 ad accedere alla tua posizione ? Allow %1 to access your microphone? - Autorizzare %1 ad accedere al tuo microfono ? + Autorizza %1 ad accedere al tuo microfono ? Allow %1 to access your webcam? - Autorizzare %1 ad accedere alla tua webcam ? + Autorizza %1 ad accedere alla tua webcam ? Allow %1 to access your microphone and webcam? - Autorizzare %1 ad accedere al tuo microfono ed alla tua webcam ? + Autorizza %1 ad accedere al tuo microfono ed alla tua webcam ? Allow %1 to lock your mouse cursor? - Autorizzare %1 a bloccare il cursore della tua mouse ? + Autorizza %1 a bloccare il cursore del tuo mouse ? Allow %1 to capture video of your desktop? - Autorizzare %1 a fare una cattura video del tuo desktop ? + Autorizza %1 a catturare un video del tuo desktop ? Allow %1 to capture audio and video of your desktop? - Autorizzare %1 a fare una cattura audio e video del tuo desktop ? + Autorizza %1 a catturare audio e video del tuo desktop ? Permission Request - Chiedere l’autorizzazione + Richiesta di autorizzazione @@ -3579,22 +3579,22 @@ Si prega di riavviare l'applicazione per accedere ai documenti aggiornati.< Render process normal exit - Uscita normale del procedimento resa + Uscita normale del procedimento di rendering Render process abnormal exit - Uscita anomala del procedimento di resa + Uscita anomala del procedimento di rendering Render process crashed - Il procedimento di resa ha subito un crash + Il procedimento di rendering ha subito un crash Render process killed - Il procedimento di resa si è fermato + Procedimento di rendering interrotto @@ -4062,7 +4062,7 @@ p, li { white-space: pre-wrap; } List of screens used for Control, Display and Previous pages - Lista degli schermi utilizzati per le visualizzazioni Principale, Esteso ePpagine precedenti + Lista degli schermi utilizzati per le visualizzazioni Principale, Esteso e Pagine precedenti diff --git a/resources/images/backgroundPalette/background1.svg b/resources/images/backgroundPalette/background1.svg deleted file mode 100644 index 468c39377..000000000 --- a/resources/images/backgroundPalette/background1.svg +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - - - -]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/resources/images/backgroundPalette/background1On.svg b/resources/images/backgroundPalette/background1On.svg deleted file mode 100644 index 83d52f55c..000000000 --- a/resources/images/backgroundPalette/background1On.svg +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/resources/images/backgroundPalette/background2.svg b/resources/images/backgroundPalette/background2.svg deleted file mode 100644 index 1023164cd..000000000 --- a/resources/images/backgroundPalette/background2.svg +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - - - - - -]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/resources/images/backgroundPalette/background2On.svg b/resources/images/backgroundPalette/background2On.svg deleted file mode 100644 index 75c81a8f4..000000000 --- a/resources/images/backgroundPalette/background2On.svg +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - - -]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/resources/images/backgroundPalette/background3.svg b/resources/images/backgroundPalette/background3.svg deleted file mode 100644 index 7198b419a..000000000 --- a/resources/images/backgroundPalette/background3.svg +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - - - -]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/resources/images/backgroundPalette/background3On.svg b/resources/images/backgroundPalette/background3On.svg deleted file mode 100644 index 26ebcd0b2..000000000 --- a/resources/images/backgroundPalette/background3On.svg +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - -]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/resources/images/backgroundPalette/background4.svg b/resources/images/backgroundPalette/background4.svg deleted file mode 100644 index 053ebf054..000000000 --- a/resources/images/backgroundPalette/background4.svg +++ /dev/null @@ -1,133 +0,0 @@ - - - -image/svg+xml \ No newline at end of file diff --git a/resources/images/backgroundPalette/background4On.svg b/resources/images/backgroundPalette/background4On.svg deleted file mode 100644 index 2513743cf..000000000 --- a/resources/images/backgroundPalette/background4On.svg +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - - -]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/resources/images/backgroundPalette/background5.svg b/resources/images/backgroundPalette/background5.svg deleted file mode 100644 index ac6e870e1..000000000 --- a/resources/images/backgroundPalette/background5.svg +++ /dev/null @@ -1,146 +0,0 @@ - - - -image/svg+xml \ No newline at end of file diff --git a/resources/images/backgroundPalette/background5On.svg b/resources/images/backgroundPalette/background5On.svg deleted file mode 100644 index 25d0a1c3c..000000000 --- a/resources/images/backgroundPalette/background5On.svg +++ /dev/null @@ -1,149 +0,0 @@ - - - -image/svg+xml \ No newline at end of file diff --git a/resources/images/backgroundPalette/background6.svg b/resources/images/backgroundPalette/background6.svg deleted file mode 100644 index 1c38545bb..000000000 --- a/resources/images/backgroundPalette/background6.svg +++ /dev/null @@ -1,146 +0,0 @@ - - - -image/svg+xml \ No newline at end of file diff --git a/resources/images/backgroundPalette/background6On.svg b/resources/images/backgroundPalette/background6On.svg deleted file mode 100644 index 1210a2516..000000000 --- a/resources/images/backgroundPalette/background6On.svg +++ /dev/null @@ -1,150 +0,0 @@ - - - -image/svg+xml \ No newline at end of file diff --git a/resources/images/backgroundPalette/background7.svg b/resources/images/backgroundPalette/background7.svg deleted file mode 100644 index bf960aaed..000000000 --- a/resources/images/backgroundPalette/background7.svg +++ /dev/null @@ -1,177 +0,0 @@ - - - -image/svg+xml diff --git a/resources/images/backgroundPalette/background7On.svg b/resources/images/backgroundPalette/background7On.svg deleted file mode 100644 index a1c67bbf6..000000000 --- a/resources/images/backgroundPalette/background7On.svg +++ /dev/null @@ -1,180 +0,0 @@ - - - -image/svg+xml diff --git a/resources/images/backgroundPalette/background8.svg b/resources/images/backgroundPalette/background8.svg deleted file mode 100644 index ad06d61b4..000000000 --- a/resources/images/backgroundPalette/background8.svg +++ /dev/null @@ -1,177 +0,0 @@ - - - -image/svg+xml diff --git a/resources/images/backgroundPalette/background8On.svg b/resources/images/backgroundPalette/background8On.svg deleted file mode 100644 index d622b6433..000000000 --- a/resources/images/backgroundPalette/background8On.svg +++ /dev/null @@ -1,220 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -image/svg+xml - - - - - - - - - - - - diff --git a/resources/images/backgroundPalette/bgButtonTemplateDarkOff.svg b/resources/images/backgroundPalette/bgButtonTemplateDarkOff.svg new file mode 100644 index 000000000..df2389127 --- /dev/null +++ b/resources/images/backgroundPalette/bgButtonTemplateDarkOff.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/images/backgroundPalette/bgButtonTemplateDarkOn.svg b/resources/images/backgroundPalette/bgButtonTemplateDarkOn.svg new file mode 100644 index 000000000..ede9a1f6d --- /dev/null +++ b/resources/images/backgroundPalette/bgButtonTemplateDarkOn.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/images/backgroundPalette/bgButtonTemplateLightOff.svg b/resources/images/backgroundPalette/bgButtonTemplateLightOff.svg new file mode 100644 index 000000000..c8282c6a5 --- /dev/null +++ b/resources/images/backgroundPalette/bgButtonTemplateLightOff.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/images/backgroundPalette/bgButtonTemplateLightOn.svg b/resources/images/backgroundPalette/bgButtonTemplateLightOn.svg new file mode 100644 index 000000000..971f280c8 --- /dev/null +++ b/resources/images/backgroundPalette/bgButtonTemplateLightOn.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/images/close-no-bg.png b/resources/images/close-no-bg.png new file mode 100644 index 000000000..949b5900c Binary files /dev/null and b/resources/images/close-no-bg.png differ diff --git a/resources/images/darkMode.svg b/resources/images/darkMode.svg new file mode 100644 index 000000000..9e8caa180 --- /dev/null +++ b/resources/images/darkMode.svg @@ -0,0 +1,16 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/resources/images/down_arrow.png b/resources/images/down_arrow.png deleted file mode 100644 index a51817681..000000000 Binary files a/resources/images/down_arrow.png and /dev/null differ diff --git a/resources/images/down_arrow.svg b/resources/images/down_arrow.svg new file mode 100644 index 000000000..f3c9dd5fc --- /dev/null +++ b/resources/images/down_arrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/images/left_arrow.png b/resources/images/left_arrow.png deleted file mode 100644 index 53e58f205..000000000 Binary files a/resources/images/left_arrow.png and /dev/null differ diff --git a/resources/images/left_arrow.svg b/resources/images/left_arrow.svg new file mode 100644 index 000000000..c7d64d1d0 --- /dev/null +++ b/resources/images/left_arrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/images/lightMode.svg b/resources/images/lightMode.svg new file mode 100644 index 000000000..6d6e7992e --- /dev/null +++ b/resources/images/lightMode.svg @@ -0,0 +1,16 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/resources/images/right_arrow.png b/resources/images/right_arrow.png deleted file mode 100644 index e9a64d2da..000000000 Binary files a/resources/images/right_arrow.png and /dev/null differ diff --git a/resources/images/right_arrow.svg b/resources/images/right_arrow.svg new file mode 100644 index 000000000..eacb2d0f2 --- /dev/null +++ b/resources/images/right_arrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/images/up_arrow.png b/resources/images/up_arrow.png deleted file mode 100644 index 10aa5ab3b..000000000 Binary files a/resources/images/up_arrow.png and /dev/null differ diff --git a/resources/images/up_arrow.svg b/resources/images/up_arrow.svg new file mode 100644 index 000000000..3bc4a5ca4 --- /dev/null +++ b/resources/images/up_arrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/lightTheme.qss b/resources/lightTheme.qss new file mode 100644 index 000000000..dd81d453d --- /dev/null +++ b/resources/lightTheme.qss @@ -0,0 +1,971 @@ +/* + * OpenBoard Light Theme + */ + +QWidget { + background-color: #edf2f7; + color: #1f2937; +} + +QMainWindow, +QDialog, +QMessageBox, +QMenuBar, +QStatusBar, +QToolBar, +QDockWidget::title { + background-color: #edf2f7; + color: #1f2937; +} + +QScrollArea, +QAbstractScrollArea, +QListView, +QTreeView, +QTableView { + background-color: #ffffff; + color: #1f2937; +} + +QLineEdit, +QTextEdit, +QPlainTextEdit, +QComboBox, +QSpinBox, +QDoubleSpinBox { + background-color: #ffffff; + color: #1f2937; + border: 1px solid #d9e0e8; + border-radius: 6px; + padding: 4px 10px; + selection-background-color: #eaf4ff; + selection-color: #1f2937; +} + +QLineEdit:hover, +QTextEdit:hover, +QPlainTextEdit:hover, +QComboBox:hover, +QSpinBox:hover, +QDoubleSpinBox:hover { + border: 1px solid #c7d3df; +} + +QLineEdit:focus, +QTextEdit:focus, +QPlainTextEdit:focus, +QComboBox:focus, +QSpinBox:focus, +QDoubleSpinBox:focus { + border: 1px solid #3d8fd1; + background-color: #ffffff; +} + +QSpinBox::up-button, +QDoubleSpinBox::up-button, +QSpinBox::down-button, +QDoubleSpinBox::down-button { + background-color: #f7f9fc; + border-left: 1px solid #d9e0e8; + width: 18px; +} + +QSpinBox::up-button, +QDoubleSpinBox::up-button { + border-top-right-radius: 6px; +} + +QSpinBox::down-button, +QDoubleSpinBox::down-button { + border-bottom-right-radius: 6px; +} + +QSpinBox::up-button:hover, +QDoubleSpinBox::up-button:hover, +QSpinBox::down-button:hover, +QDoubleSpinBox::down-button:hover { + background-color: #eef6ff; +} + +QSpinBox::up-arrow, +QDoubleSpinBox::up-arrow { + image: url(:/images/up_arrow.svg); + width: 10px; + height: 10px; +} + +QSpinBox::down-arrow, +QDoubleSpinBox::down-arrow { + image: url(:/images/down_arrow.svg); + width: 10px; + height: 10px; +} + +QComboBox::drop-down { + border: none; + width: 24px; +} + +QComboBox::down-arrow { + image: url(:/images/down_arrow.svg); + width: 12px; + height: 12px; +} + +QComboBox QAbstractItemView { + background-color: #ffffff; + color: #1f2937; + border: 1px solid #d9e0e8; + selection-background-color: #eaf4ff; + selection-color: #1f2937; + outline: none; +} + +QPushButton { + background-color: #f7f9fc; + color: #1f2937; + border: 1px solid #d9e0e8; + border-radius: 6px; + padding: 6px 16px; + min-height: 20px; +} + +QPushButton:hover { + background-color: #eef6ff; + color: #1f2937; + border: 1px solid #d3e4f6; +} + +QPushButton:pressed { + background-color: #3d8fd1; + color: #ffffff; + border: 1px solid #2a82da; +} + +QPushButton:checked, +QPushButton:default { + background-color: #3d8fd1; + color: #ffffff; + border: 1px solid #2a82da; +} + +QPushButton:disabled { + background-color: #eef2f7; + color: #94a3b8; + border: 1px solid #e2e8f0; +} + +QToolButton { + background-color: transparent; + color: #1f2937; + border: 1px solid transparent; + border-radius: 6px; + padding: 5px; + margin: 2px; +} + +QToolButton:hover { + background-color: #eef6ff; + color: #1f2937; + border: 1px solid #d3e4f6; +} + +QToolButton:pressed, +QToolButton:checked { + background-color: #3d8fd1; + color: #ffffff; + border: 1px solid #2a82da; +} + +QDialog#preferencesDialog QPushButton#closeButton, +QDialog#preferencesDialog QPushButton#closeButton:hover, +QDialog#preferencesDialog QPushButton#closeButton:pressed, +QDialog#preferencesDialog QPushButton#closeButton:default { + color: #ffffff; +} + +QDialog#preferencesDialog { + background-color: #f2f6fa; +} + +QDialog#preferencesDialog QTabWidget::pane, +QDialog#preferencesDialog QFrame#frame, +QDialog#preferencesDialog QFrame#gridFrame { + background-color: #f2f6fa; +} + +QDialog#preferencesDialog QScrollArea, +QDialog#preferencesDialog QAbstractScrollArea, +QDialog#preferencesDialog QWidget#scrollAreaWidgetContents, +QDialog#preferencesDialog QWidget#scrollAreaWidgetContents_2 { + background-color: #edf2f7; +} + +QDialog#preferencesDialog QTabBar::tab:selected { + background-color: #f2f6fa; + border-bottom-color: #f2f6fa; +} + +QDialog#preferencesDialog QGroupBox, +QDialog#preferencesDialog QGroupBox::title { + background-color: #edf2f7; +} + +#UBPageNavigationWidget { + background-color: #edf2f7; +} + +#UBMessageWindow { + color: #1f2937; +} + +#UBMessageWindow QLabel#UBMessageWindowLabel { + background: transparent; + border: none; + color: #0f172a; + font-size: 15px; + font-weight: 600; + padding: 0px 2px 0px 0px; +} + +#UBMessageWindow UBSpinningWheel#UBMessageWindowSpinner { + background: transparent; + color: #3d8fd1; +} + +QToolButton#ubButtonGroupLeft, +QToolButton#ubButtonGroupCenter, +QToolButton#ubButtonGroupRight, +QToolButton#desktop-ubButtonGroupLeft, +QToolButton#desktop-ubButtonGroupCenter, +QToolButton#desktop-ubButtonGroupRight { + background: qlineargradient(x1: 0, y1: 0.49, x2: 0, y2: 0.5, stop: 0 #fdfefe, stop: 1 #e7edf4); + color: #1f2937; + border-radius: 0px; + border-top: 1px solid #d9e0e8; + border-bottom: 1px solid #d9e0e8; + border-left: none; + border-right: none; +} + +QToolButton#ubButtonGroupLeft, +QToolButton#desktop-ubButtonGroupLeft { + border-left: 1px solid #d9e0e8; + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; +} + +QToolButton#ubButtonGroupRight, +QToolButton#desktop-ubButtonGroupRight { + border-right: 1px solid #d9e0e8; + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; +} + +QToolButton#ubButtonGroupLeft:hover, +QToolButton#ubButtonGroupCenter:hover, +QToolButton#ubButtonGroupRight:hover, +QToolButton#desktop-ubButtonGroupLeft:hover, +QToolButton#desktop-ubButtonGroupCenter:hover, +QToolButton#desktop-ubButtonGroupRight:hover { + background: qlineargradient(x1: 0, y1: 0.49, x2: 0, y2: 0.5, stop: 0 #f4f9ff, stop: 1 #dcebf9); + color: #1f2937; + border-top-color: #c6d9ec; + border-bottom-color: #c6d9ec; +} + +QToolButton#ubButtonGroupLeft:pressed, +QToolButton#ubButtonGroupCenter:pressed, +QToolButton#ubButtonGroupRight:pressed, +QToolButton#desktop-ubButtonGroupLeft:pressed, +QToolButton#desktop-ubButtonGroupCenter:pressed, +QToolButton#desktop-ubButtonGroupRight:pressed { + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #4c9bdb, stop: 1 #2a82da); + color: #ffffff; + border-top: 1px solid #2a82da; + border-bottom: 1px solid #2a82da; + border-left: 1px solid #2a82da; + border-right: 1px solid #2a82da; +} + +QToolButton#ubButtonGroupLeft:checked, +QToolButton#ubButtonGroupCenter:checked, +QToolButton#ubButtonGroupRight:checked, +QToolButton#desktop-ubButtonGroupLeft:checked, +QToolButton#desktop-ubButtonGroupCenter:checked, +QToolButton#desktop-ubButtonGroupRight:checked { + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #5aa5e0, stop: 1 #3d8fd1); + color: #ffffff; + border-top: 1px solid #2a82da; + border-bottom: 1px solid #2a82da; + border-left: 1px solid #2a82da; + border-right: 1px solid #2a82da; +} + +QToolButton#ubButtonGroupRight[colorPaletteConfigButton="true"], +QToolButton#desktop-ubButtonGroupRight[colorPaletteConfigButton="true"] { + background: qlineargradient(x1: 0, y1: 0.49, x2: 0, y2: 0.5, stop: 0 #e8edf4, stop: 1 #cfd8e3); + color: #334155; + border-top: 1px solid #bcc8d5; + border-bottom: 1px solid #bcc8d5; + border-left: 1px solid #bcc8d5; + border-right: 1px solid #bcc8d5; +} + +QToolButton#ubButtonGroupRight[colorPaletteConfigButton="true"]:hover, +QToolButton#desktop-ubButtonGroupRight[colorPaletteConfigButton="true"]:hover { + background: qlineargradient(x1: 0, y1: 0.49, x2: 0, y2: 0.5, stop: 0 #dee6ef, stop: 1 #c4d0dc); + color: #1e293b; +} + +QToolButton#ubButtonGroupRight[colorPaletteConfigButton="true"]:pressed, +QToolButton#desktop-ubButtonGroupRight[colorPaletteConfigButton="true"]:pressed { + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #d1dae5, stop: 1 #b4c1d0); + color: #0f172a; +} + +QDialog#colorPreferencesDialog { + background-color: #f2f6fa; +} + +QDialog#colorPreferencesDialog QWidget#colorPreferencesHeader { + background-color: #edf2f7; + border: 1px solid #d9e0e8; + border-radius: 10px; +} + +QDialog#colorPreferencesDialog QLabel#colorPreferencesHeaderLabel { + color: #0f172a; + font-size: 14px; + font-weight: 600; + background: transparent; +} + +QDialog#colorPreferencesDialog QSlider#colorPreferencesPaletteSizeSlider { + min-width: 190px; + background: transparent; +} + +QDialog#colorPreferencesDialog QSlider#colorPreferencesPaletteSizeSlider::groove:horizontal { + height: 6px; + background-color: #d7e1ec; + border: none; + border-radius: 3px; +} + +QDialog#colorPreferencesDialog QSlider#colorPreferencesPaletteSizeSlider::handle:horizontal { + width: 18px; + margin: -7px 0; + background-color: #3d8fd1; + border: 1px solid #2a82da; + border-radius: 9px; +} + +QDialog#colorPreferencesDialog QSlider#colorPreferencesPaletteSizeSlider::sub-page:horizontal { + background-color: #3d8fd1; + border-radius: 3px; +} + +QDialog#colorPreferencesDialog QLabel#colorPreferencesPaletteSizeValue { + min-width: 32px; + padding: 4px 8px; + background: #ffffff; + color: #0f172a; + border: 1px solid #d9e0e8; + border-radius: 6px; + font-size: 14px; + font-weight: 600; +} + +QDialog#colorPreferencesDialog QWidget#colorPreferencesHint { + background: #fff8db; + border: 1px solid #ead9a2; + border-radius: 8px; +} + +QDialog#colorPreferencesDialog QLabel#colorPreferencesHintIcon { + background: transparent; + min-width: 20px; +} + +QDialog#colorPreferencesDialog QLabel#colorPreferencesHintText { + background: transparent; + color: #5b4b1f; + font-size: 12px; + line-height: 1.3em; +} + +QDialog#colorPreferencesDialog QTabWidget#colorPreferencesTabWidget::pane { + border: none; + background: transparent; + top: -1px; +} + +QDialog#colorPreferencesDialog QTabBar::tab, +QDialog#colorPreferencesDialog QTabBar::tab:top { + background: #e2eaf3; + color: #475569; + border: 1px solid #d9e0e8; + border-radius: 6px; + padding: 8px 18px; + margin-right: 8px; + font-weight: 600; +} + +QDialog#colorPreferencesDialog QTabBar::tab:selected { + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #5aa5e0, stop: 1 #3d8fd1); + color: #ffffff; + border: 1px solid #2a82da; +} + +QDialog#colorPreferencesDialog QTabBar::tab:hover:!selected { + background: #e8eff7; + color: #1e293b; +} + +QDialog#colorPreferencesDialog QWidget#colorPreferencesSection { + background: #edf2f7; + border: 1px solid #d9e0e8; + border-radius: 10px; +} + +QDialog#colorPreferencesDialog QLabel#colorPreferencesSectionTitle { + background: transparent; + color: #0f172a; + font-size: 13px; + font-weight: 600; +} + +QDialog#colorPreferencesDialog QFrame#colorPreviewFrame { + border-radius: 8px; +} + +QDialog#colorPreferencesDialog QFrame#colorPreviewFrame[previewMode="light"] { + background: #ffffff; + border: 1px solid #d9e0e8; +} + +QDialog#colorPreferencesDialog QFrame#colorPreviewFrame[previewMode="dark"] { + background: #131a24; + border: 1px solid #475569; +} + +QDialog#colorPreferencesDialog QLabel#colorShortcutLabel { + background: transparent; + color: #64748b; + font-size: 11px; + font-weight: 700; +} + +QDialog#colorPreferencesDialog QFrame#colorPreviewFrame[previewMode="dark"] QLabel#colorShortcutLabel { + color: #cbd5e1; +} + +QDialog#colorPreferencesDialog QFrame#colorPreviewFrame QToolButton#ubButtonGroupLeft, +QDialog#colorPreferencesDialog QFrame#colorPreviewFrame QToolButton#ubButtonGroupCenter, +QDialog#colorPreferencesDialog QFrame#colorPreviewFrame QToolButton#ubButtonGroupRight { + background: qlineargradient(x1: 0, y1: 0.49, x2: 0, y2: 0.5, stop: 0 #fdfefe, stop: 1 #e7edf4); + border-top: 1px solid #d9e0e8; + border-bottom: 1px solid #d9e0e8; + border-left: none; + border-right: none; + border-radius: 0px; +} + +QDialog#colorPreferencesDialog QFrame#colorPreviewFrame[previewMode="light"] QToolButton#ubButtonGroupLeft { + border-left: 1px solid #d9e0e8; + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; +} + +QDialog#colorPreferencesDialog QFrame#colorPreviewFrame[previewMode="light"] QToolButton#ubButtonGroupRight { + border-right: 1px solid #d9e0e8; + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; +} + +QDialog#colorPreferencesDialog QFrame#colorPreviewFrame[previewMode="dark"] QToolButton#ubButtonGroupLeft, +QDialog#colorPreferencesDialog QFrame#colorPreviewFrame[previewMode="dark"] QToolButton#ubButtonGroupCenter, +QDialog#colorPreferencesDialog QFrame#colorPreviewFrame[previewMode="dark"] QToolButton#ubButtonGroupRight { + background: qlineargradient(x1: 0, y1: 0.49, x2: 0, y2: 0.5, stop: 0 #505050, stop: 1 #3a3a3a); + border-top: 1px solid #435066; + border-bottom: 1px solid #435066; + color: #ffffff; +} + +QDialog#colorPreferencesDialog QFrame#colorPreviewFrame[previewMode="dark"] QToolButton#ubButtonGroupLeft { + border-left: 1px solid #435066; + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; +} + +QDialog#colorPreferencesDialog QFrame#colorPreviewFrame[previewMode="dark"] QToolButton#ubButtonGroupRight { + border-right: 1px solid #435066; + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; +} + +QDialog#colorPreferencesDialog QFrame#colorPreviewFrame QToolButton:hover { + background: qlineargradient(x1: 0, y1: 0.49, x2: 0, y2: 0.5, stop: 0 #f4f9ff, stop: 1 #dcebf9); + border-top-color: #c6d9ec; + border-bottom-color: #c6d9ec; +} + +QDialog#colorPreferencesDialog QFrame#colorPreviewFrame[previewMode="dark"] QToolButton:hover { + background: qlineargradient(x1: 0, y1: 0.49, x2: 0, y2: 0.5, stop: 0 #606060, stop: 1 #4a4a4a); + border-top-color: #64748b; + border-bottom-color: #64748b; +} + +QDialog#colorPreferencesDialog QLabel#colorPreferencesOpacityValue { + color: #0f172a; + font-size: 13px; + font-weight: 700; + background: transparent; +} + +QDialog#colorPreferencesDialog QPushButton#colorPreferencesResetButton { + padding: 7px 14px; + background: #f7f9fc; + color: #334155; + border: 1px solid #d9e0e8; + border-radius: 6px; + font-weight: 600; +} + +QDialog#colorPreferencesDialog QPushButton#colorPreferencesResetButton:hover { + background: #eef6ff; + border: 1px solid #d3e4f6; +} + +QDialog#colorPreferencesDialog QPushButton#colorPreferencesResetButton:focus, +QDialog#colorPreferencesDialog QPushButton#colorPreferencesResetButton:pressed, +QDialog#colorPreferencesDialog QPushButton#colorPreferencesResetButton:checked, +QDialog#colorPreferencesDialog QPushButton#colorPreferencesResetButton:default { + background: #3d8fd1; + color: #ffffff; + border: 1px solid #2a82da; +} + +QDialog#colorPreferencesDialog QDialogButtonBox#colorPreferencesButtonBox QPushButton { + min-width: 92px; + padding: 8px 18px; + border-radius: 6px; + font-weight: 600; +} + +QDialog#colorPreferencesDialog QDialogButtonBox#colorPreferencesButtonBox QPushButton:focus, +QDialog#colorPreferencesDialog QDialogButtonBox#colorPreferencesButtonBox QPushButton:pressed, +QDialog#colorPreferencesDialog QDialogButtonBox#colorPreferencesButtonBox QPushButton:checked, +QDialog#colorPreferencesDialog QDialogButtonBox#colorPreferencesButtonBox QPushButton:default { + background-color: #3d8fd1; + color: #ffffff; + border: 1px solid #2a82da; +} + +QCheckBox, +QRadioButton { + color: #1f2937; + spacing: 6px; +} + +QCheckBox::indicator, +QRadioButton::indicator { + width: 16px; + height: 16px; + border: 1px solid #c7d3df; + border-radius: 4px; + background-color: #ffffff; +} + +QCheckBox::indicator:hover, +QRadioButton::indicator:hover { + border: 1px solid #3d8fd1; + background-color: #eef6ff; +} + +QCheckBox::indicator:checked, +QRadioButton::indicator:checked { + background-color: #3d8fd1; + border: 1px solid #2a82da; +} + +QRadioButton::indicator { + border-radius: 8px; +} + +QSlider::groove:horizontal, +QSlider::groove:vertical { + background-color: #e2e8f0; + border: 1px solid #d3dce6; + border-radius: 4px; +} + +QSlider::groove:horizontal { + height: 8px; +} + +QSlider::groove:vertical { + width: 8px; +} + +QSlider::handle:horizontal, +QSlider::handle:vertical { + background-color: #3d8fd1; + border: 1px solid #2a82da; + border-radius: 6px; +} + +QSlider::handle:horizontal { + width: 16px; + margin: -5px 0; +} + +QSlider::handle:vertical { + height: 16px; + margin: 0 -5px; +} + +QScrollBar:vertical { + background: transparent; + width: 12px; + margin: 8px 4px 8px 0px; +} + +QScrollBar:horizontal { + background: transparent; + height: 12px; + margin: 0px 8px 4px 8px; +} + +QScrollBar::handle:vertical, +QScrollBar::handle:horizontal { + background: #cdd7e3; + border: 1px solid #c0ccd8; + border-radius: 4px; +} + +QScrollBar::handle:vertical:!hover, +QScrollBar::handle:horizontal:!hover { + background: #cdd7e3; + border: 1px solid #c0ccd8; +} + +QScrollBar::handle:vertical { + min-height: 20px; +} + +QScrollBar::handle:horizontal { + min-width: 20px; +} + +QScrollBar::handle:vertical:hover, +QScrollBar::handle:horizontal:hover { + background: #5aa5e0; + border: 1px solid #2a82da; +} + +QScrollBar::handle:vertical:pressed, +QScrollBar::handle:horizontal:pressed { + background: #2a82da; + border: 1px solid #2a82da; +} + +QScrollBar::add-line:vertical, +QScrollBar::sub-line:vertical, +QScrollBar::add-line:horizontal, +QScrollBar::sub-line:horizontal { + background: transparent; + border: none; + width: 0px; + height: 0px; +} + +QScrollBar::add-page:vertical, +QScrollBar::sub-page:vertical, +QScrollBar::add-page:horizontal, +QScrollBar::sub-page:horizontal { + background: transparent; +} + +QTreeView { + background-color: #ffffff; + color: #1f2937; + border: 1px solid #d9e0e8; + alternate-background-color: #fbfcfe; + selection-background-color: #eaf4ff; + selection-color: #1f2937; + show-decoration-selected: 0; + outline: none; +} + +QListView, +QTableView { + background-color: #ffffff; + color: #1f2937; + border: 1px solid #d9e0e8; + alternate-background-color: #fbfcfe; + selection-background-color: #eaf4ff; + selection-color: #1f2937; + show-decoration-selected: 1; + outline: none; +} + +QTreeView::item, +QListView::item, +QTableView::item { + padding: 4px; +} + +QTreeView::item:hover, +QListView::item:hover, +QTableView::item:hover { + background-color: #f3f8fd; +} + +QTreeView::item:selected, +QListView::item:selected, +QTableView::item:selected { + background-color: #3d8fd1; + color: #ffffff; +} + +QTreeView::item:selected:active, +QListView::item:selected:active, +QTableView::item:selected:active { + background: #3d8fd1; + color: #ffffff; + border: none; +} + +QTreeView::branch:selected { + background: transparent; + color: inherit; + border: none; +} + +QTreeView QLineEdit, +QListView QLineEdit, +QTableView QLineEdit { + padding: 1px 6px; +} + +QMenuBar { + border-bottom: 1px solid #e2e8f0; +} + +QMenuBar::item { + background-color: transparent; + padding: 4px 8px; + border-radius: 4px; +} + +QMenuBar::item:selected, +QMenuBar::item:pressed { + background-color: #eef6ff; + color: #1f2937; +} + +QMenu { + background-color: #ffffff; + color: #1f2937; + border: 1px solid #d9e0e8; + padding: 6px; +} + +QMenu::item { + padding: 6px 30px; + border-radius: 4px; +} + +QMenu::item:selected { + background-color: #eef6ff; + color: #1f2937; +} + +QMenu::separator { + height: 1px; + background-color: #e2e8f0; + margin: 4px 0px; +} + +QToolBar { + border: none; + spacing: 4px; + padding: 6px; +} + +QToolBar QToolButton:pressed, +QToolBar QToolButton:checked { + background-color: #3d8fd1; + color: #ffffff; + border: 1px solid #2a82da; +} + +QToolButton#ubButtonMenu:pressed, +QToolButton#ubButtonMenu:checked, +QToolBar QToolButton#ubButtonMenu:pressed, +QToolBar QToolButton#ubButtonMenu:checked { + background-color: #3d8fd1; + color: #ffffff; + border: 1px solid #2a82da; +} + +QToolBar::separator { + background-color: #d9e0e8; + width: 1px; + margin: 6px 4px; +} + +QTabWidget::pane { + border: 1px solid #d9e0e8; + background-color: #ffffff; + border-top: none; + border-bottom-left-radius: 8px; + border-bottom-right-radius: 8px; +} + +QTabBar::tab { + background-color: #eef2f7; + color: #667085; + border: 1px solid #d9e0e8; + padding: 7px 14px; + margin-right: 4px; + border-top-left-radius: 6px; + border-top-right-radius: 6px; +} + +QTabBar::tab:selected { + background-color: #ffffff; + color: #1f2937; + border-bottom-color: #ffffff; +} + +QTabBar::tab:hover:!selected { + background-color: #f3f8fd; + color: #1f2937; +} + +QHeaderView::section { + background-color: #fbfcfe; + color: #667085; + padding: 6px; + border: 1px solid #e2e8f0; + font-weight: bold; +} + +QGroupBox { + color: #1f2937; + border: 1px solid #d9e0e8; + border-radius: 8px; + margin-top: 12px; + font-weight: bold; + padding-top: 10px; + background-color: #ffffff; +} + +QGroupBox::title { + subcontrol-origin: margin; + subcontrol-position: top left; + padding: 0 6px; + color: #667085; + background-color: #f5f7fa; +} + +QProgressBar { + background-color: #edf2f7; + border: 1px solid #d9e0e8; + border-radius: 6px; + text-align: center; + color: #1f2937; + height: 18px; +} + +QProgressBar::chunk { + background-color: #3d8fd1; + border-radius: 5px; +} + +QSplitter::handle { + background-color: #e2e8f0; +} + +QSplitter::handle:hover { + background-color: #bfdcff; +} + +QToolTip { + background-color: #ffffff; + color: #1f2937; + border: 1px solid #d9e0e8; + padding: 6px; + border-radius: 3px; +} + +#BackgroundPaletteScrollArea { + background-color: #f2f6fa; + border: 1px solid #cfd8e3; + border-radius: 8px; +} + +#BackgroundPaletteScrollArea QWidget { + background-color: #f2f6fa; +} + +#BackgroundPalette { + background-color: #f2f6fa; + border: 1px solid #cfd8e3; + border-radius: 10px; +} + +#AddItemPalette { + background-color: #f2f6fa; + border: 1px solid #cfd8e3; + border-radius: 10px; +} + +#DesktopPropertyPalette { + background-color: #f2f6fa; + border: 1px solid #cfd8e3; + border-radius: 10px; +} + +#UBStartupHintsPalette QCheckBox { + background: transparent; + color: #1f2937; +} + +#BackgroundPalette QLabel, +#BackgroundPaletteLabel { + color: #1f2937; + background: transparent; +} + +#BackgroundPalette QToolButton#closeButton, +#AddItemPalette QToolButton#closeButton, +#UBStartupHintsPalette QToolButton#closeButton { + background: transparent; + border: none; + border-radius: 5px; + padding: 0px; + color: #667085; + font-size: 20px; + font-weight: bold; + min-width: 16px; + max-width: 16px; + min-height: 16px; + max-height: 16px; +} + +#BackgroundPalette QToolButton#closeButton:hover, +#AddItemPalette QToolButton#closeButton:hover, +#UBStartupHintsPalette QToolButton#closeButton:hover { + background: #eef6ff; + color: #1f2937; +} + +#BackgroundPalette QToolButton#closeButton:pressed, +#AddItemPalette QToolButton#closeButton:pressed, +#UBStartupHintsPalette QToolButton#closeButton:pressed { + background: #dbeeff; +} diff --git a/resources/startupHints/assets/common/PicturesCategory.svg b/resources/startupHints/assets/common/PicturesCategory.svg new file mode 100644 index 000000000..1132cadcb --- /dev/null +++ b/resources/startupHints/assets/common/PicturesCategory.svg @@ -0,0 +1,424 @@ + + + +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/startupHints/assets/common/board.png b/resources/startupHints/assets/common/board.png new file mode 100644 index 000000000..aca1a036c Binary files /dev/null and b/resources/startupHints/assets/common/board.png differ diff --git a/resources/startupHints/assets/common/eraserArrow.svg b/resources/startupHints/assets/common/eraserArrow.svg index 21e354551..e0a6354ab 100644 --- a/resources/startupHints/assets/common/eraserArrow.svg +++ b/resources/startupHints/assets/common/eraserArrow.svg @@ -1,199 +1,199 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/startupHints/assets/common/favorites.png b/resources/startupHints/assets/common/favorites.png new file mode 100644 index 000000000..59637b880 Binary files /dev/null and b/resources/startupHints/assets/common/favorites.png differ diff --git a/resources/startupHints/images/left.png b/resources/startupHints/assets/common/left.png similarity index 100% rename from resources/startupHints/images/left.png rename to resources/startupHints/assets/common/left.png diff --git a/resources/startupHints/assets/common/markerArrow.svg b/resources/startupHints/assets/common/markerArrow.svg index 0e2b9ad66..066a9e1b7 100644 --- a/resources/startupHints/assets/common/markerArrow.svg +++ b/resources/startupHints/assets/common/markerArrow.svg @@ -1,230 +1,230 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/startupHints/assets/common/penArrow.svg b/resources/startupHints/assets/common/penArrow.svg index 0ef25469b..27bc38296 100644 --- a/resources/startupHints/assets/common/penArrow.svg +++ b/resources/startupHints/assets/common/penArrow.svg @@ -1,286 +1,286 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/startupHints/assets/common/record.png b/resources/startupHints/assets/common/record.png new file mode 100644 index 000000000..f9903256c Binary files /dev/null and b/resources/startupHints/assets/common/record.png differ diff --git a/resources/startupHints/images/right.png b/resources/startupHints/assets/common/right.png similarity index 100% rename from resources/startupHints/images/right.png rename to resources/startupHints/assets/common/right.png diff --git a/resources/startupHints/assets/hint1/openboard-logo.png.bck b/resources/startupHints/assets/hint1/openboard-logo.png.bck deleted file mode 100644 index fe54ac431..000000000 Binary files a/resources/startupHints/assets/hint1/openboard-logo.png.bck and /dev/null differ diff --git a/resources/startupHints/assets/hint1/openboard-logo.png b/resources/startupHints/assets/hint1/openboard-main-logo.png similarity index 100% rename from resources/startupHints/assets/hint1/openboard-logo.png rename to resources/startupHints/assets/hint1/openboard-main-logo.png diff --git a/resources/startupHints/assets/hint1/menu.png b/resources/startupHints/assets/hint1/openboard-menu-icon.png similarity index 100% rename from resources/startupHints/assets/hint1/menu.png rename to resources/startupHints/assets/hint1/openboard-menu-icon.png diff --git a/resources/startupHints/assets/hint10/application-gif1.gif b/resources/startupHints/assets/hint10/built-in-applications-demo.gif similarity index 100% rename from resources/startupHints/assets/hint10/application-gif1.gif rename to resources/startupHints/assets/hint10/built-in-applications-demo.gif diff --git a/resources/startupHints/assets/hint11/podcast-gif1.gif b/resources/startupHints/assets/hint11/video-capture-open-tool.gif similarity index 100% rename from resources/startupHints/assets/hint11/podcast-gif1.gif rename to resources/startupHints/assets/hint11/video-capture-open-tool.gif diff --git a/resources/startupHints/assets/hint11/podcast-gif2.gif b/resources/startupHints/assets/hint11/video-capture-save-to-desktop.gif similarity index 100% rename from resources/startupHints/assets/hint11/podcast-gif2.gif rename to resources/startupHints/assets/hint11/video-capture-save-to-desktop.gif diff --git a/resources/startupHints/assets/hint11/podcast2.png b/resources/startupHints/assets/hint11/video-recorder-interface.png similarity index 100% rename from resources/startupHints/assets/hint11/podcast2.png rename to resources/startupHints/assets/hint11/video-recorder-interface.png diff --git a/resources/startupHints/assets/hint12/favorites-add-document.png b/resources/startupHints/assets/hint12/favorites-add-document.png new file mode 100644 index 000000000..fe65a67a3 Binary files /dev/null and b/resources/startupHints/assets/hint12/favorites-add-document.png differ diff --git a/resources/startupHints/assets/hint12/favorites2.png b/resources/startupHints/assets/hint12/favorites-library-view.png similarity index 100% rename from resources/startupHints/assets/hint12/favorites2.png rename to resources/startupHints/assets/hint12/favorites-library-view.png diff --git a/resources/startupHints/assets/hint12/favorites.png b/resources/startupHints/assets/hint12/favorites.png deleted file mode 100644 index 3a856ad8c..000000000 Binary files a/resources/startupHints/assets/hint12/favorites.png and /dev/null differ diff --git a/resources/startupHints/assets/hint12/favorites3.png b/resources/startupHints/assets/hint12/recent-documents-view.png similarity index 100% rename from resources/startupHints/assets/hint12/favorites3.png rename to resources/startupHints/assets/hint12/recent-documents-view.png diff --git a/resources/startupHints/assets/hint2/arrow.png b/resources/startupHints/assets/hint2/board-mode-arrow.png similarity index 100% rename from resources/startupHints/assets/hint2/arrow.png rename to resources/startupHints/assets/hint2/board-mode-arrow.png diff --git a/resources/startupHints/assets/hint2/bo-gif1.gif b/resources/startupHints/assets/hint2/board-mode-background-change.gif similarity index 100% rename from resources/startupHints/assets/hint2/bo-gif1.gif rename to resources/startupHints/assets/hint2/board-mode-background-change.gif diff --git a/resources/startupHints/assets/hint2/bo1.png b/resources/startupHints/assets/hint2/board-mode-pen-palette.png similarity index 100% rename from resources/startupHints/assets/hint2/bo1.png rename to resources/startupHints/assets/hint2/board-mode-pen-palette.png diff --git a/resources/startupHints/assets/hint2/bo9.png b/resources/startupHints/assets/hint2/board-mode-toolbar.png similarity index 100% rename from resources/startupHints/assets/hint2/bo9.png rename to resources/startupHints/assets/hint2/board-mode-toolbar.png diff --git a/resources/startupHints/assets/hint3/bureau1.png.bck b/resources/startupHints/assets/hint3/bureau1.png.bck deleted file mode 100644 index d8928d407..000000000 Binary files a/resources/startupHints/assets/hint3/bureau1.png.bck and /dev/null differ diff --git a/resources/startupHints/assets/hint3/bureau-gif1.gif b/resources/startupHints/assets/hint3/desktop-mode-annotation.gif similarity index 100% rename from resources/startupHints/assets/hint3/bureau-gif1.gif rename to resources/startupHints/assets/hint3/desktop-mode-annotation.gif diff --git a/resources/startupHints/assets/hint3/bureau1.png b/resources/startupHints/assets/hint3/desktop-mode-icon.png similarity index 100% rename from resources/startupHints/assets/hint3/bureau1.png rename to resources/startupHints/assets/hint3/desktop-mode-icon.png diff --git a/resources/startupHints/assets/hint3/bureau-gif2.gif b/resources/startupHints/assets/hint3/desktop-mode-navigation.gif similarity index 100% rename from resources/startupHints/assets/hint3/bureau-gif2.gif rename to resources/startupHints/assets/hint3/desktop-mode-navigation.gif diff --git a/resources/startupHints/assets/hint4/gif-document1.gif b/resources/startupHints/assets/hint4/documents-folder-organization.gif similarity index 100% rename from resources/startupHints/assets/hint4/gif-document1.gif rename to resources/startupHints/assets/hint4/documents-folder-organization.gif diff --git a/resources/startupHints/assets/hint4/document1.png b/resources/startupHints/assets/hint4/documents-manager-view.png similarity index 100% rename from resources/startupHints/assets/hint4/document1.png rename to resources/startupHints/assets/hint4/documents-manager-view.png diff --git a/resources/startupHints/assets/hint4/gif-document2.gif b/resources/startupHints/assets/hint4/documents-page-actions.gif similarity index 100% rename from resources/startupHints/assets/hint4/gif-document2.gif rename to resources/startupHints/assets/hint4/documents-page-actions.gif diff --git a/resources/startupHints/assets/hint5/gif2.gif b/resources/startupHints/assets/hint5/documents-export-files.gif similarity index 100% rename from resources/startupHints/assets/hint5/gif2.gif rename to resources/startupHints/assets/hint5/documents-export-files.gif diff --git a/resources/startupHints/assets/hint5/img1.png b/resources/startupHints/assets/hint5/documents-import-export-panel.png similarity index 100% rename from resources/startupHints/assets/hint5/img1.png rename to resources/startupHints/assets/hint5/documents-import-export-panel.png diff --git a/resources/startupHints/assets/hint5/gif1.gif b/resources/startupHints/assets/hint5/documents-import-files.gif similarity index 100% rename from resources/startupHints/assets/hint5/gif1.gif rename to resources/startupHints/assets/hint5/documents-import-files.gif diff --git a/resources/startupHints/assets/hint6/addToolToLibrary.png b/resources/startupHints/assets/hint6/add-tool-to-library-icon.png similarity index 100% rename from resources/startupHints/assets/hint6/addToolToLibrary.png rename to resources/startupHints/assets/hint6/add-tool-to-library-icon.png diff --git a/resources/startupHints/assets/hint6/app1.png b/resources/startupHints/assets/hint6/web-browser-integrated-view.png similarity index 100% rename from resources/startupHints/assets/hint6/app1.png rename to resources/startupHints/assets/hint6/web-browser-integrated-view.png diff --git a/resources/startupHints/assets/hint6/app-gif2.gif b/resources/startupHints/assets/hint6/web-create-library-app.gif similarity index 100% rename from resources/startupHints/assets/hint6/app-gif2.gif rename to resources/startupHints/assets/hint6/web-create-library-app.gif diff --git a/resources/startupHints/assets/hint6/app2.png b/resources/startupHints/assets/hint6/web-library-entry.png similarity index 100% rename from resources/startupHints/assets/hint6/app2.png rename to resources/startupHints/assets/hint6/web-library-entry.png diff --git a/resources/startupHints/assets/hint6/app-gif1.gif b/resources/startupHints/assets/hint6/web-open-website.gif similarity index 100% rename from resources/startupHints/assets/hint6/app-gif1.gif rename to resources/startupHints/assets/hint6/web-open-website.gif diff --git a/resources/startupHints/assets/hint6/app-gif3.gif b/resources/startupHints/assets/hint6/web-saved-in-library.gif similarity index 100% rename from resources/startupHints/assets/hint6/app-gif3.gif rename to resources/startupHints/assets/hint6/web-saved-in-library.gif diff --git a/resources/startupHints/assets/hint7/gif-capture1.gif b/resources/startupHints/assets/hint7/board-area-capture.gif similarity index 100% rename from resources/startupHints/assets/hint7/gif-capture1.gif rename to resources/startupHints/assets/hint7/board-area-capture.gif diff --git a/resources/startupHints/assets/hint7/caputre2.png b/resources/startupHints/assets/hint7/board-capture-area-selection.png similarity index 100% rename from resources/startupHints/assets/hint7/caputre2.png rename to resources/startupHints/assets/hint7/board-capture-area-selection.png diff --git a/resources/startupHints/assets/hint7/caputre3.png b/resources/startupHints/assets/hint7/board-capture-result.png similarity index 100% rename from resources/startupHints/assets/hint7/caputre3.png rename to resources/startupHints/assets/hint7/board-capture-result.png diff --git a/resources/startupHints/assets/hint7/capture1.png b/resources/startupHints/assets/hint7/board-capture-tool.png similarity index 100% rename from resources/startupHints/assets/hint7/capture1.png rename to resources/startupHints/assets/hint7/board-capture-tool.png diff --git a/resources/startupHints/assets/hint7/capture-gif2.gif b/resources/startupHints/assets/hint7/desktop-area-capture.gif similarity index 100% rename from resources/startupHints/assets/hint7/capture-gif2.gif rename to resources/startupHints/assets/hint7/desktop-area-capture.gif diff --git a/resources/startupHints/assets/hint7/capture4.png b/resources/startupHints/assets/hint7/desktop-capture-tool.png similarity index 100% rename from resources/startupHints/assets/hint7/capture4.png rename to resources/startupHints/assets/hint7/desktop-capture-tool.png diff --git a/resources/startupHints/assets/hint8/documents-import-export-zone.png b/resources/startupHints/assets/hint8/documents-import-export-zone.png new file mode 100644 index 000000000..10698d241 Binary files /dev/null and b/resources/startupHints/assets/hint8/documents-import-export-zone.png differ diff --git a/resources/startupHints/assets/hint8/capture2.png b/resources/startupHints/assets/hint8/image-library-folder-view.png similarity index 100% rename from resources/startupHints/assets/hint8/capture2.png rename to resources/startupHints/assets/hint8/image-library-folder-view.png diff --git a/resources/startupHints/assets/hint8/capture1.png b/resources/startupHints/assets/hint8/image-library-open.png similarity index 100% rename from resources/startupHints/assets/hint8/capture1.png rename to resources/startupHints/assets/hint8/image-library-open.png diff --git a/resources/startupHints/assets/hint8/image-gif1.gif b/resources/startupHints/assets/hint8/images-folder-organization.gif similarity index 100% rename from resources/startupHints/assets/hint8/image-gif1.gif rename to resources/startupHints/assets/hint8/images-folder-organization.gif diff --git a/resources/startupHints/assets/hint8/image-gif3.gif b/resources/startupHints/assets/hint8/images-local-import.gif similarity index 100% rename from resources/startupHints/assets/hint8/image-gif3.gif rename to resources/startupHints/assets/hint8/images-local-import.gif diff --git a/resources/startupHints/assets/hint8/image-gif2.gif b/resources/startupHints/assets/hint8/images-web-search-import.gif similarity index 100% rename from resources/startupHints/assets/hint8/image-gif2.gif rename to resources/startupHints/assets/hint8/images-web-search-import.gif diff --git a/resources/startupHints/assets/hint9/cat-pict.png b/resources/startupHints/assets/hint9/categorize-pictures-widget.png similarity index 100% rename from resources/startupHints/assets/hint9/cat-pict.png rename to resources/startupHints/assets/hint9/categorize-pictures-widget.png diff --git a/resources/startupHints/assets/hint9/interactivite-gif2.gif b/resources/startupHints/assets/hint9/interactivity-add-images.gif similarity index 100% rename from resources/startupHints/assets/hint9/interactivite-gif2.gif rename to resources/startupHints/assets/hint9/interactivity-add-images.gif diff --git a/resources/startupHints/assets/hint9/interactivite3.png b/resources/startupHints/assets/hint9/interactivity-category-editor.png similarity index 100% rename from resources/startupHints/assets/hint9/interactivite3.png rename to resources/startupHints/assets/hint9/interactivity-category-editor.png diff --git a/resources/startupHints/assets/hint9/interactivite-gif3.gif b/resources/startupHints/assets/hint9/interactivity-run-exercise.gif similarity index 100% rename from resources/startupHints/assets/hint9/interactivite-gif3.gif rename to resources/startupHints/assets/hint9/interactivity-run-exercise.gif diff --git a/resources/startupHints/assets/hint9/interactivite1.png b/resources/startupHints/assets/hint9/interactivity-widget-list.png similarity index 100% rename from resources/startupHints/assets/hint9/interactivite1.png rename to resources/startupHints/assets/hint9/interactivity-widget-list.png diff --git a/resources/startupHints/css/basic.css b/resources/startupHints/css/basic.css deleted file mode 100644 index e6b509beb..000000000 --- a/resources/startupHints/css/basic.css +++ /dev/null @@ -1,82 +0,0 @@ -html, body{ - width: 100%; - height: 100%; - margin: 0; - padding: 0; - font-family: sans-serif; - overflow: hidden; -} - -body{ - background-color: #aaaaaa; -} - -#main{ - background-color: #eeeeee; - width: 99%; - margin: 0px; - margin-left:1px; - padding: 0px; - height: 99%; - border-color:#999999; - border-width:2px; - border-radius:10px; - border-style:solid; -} - -#content{ - width: 100%; - margin: 0px; - padding: 0px; - height: 90%; -} - -iframe{ - border: none; - padding: 5px; -} - -#separator{ - width: 96%; - margin: 0 auto; - border: 2px solid #ccc; -} - -#controls{ - height: 30px; -} - -#navigation{ - margin-left: 10px; - height: 100%; - float: left; - margin-top: 5px; -} - -#left{ - width: 30px; - height: 30px; - background-image: url(../images/left.png); - cursor: pointer; - float: left; -} - -#right{ - width: 30px; - height: 30px; - background-image: url(../images/right.png); - cursor: pointer; - float: right; -} - -#title{ - width: 250px; - height: 100%; - float: left; - margin: 0 5px; - text-align: center; - vertical-align: middle; - display: table; - user-select: none; -} - diff --git a/resources/startupHints/css/dark.css b/resources/startupHints/css/dark.css new file mode 100644 index 000000000..159b40487 --- /dev/null +++ b/resources/startupHints/css/dark.css @@ -0,0 +1,50 @@ +:root, +:root[data-theme="dark"] { + /* Core colors */ + --bg: #1f2329; + --text: #e5e7eb; + --muted: #9ca3af; + --brand: #8ba3d7; + --brand-soft: #2a3038; + --surface: #2b313a; + --line: #4b5563; + --tip-bg: #26303c; + + /* Host and navigation shell */ + --host-content-bg: #1b2027; + --host-frame-bg: #1f2329; + --nav-shell-bg: rgba(35, 43, 54, 0.92); + --nav-shell-border: rgba(114, 139, 176, 0.42); + --nav-shell-shadow: 0 4px 12px rgba(2, 7, 17, 0.44), 0 1px 2px rgba(0, 0, 0, 0.34); + --nav-btn-border: rgba(122, 146, 185, 0.45); + --nav-btn-bg: #2a3442; + --nav-btn-shadow: 0 1px 2px rgba(0, 0, 0, 0.32); + --nav-btn-hover-border: rgba(150, 178, 221, 0.72); + --nav-btn-hover-shadow: 0 2px 6px rgba(8, 16, 34, 0.48); + --nav-btn-active-shadow: 0 1px 2px rgba(0, 0, 0, 0.45); + --nav-btn-focus-ring: 0 0 0 3px rgba(148, 186, 255, 0.3); + --nav-btn-focus-border: #98bfff; + --nav-title-color: #6682b5; + --nav-title-bg: rgba(33, 42, 54, 0.86); + --nav-title-border: rgba(136, 164, 207, 0.34); + --nav-divider: rgba(148, 175, 218, 0.36); + --nav-icon-filter: brightness(1.15) contrast(1.02); + --nav-icon-hover-filter: brightness(0) saturate(100%) invert(73%) sepia(41%) saturate(808%) hue-rotate(179deg) brightness(101%) contrast(99%); + + /* Scrollbars */ + --scrollbar-track: #2b2b2b; + --scrollbar-thumb: #ffffff; +} + +body { + background-color: var(--bg); + color: var(--text); +} + +#content { + background: var(--host-content-bg); +} + +#source { + background: var(--host-frame-bg); +} \ No newline at end of file diff --git a/resources/startupHints/css/light.css b/resources/startupHints/css/light.css new file mode 100644 index 000000000..65e95b1f1 --- /dev/null +++ b/resources/startupHints/css/light.css @@ -0,0 +1,50 @@ +:root, +:root[data-theme="light"] { + /* Core colors */ + --bg: #ffffff; + --text: #1f2937; + --muted: #6b7280; + --brand: #6682b5; + --brand-soft: #ffffff; + --surface: #ffffff; + --line: #e5e7eb; + --tip-bg: #f8fafc; + + /* Host and navigation shell */ + --host-content-bg: #fafafa; + --host-frame-bg: #ffffff; + --nav-shell-bg: rgba(246, 250, 255, 0.94); + --nav-shell-border: rgba(147, 175, 226, 0.44); + --nav-shell-shadow: 0 4px 12px rgba(27, 58, 114, 0.1), 0 1px 2px rgba(17, 24, 39, 0.08); + --nav-btn-border: rgba(145, 172, 223, 0.58); + --nav-btn-bg: #f5f8fe; + --nav-btn-shadow: 0 1px 2px rgba(31, 59, 102, 0.08); + --nav-btn-hover-border: rgba(66, 104, 172, 0.7); + --nav-btn-hover-shadow: 0 2px 6px rgba(33, 76, 149, 0.16); + --nav-btn-active-shadow: 0 1px 2px rgba(33, 76, 149, 0.18); + --nav-btn-focus-ring: 0 0 0 3px rgba(59, 130, 246, 0.26); + --nav-btn-focus-border: #2f5fb3; + --nav-title-color: #6682b5; + --nav-title-bg: rgba(255, 255, 255, 0.82); + --nav-title-border: rgba(139, 167, 214, 0.35); + --nav-divider: rgba(93, 125, 181, 0.34); + --nav-icon-filter: none; + --nav-icon-hover-filter: brightness(0) saturate(100%) invert(42%) sepia(31%) saturate(1014%) hue-rotate(181deg) brightness(90%) contrast(91%); + + /* Scrollbars (aligned with lightTheme.qss) */ + --scrollbar-track: transparent; + --scrollbar-thumb: #cdd7e3; +} + +body { + background-color: var(--bg); + color: var(--text); +} + +#content { + background: var(--host-content-bg); +} + +#source { + background: var(--host-frame-bg); +} \ No newline at end of file diff --git a/resources/startupHints/css/style.css b/resources/startupHints/css/style.css new file mode 100644 index 000000000..88b1b088c --- /dev/null +++ b/resources/startupHints/css/style.css @@ -0,0 +1,492 @@ +/* + Startup hints stylesheet + Scope: + - Host viewer (index.html) + - Hint page content rendered inside the iframe (all locales) +*/ + +/* ========================= + 1) Base and typography + ========================= */ +*, +*::before, +*::after { + box-sizing: border-box; +} + +html, +body { + margin: 0; + padding: 0; + background: var(--bg); + color: var(--text); + font-family: Arial, Helvetica, sans-serif; + line-height: 1.55; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; +} + +img { + max-width: 100%; + height: auto; +} + +/* Scrollbars follow OpenBoard theme colors (qss to update to harmonize all OpenBoard scrollbars) */ +html, +body, +#content, +#source { + scrollbar-width: auto; + scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track); +} + +*::-webkit-scrollbar { + width: 12px; + height: 12px; +} + +*::-webkit-scrollbar-track { + background: var(--scrollbar-track); +} + +*::-webkit-scrollbar-thumb { + background: var(--scrollbar-thumb); +} + +*::-webkit-scrollbar-corner { + background: var(--scrollbar-track); +} + +p { + margin: 0.5rem 0; +} + +/* ========================= + 2) Host viewer (index.html) + ========================= */ +#main:not(.container) { + min-height: 100vh; + height: 100dvh; + display: grid; + grid-template-rows: minmax(0, 1fr) auto; +} + +#content { + min-height: 0; + background: var(--host-content-bg); +} + +#source { + width: 100%; + height: 100%; + border: 0; + display: block; + background: var(--host-frame-bg); +} + +#controls { + margin: 0; + padding: 10px 12px calc(12px + env(safe-area-inset-bottom)); + display: flex; + align-items: center; + justify-content: center; + background: transparent; + border-top: 1px solid var(--line); +} + +#navigation { + display: grid; + grid-template-columns: 46px minmax(0, 1fr) 46px; + align-items: center; + gap: 8px; + width: min(400px, calc(100vw - 20px)); + padding: 0; +} + +#left, +#right { + width: 40px; + height: 40px; + border: 0; + border-radius: 8px; + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + user-select: none; + background: transparent; + box-shadow: none; + padding: 0; + opacity: 0.86; + transition: transform 0.14s ease, opacity 0.18s ease; +} + +.nav-icon { + width: 30px; + height: 30px; + display: block; + filter: var(--nav-icon-filter); + transition: transform 0.18s ease, filter 0.18s ease; +} + +#left:hover, +#right:hover { + transform: translateY(-1px); + opacity: 1; +} + +#left:hover .nav-icon, +#right:hover .nav-icon { + transform: scale(1.03); + filter: var(--nav-icon-hover-filter); +} + +#left:active, +#right:active { + transform: translateY(0); + opacity: 0.9; +} + +#left:active .nav-icon, +#right:active .nav-icon { + transform: scale(0.98); +} + +#left:focus-visible, +#right:focus-visible { + outline: none; + opacity: 1; + border-radius: 4px; + box-shadow: var(--nav-btn-focus-ring); +} + +#title { + min-width: 120px; + text-align: center; + font-size: 15px; + font-weight: 600; + letter-spacing: 0.08em; + color: var(--nav-title-color); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: flex; + align-items: center; + justify-content: center; + gap: 10px; +} + +#title::before, +#title::after { + content: ""; + flex: 1 1 auto; + max-width: 44px; + height: 1px; + background: var(--nav-divider); +} + +#title span { + display: inline-block; + padding: 0; + border: 0; + border-radius: 0; + background: transparent; + font-variant-numeric: tabular-nums; + text-transform: uppercase; +} + +@media (max-width: 520px) { + #navigation { + width: min(350px, calc(100vw - 12px)); + gap: 6px; + padding: 0; + } + + #left, + #right { + width: 34px; + height: 34px; + } + + .nav-icon { + width: 26px; + height: 26px; + } + + #title { + font-size: 13px; + gap: 8px; + } + + #title::before, + #title::after { + max-width: 28px; + } +} + +/* ========================= + 3) Hint page content (iframe) + ========================= */ +.container { + width: min(920px, 92vw); + margin: 0 auto; + padding: 16px; +} + +.page-header { + border-bottom: 1px solid var(--line); + background: var(--brand-soft); +} + +.page-footer { + background: var(--bg); +} + +.logo-title { + display: flex; + align-items: center; + justify-content: center; + gap: 20px; +} + +.title { + margin: 0; + text-align: center; + color: var(--brand); + font-size: clamp(1.4rem, 2.6vw, 2rem); + font-weight: 700; +} + +/* Keep a fixed header title size across FR hint pages (1-12). */ +html[lang="fr"] .title { + font-size: 1.8rem; +} + +#logo { + width: 36px; + height: 36px; +} + +#logo-small { + width: 48px; + height: 48px; +} + +.lead { + font-size: 1.05rem; + margin: 0.75rem 0 0.25rem; +} + +.h2 { + margin: 1rem 0 0.25rem; + font-size: clamp(1.05rem, 2.2vw, 1.25rem); + font-weight: 700; +} + +.modes { + margin-top: 0.75rem; +} + +.modes-grid { + list-style: none; + margin: 0.5rem 0 0; + padding: 0; + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 12px; +} + +.mode-card { + background: var(--surface); + border: 1px solid var(--line); + border-radius: 12px; + padding: 16px; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05); + transition: transform 0.15s ease, box-shadow 0.15s ease; +} + +.mode-card:hover { + transform: translateY(-2px); + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.08); +} + +.mode-title { + margin: 0 0 8px; + display: inline-flex; + align-items: center; + gap: 8px; + font-size: 1.05rem; + font-weight: 600; + color: var(--brand); +} + +.mode-link { + display: inline-flex; + align-items: center; + gap: 8px; + color: inherit; + text-decoration: none; +} + +.mode-link:focus-visible { + outline: 2px solid var(--brand); + outline-offset: 2px; +} + +.mode-card-link { + display: block; + margin: -16px; + padding: 16px; + border-radius: inherit; + color: inherit; + text-decoration: none; +} + +.mode-card-link:focus-visible { + outline: 2px solid var(--brand); + outline-offset: 2px; +} + +.mode-icon { + width: 20px; + height: 20px; + flex: 0 0 20px; +} + +.mode-text { + margin: 0; +} + +.start { + margin-top: 0.75rem; +} + +.image { + margin: 20px 0 12px; + text-align: center; +} + +.image figcaption { + margin-top: 8px; + font-size: 0.9rem; + color: var(--muted); +} + +.features { + list-style: none; + margin: 0.5rem 0 0; + padding: 0; +} + +.features li { + position: relative; + margin: 0.25rem 0; + padding-left: 1.1rem; +} + +.features li::before { + content: "•"; + position: absolute; + left: 0; + color: var(--brand); + line-height: 1.2; +} + +.file-extension { + font-family: Consolas, "Courier New", monospace; + font-size: 0.95em; + color: var(--brand); +} + +.non-clickable { + cursor: default; + text-decoration: underline dotted; +} + +.non-clickable:focus { + outline: none; +} + +.tip { + margin-top: 1.5rem; + padding: 10px 12px; + display: flex; + align-items: flex-start; + gap: 8px; + border: 1px dashed var(--brand); + border-radius: 10px; + background: var(--tip-bg); + font-size: 0.95rem; + line-height: 1.4; +} + +.tip p { + margin: 0; +} + +.tip-icon { + width: 24px; + height: 24px; + margin-top: 2px; +} + +.inline-icon { + vertical-align: -4px; + margin-left: 4px; +} + +/* Semantic screenshot sizing helpers used across hint pages. */ +.hint-shot, +.hint-shot--standard { + width: 100%; + max-width: min(360px, 92vw); + height: auto; +} + +.hint-shot--narrow { + width: 100%; + max-width: min(240px, 92vw); + height: auto; +} + +.hint-shot--wide { + width: 100%; + max-width: min(420px, 92vw); + height: auto; +} + +/* ========================= + 4) Responsive and accessibility + ========================= */ +@media (max-width: 640px) { + .modes-grid { + grid-template-columns: 1fr; + } +} + +@media (max-width: 480px) { + #navigation { + grid-template-columns: 30px minmax(120px, 1fr) 30px; + gap: 6px; + padding: 4px; + } + + #left, + #right { + width: 28px; + height: 28px; + } + + #title { + min-width: 120px; + font-size: 12px; + } +} + +@media (prefers-reduced-motion: reduce) { + .mode-card, + #left, + #right { + transition: none; + } +} diff --git a/resources/startupHints/index.html b/resources/startupHints/index.html index 6353c7c5a..ae7355224 100644 --- a/resources/startupHints/index.html +++ b/resources/startupHints/index.html @@ -1,102 +1,32 @@ - - - - Introduce - - - - - - - -
-
- -
-
-
- -
-
- - + + + + + + Startuphints + + + + + +
+
+ +
+
+ +
+
+ + + + + diff --git a/resources/startupHints/js/app.js b/resources/startupHints/js/app.js new file mode 100644 index 000000000..2f305c491 --- /dev/null +++ b/resources/startupHints/js/app.js @@ -0,0 +1,445 @@ +"use strict"; + +/* + Startup hints runtime model: + 1) This host page controls navigation and title (index shell). + 2) Host theme CSS is applied here (light.css / dark.css). + 3) Each iframe page applies its own theme through theme-loader.js + using the ?theme=... query param. +*/ + +// ─── Config ────────────────────────────────────────────────────────────────── + +var CONFIG = { + language: "en", + localesRoot: "locales", + maxHints: 50 +}; + +var DEBUG = new URLSearchParams(window.location.search).get("debug") === "1"; + +function debugLog() { + if (!DEBUG) return; + var args = Array.prototype.slice.call(arguments); + args.unshift("[startupHints]"); + console.log.apply(console, args); +} + +var THEME_SYNC_RETRY_INTERVAL_MS = 100; +var THEME_SYNC_MAX_ATTEMPTS = 40; + +// ─── State ─────────────────────────────────────────────────────────────────── + +var state = { + currentIndex: 1, + fileCount: 0, + theme: "light" +}; + +// ─── DOM cache ─────────────────────────────────────────────────────────────── + +var dom = { + source: null, + left: null, + right: null, + titleSpan: null +}; + +function cacheDomElements() { + dom.source = document.getElementById("source"); + dom.left = document.getElementById("left"); + dom.right = document.getElementById("right"); + dom.titleSpan = document.querySelector("#title span"); +} + +// ─── URL builders ──────────────────────────────────────────────────────────── + +function buildHintFileUrl(index) { + return CONFIG.localesRoot + "/" + CONFIG.language + "/" + index + ".html"; +} + +function buildHintUrl(index) { + return buildHintFileUrl(index) + "?theme=" + encodeURIComponent(state.theme); +} + +function buildErrorUrl() { + return CONFIG.localesRoot + "/" + CONFIG.language + "/error.html" + + "?theme=" + encodeURIComponent(state.theme); +} + +// ─── Language detection ───────────────────────────────────────────────────── + +function normalizeLanguage(value) { + if (typeof value !== "string") return null; + var trimmed = value.trim(); + if (!trimmed) return null; + + // Accept forms like "de", "de-DE", "de_DE" and keep the base language. + var base = trimmed.split(/[-_]/)[0].toLowerCase(); + return /^[a-z]{2,3}$/.test(base) ? base : null; +} + +function detectLanguageFromUrl() { + var params = new URLSearchParams(window.location.search); + return normalizeLanguage(params.get("lang") || params.get("language")); +} + +function detectLanguageFromSankore() { + if (!window.sankore) return null; + + try { + if (typeof window.sankore.lang === "function") { + return normalizeLanguage(window.sankore.lang()); + } + + return normalizeLanguage(window.sankore.lang); + } catch (e) { + return null; + } +} + +function getCurrentLanguage() { + return detectLanguageFromUrl() + || detectLanguageFromSankore() + || normalizeLanguage(CONFIG.language) + || "en"; +} + +function localeExists(language) { + return requestText(CONFIG.localesRoot + "/" + language + "/1.html") + .then(function() { return true; }) + .catch(function() { return false; }); +} + +async function resolveLanguage(preferredLanguage) { + var candidates = [ + preferredLanguage, + normalizeLanguage(CONFIG.language), + "en" + ].filter(Boolean); + + var seen = Object.create(null); + for (var i = 0; i < candidates.length; i++) { + var candidate = candidates[i]; + if (seen[candidate]) continue; + seen[candidate] = true; + + if (await localeExists(candidate)) return candidate; + } + + return "en"; +} + +// ─── Theme detection ───────────────────────────────────────────────────────── + +function normalizeTheme(value) { + if (value === "dark" || value === "light") return value; + return null; +} + +function detectThemeFromUrl() { + var params = new URLSearchParams(window.location.search); + return normalizeTheme(params.get("theme")); +} + +function detectInitialHintFromUrl() { + var params = new URLSearchParams(window.location.search); + var raw = params.get("hint"); + if (!raw) return null; + + var index = parseInt(raw, 10); + if (!Number.isFinite(index) || index < 1) return null; + return index; +} + +function resolveInitialHintIndex(fileCount) { + var requested = detectInitialHintFromUrl(); + if (!requested) return 1; + if (requested > fileCount) return fileCount; + return requested; +} + +function detectThemeFromSankore() { + if (window.sankore && typeof window.sankore.isDarkMode !== "undefined") { + return window.sankore.isDarkMode ? "dark" : "light"; + } + return null; +} + +function resolveTheme() { + return detectThemeFromUrl() || detectThemeFromSankore() || state.theme || "light"; +} + +// ─── Theme application ─────────────────────────────────────────────────────── + +// Host-only theme stylesheet. +// The iframe theme is handled by theme-loader.js. + +function applyHostThemeStylesheet(theme) { + var safeTheme = normalizeTheme(theme) || "light"; + var themeUrl = new URL("css/" + safeTheme + ".css", window.location.href).href; + var linkId = "ob-theme-css"; + var link = document.getElementById(linkId); + + if (!link) { + link = document.createElement("link"); + link.id = linkId; + link.rel = "stylesheet"; + link.type = "text/css"; + document.head.appendChild(link); + } + + if (link.href !== themeUrl) link.href = themeUrl; +} + +function applyHostTheme(theme) { + var safeTheme = normalizeTheme(theme) || "light"; + document.documentElement.setAttribute("data-theme", safeTheme); + applyHostThemeStylesheet(safeTheme); +} + +// Reload the current iframe page with the updated ?theme value. +function refreshCurrentHintTheme() { + if (!dom.source || state.fileCount < 1) return; + dom.source.src = buildHintUrl(state.currentIndex); +} + +// Sync host + iframe when OpenBoard theme changes. +function bindThemeSync() { + function connectThemeBridge() { + if (!window.sankore + || !window.sankore.themeChanged + || typeof window.sankore.themeChanged.connect !== "function") { + return false; + } + + window.sankore.themeChanged.connect(function(isDark) { + state.theme = isDark ? "dark" : "light"; + applyHostTheme(state.theme); + // The iframe theme is applied by theme-loader.js via ?theme=... + refreshCurrentHintTheme(); + }); + + var current = detectThemeFromSankore(); + if (current && current !== state.theme) { + state.theme = current; + applyHostTheme(state.theme); + } + + return true; + } + + if (connectThemeBridge()) return; + + var attempts = 0; + var timer = setInterval(function() { + if (connectThemeBridge() || ++attempts >= THEME_SYNC_MAX_ATTEMPTS) { + clearInterval(timer); + } + }, THEME_SYNC_RETRY_INTERVAL_MS); +} + +// ─── Navigation ────────────────────────────────────────────────────────────── + +function loadCurrentHint() { + if (!dom.source) return; + state.theme = resolveTheme(); + applyHostTheme(state.theme); + dom.source.src = buildHintUrl(state.currentIndex); + debugLog("loadHint", { + index: state.currentIndex, + fileCount: state.fileCount, + language: CONFIG.language, + theme: state.theme, + src: dom.source.src + }); +} + +function goToNextHint() { + if (state.fileCount < 1) return; + state.currentIndex = state.currentIndex < state.fileCount + ? state.currentIndex + 1 + : 1; + loadCurrentHint(); +} + +function goToPreviousHint() { + if (state.fileCount < 1) return; + state.currentIndex = state.currentIndex > 1 + ? state.currentIndex - 1 + : state.fileCount; + loadCurrentHint(); +} + +function goToHint(index) { + if (state.fileCount < 1) return; + + var target = parseInt(index, 10); + if (!Number.isFinite(target)) return; + + if (target < 1) target = 1; + if (target > state.fileCount) target = state.fileCount; + + state.currentIndex = target; + loadCurrentHint(); +} + +function bindNavigationHandlers() { + if (dom.right) dom.right.addEventListener("click", goToNextHint); + if (dom.left) dom.left.addEventListener("click", goToPreviousHint); + + document.addEventListener("keydown", function(e) { + if (e.key === "ArrowRight") goToNextHint(); + if (e.key === "ArrowLeft") goToPreviousHint(); + }); +} + +// ─── Iframe handlers ───────────────────────────────────────────────────────── + +function bindFrameHandlers() { + if (!dom.source) return; + dom.source.addEventListener("load", setTitle); + dom.source.addEventListener("load", function() { + debugLog("iframeLoaded", { + index: state.currentIndex, + src: dom.source ? dom.source.src : null + }); + }); +} + +// ─── Hint detection ────────────────────────────────────────────────────────── + +// Probes files sequentially until the first missing page is found. +async function detectAvailableHints() { + while (state.fileCount < CONFIG.maxHints) { + try { + await requestText(buildHintFileUrl(state.fileCount + 1)); + state.fileCount++; + } catch (e) { + break; + } + } + + if (state.fileCount === CONFIG.maxHints) { + console.warn("Hint detection reached maxHints limit:", CONFIG.maxHints); + } +} + +// ─── HTTP helper ───────────────────────────────────────────────────────────── + +// Tries fetch first, falls back to XHR for file:// contexts (OpenBoard). +function requestText(url) { + return fetch(url, { method: "GET", cache: "no-store" }) + .then(function(response) { + if (!response.ok) throw new Error("HTTP " + response.status); + return response.text().then(function(text) { + if (isMissingFileResponse(text)) throw new Error("Missing file"); + return text; + }); + }) + .catch(function() { + return new Promise(function(resolve, reject) { + var xhr = new XMLHttpRequest(); + xhr.open("GET", url, true); + xhr.onreadystatechange = function() { + if (xhr.readyState !== 4) return; + var ok = xhr.status >= 200 && xhr.status < 300; + var hasBody = typeof xhr.responseText === "string" && xhr.responseText.length > 0; + var local = xhr.status === 0 && hasBody && !isMissingFileResponse(xhr.responseText); + if (ok || local) resolve(xhr.responseText); + else reject(new Error("HTTP " + xhr.status)); + }; + xhr.onerror = function() { reject(new Error("Network error")); }; + xhr.send(); + }); + }); +} + +function isMissingFileResponse(text) { + if (typeof text !== "string") return true; + return text.indexOf("ERR_FILE_NOT_FOUND") !== -1 + || text.indexOf("404 Not Found") !== -1; +} + +// ─── Title ─────────────────────────────────────────────────────────────────── + +// Called on iframe load to refresh the center label. +function setTitle() { + if (!dom.source || !dom.titleSpan) cacheDomElements(); + if (!dom.source || !dom.titleSpan) return; + + try { + var doc = dom.source.contentWindow.document; + var titleEl = doc.getElementsByTagName("title")[0]; + var pageTitle = titleEl ? titleEl.innerHTML : ""; + dom.titleSpan.textContent = state.currentIndex + "/" + state.fileCount + + (pageTitle ? " : " + pageTitle : ""); + } catch (e) { + dom.titleSpan.textContent = state.currentIndex + "/" + state.fileCount; + } +} + +// ─── Init ──────────────────────────────────────────────────────────────────── + +async function init() { + debugLog("init:start", { + search: window.location.search, + sankorePresent: !!window.sankore + }); + + cacheDomElements(); + bindFrameHandlers(); + + var requestedLanguage = getCurrentLanguage(); + CONFIG.language = await resolveLanguage(requestedLanguage); + debugLog("language:resolved", { + requested: requestedLanguage, + selected: CONFIG.language + }); + + state.theme = resolveTheme(); + applyHostTheme(state.theme); + bindThemeSync(); + debugLog("theme:resolved", { theme: state.theme }); + + await detectAvailableHints(); + debugLog("hints:detected", { fileCount: state.fileCount }); + + if (state.fileCount > 0) { + state.currentIndex = resolveInitialHintIndex(state.fileCount); + loadCurrentHint(); + } else if (dom.source) { + dom.source.src = buildErrorUrl(); + debugLog("hints:none", { errorSrc: dom.source.src }); + } + + bindNavigationHandlers(); + + window.startupHints = window.startupHints || {}; + window.startupHints.goToHint = goToHint; + + debugLog("init:done"); +} + +if (DEBUG) { + window.addEventListener("error", function(event) { + debugLog("window:error", { + message: event.message, + source: event.filename, + line: event.lineno, + column: event.colno + }); + }); + + window.addEventListener("unhandledrejection", function(event) { + debugLog("window:unhandledrejection", { + reason: event.reason && event.reason.message ? event.reason.message : String(event.reason) + }); + }); +} + +document.addEventListener("DOMContentLoaded", function() { + init().catch(function(error) { + console.error("[startupHints] init failed", error); + }); +}); \ No newline at end of file diff --git a/resources/startupHints/js/jquery-1.6.2.min.js b/resources/startupHints/js/jquery-1.6.2.min.js deleted file mode 100644 index e67db7472..000000000 --- a/resources/startupHints/js/jquery-1.6.2.min.js +++ /dev/null @@ -1,18 +0,0 @@ -/*! - * jQuery JavaScript Library v1.6.2 - * http://jquery.com/ - * - * Copyright 2011, John Resig - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * Includes Sizzle.js - * http://sizzlejs.com/ - * Copyright 2011, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. - * - * Date: Thu Jun 30 14:16:56 2011 -0400 - */ -(function(a,b){function cv(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cs(a){if(!cg[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ch||(ch=c.createElement("iframe"),ch.frameBorder=ch.width=ch.height=0),b.appendChild(ch);if(!ci||!ch.createElement)ci=(ch.contentWindow||ch.contentDocument).document,ci.write((c.compatMode==="CSS1Compat"?"":"")+""),ci.close();d=ci.createElement(a),ci.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ch)}cg[a]=e}return cg[a]}function cr(a,b){var c={};f.each(cm.concat.apply([],cm.slice(0,b)),function(){c[this]=a});return c}function cq(){cn=b}function cp(){setTimeout(cq,0);return cn=f.now()}function cf(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ce(){try{return new a.XMLHttpRequest}catch(b){}}function b$(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){c!=="border"&&f.each(e,function(){c||(d-=parseFloat(f.css(a,"padding"+this))||0),c==="margin"?d+=parseFloat(f.css(a,c+this))||0:d-=parseFloat(f.css(a,"border"+this+"Width"))||0});return d+"px"}d=bx(a,b,b);if(d<0||d==null)d=a.style[b]||0;d=parseFloat(d)||0,c&&f.each(e,function(){d+=parseFloat(f.css(a,"padding"+this))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+this+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+this))||0)});return d+"px"}function bm(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(be,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bl(a){f.nodeName(a,"input")?bk(a):"getElementsByTagName"in a&&f.grep(a.getElementsByTagName("input"),bk)}function bk(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bj(a){return"getElementsByTagName"in a?a.getElementsByTagName("*"):"querySelectorAll"in a?a.querySelectorAll("*"):[]}function bi(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bh(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c=f.expando,d=f.data(a),e=f.data(b,d);if(d=d[c]){var g=d.events;e=e[c]=f.extend({},d);if(g){delete e.handle,e.events={};for(var h in g)for(var i=0,j=g[h].length;i=0===c})}function V(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function N(a,b){return(a&&a!=="*"?a+".":"")+b.replace(z,"`").replace(A,"&")}function M(a){var b,c,d,e,g,h,i,j,k,l,m,n,o,p=[],q=[],r=f._data(this,"events");if(!(a.liveFired===this||!r||!r.live||a.target.disabled||a.button&&a.type==="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var s=r.live.slice(0);for(i=0;ic)break;a.currentTarget=e.elem,a.data=e.handleObj.data,a.handleObj=e.handleObj,o=e.handleObj.origHandler.apply(e.elem,arguments);if(o===!1||a.isPropagationStopped()){c=e.level,o===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function K(a,c,d){var e=f.extend({},d[0]);e.type=a,e.originalEvent={},e.liveFired=b,f.event.handle.call(c,e),e.isDefaultPrevented()&&d[0].preventDefault()}function E(){return!0}function D(){return!1}function m(a,c,d){var e=c+"defer",g=c+"queue",h=c+"mark",i=f.data(a,e,b,!0);i&&(d==="queue"||!f.data(a,g,b,!0))&&(d==="mark"||!f.data(a,h,b,!0))&&setTimeout(function(){!f.data(a,g,b,!0)&&!f.data(a,h,b,!0)&&(f.removeData(a,e,!0),i.resolve())},0)}function l(a){for(var b in a)if(b!=="toJSON")return!1;return!0}function k(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(j,"$1-$2").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNaN(d)?i.test(d)?f.parseJSON(d):d:parseFloat(d)}catch(g){}f.data(a,c,d)}else d=b}return d}var c=a.document,d=a.navigator,e=a.location,f=function(){function J(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(J,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/\d/,n=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,o=/^[\],:{}\s]*$/,p=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,q=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,r=/(?:^|:|,)(?:\s*\[)+/g,s=/(webkit)[ \/]([\w.]+)/,t=/(opera)(?:.*version)?[ \/]([\w.]+)/,u=/(msie) ([\w.]+)/,v=/(mozilla)(?:.*? rv:([\w.]+))?/,w=/-([a-z])/ig,x=function(a,b){return b.toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=n.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.6.2",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.done(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;A.resolveWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!A){A=e._Deferred();if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNaN:function(a){return a==null||!m.test(a)||isNaN(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1;var c;for(c in a);return c===b||D.call(a,c)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(o.test(b.replace(p,"@").replace(q,"]").replace(r,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(b,c,d){a.DOMParser?(d=new DOMParser,c=d.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b)),d=c.documentElement,(!d||!d.nodeName||d.nodeName==="parsererror")&&e.error("Invalid XML: "+b);return c},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?h.call(arguments,0):c,--e||g.resolveWith(g,h.call(b,0))}}var b=arguments,c=0,d=b.length,e=d,g=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred();if(d>1){for(;c
a",d=a.getElementsByTagName("*"),e=a.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=a.getElementsByTagName("input")[0],k={leadingWhitespace:a.firstChild.nodeType===3,tbody:!a.getElementsByTagName("tbody").length,htmlSerialize:!!a.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55$/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:a.className!=="t",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,k.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,k.optDisabled=!h.disabled;try{delete a.test}catch(v){k.deleteExpando=!1}!a.addEventListener&&a.attachEvent&&a.fireEvent&&(a.attachEvent("onclick",function(){k.noCloneEvent=!1}),a.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),k.radioValue=i.value==="t",i.setAttribute("checked","checked"),a.appendChild(i),l=c.createDocumentFragment(),l.appendChild(a.firstChild),k.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,a.innerHTML="",a.style.width=a.style.paddingLeft="1px",m=c.getElementsByTagName("body")[0],o=c.createElement(m?"div":"body"),p={visibility:"hidden",width:0,height:0,border:0,margin:0},m&&f.extend(p,{position:"absolute",left:-1e3,top:-1e3});for(t in p)o.style[t]=p[t];o.appendChild(a),n=m||b,n.insertBefore(o,n.firstChild),k.appendChecked=i.checked,k.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,k.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="
",k.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="
t
",q=a.getElementsByTagName("td"),u=q[0].offsetHeight===0,q[0].style.display="",q[1].style.display="none",k.reliableHiddenOffsets=u&&q[0].offsetHeight===0,a.innerHTML="",c.defaultView&&c.defaultView.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",a.appendChild(j),k.reliableMarginRight=(parseInt((c.defaultView.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0),o.innerHTML="",n.removeChild(o);if(a.attachEvent)for(t in{submit:1,change:1,focusin:1})s="on"+t,u=s in a,u||(a.setAttribute(s,"return;"),u=typeof a[s]=="function"),k[t+"Bubbles"]=u;o=l=g=h=m=j=a=i=null;return k}(),f.boxModel=f.support.boxModel;var i=/^(?:\{.*\}|\[.*\])$/,j=/([a-z])([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!l(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g=f.expando,h=typeof c=="string",i,j=a.nodeType,k=j?f.cache:a,l=j?a[f.expando]:a[f.expando]&&f.expando;if((!l||e&&l&&!k[l][g])&&h&&d===b)return;l||(j?a[f.expando]=l=++f.uuid:l=f.expando),k[l]||(k[l]={},j||(k[l].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?k[l][g]=f.extend(k[l][g],c):k[l]=f.extend(k[l],c);i=k[l],e&&(i[g]||(i[g]={}),i=i[g]),d!==b&&(i[f.camelCase(c)]=d);if(c==="events"&&!i[c])return i[g]&&i[g].events;return h?i[f.camelCase(c)]||i[c]:i}},removeData:function(b,c,d){if(!!f.acceptData(b)){var e=f.expando,g=b.nodeType,h=g?f.cache:b,i=g?b[f.expando]:f.expando;if(!h[i])return;if(c){var j=d?h[i][e]:h[i];if(j){delete j[c];if(!l(j))return}}if(d){delete h[i][e];if(!l(h[i]))return}var k=h[i][e];f.support.deleteExpando||h!=a?delete h[i]:h[i]=null,k?(h[i]={},g||(h[i].toJSON=f.noop),h[i][e]=k):g&&(f.support.deleteExpando?delete b[f.expando]:b.removeAttribute?b.removeAttribute(f.expando):b[f.expando]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d=null;if(typeof a=="undefined"){if(this.length){d=f.data(this[0]);if(this[0].nodeType===1){var e=this[0].attributes,g;for(var h=0,i=e.length;h-1)return!0;return!1},val:function(a){var c,d,e=this[0];if(!arguments.length){if(e){c=f.valHooks[e.nodeName.toLowerCase()]||f.valHooks[e.type];if(c&&"get"in c&&(d=c.get(e,"value"))!==b)return d;d=e.value;return typeof d=="string"?d.replace(p,""):d==null?"":d}return b}var g=f.isFunction(a);return this.each(function(d){var e=f(this),h;if(this.nodeType===1){g?h=a.call(this,d,e.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c=a.selectedIndex,d=[],e=a.options,g=a.type==="select-one";if(c<0)return null;for(var h=g?c:0,i=g?c+1:e.length;h=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attrFix:{tabindex:"tabIndex"},attr:function(a,c,d,e){var g=a.nodeType;if(!a||g===3||g===8||g===2)return b;if(e&&c in f.attrFn)return f(a)[c](d);if(!("getAttribute"in a))return f.prop(a,c,d);var h,i,j=g!==1||!f.isXMLDoc(a);j&&(c=f.attrFix[c]||c,i=f.attrHooks[c],i||(t.test(c)?i=w:v&&c!=="className"&&(f.nodeName(a,"form")||u.test(c))&&(i=v)));if(d!==b){if(d===null){f.removeAttr(a,c);return b}if(i&&"set"in i&&j&&(h=i.set(a,d,c))!==b)return h;a.setAttribute(c,""+d);return d}if(i&&"get"in i&&j&&(h=i.get(a,c))!==null)return h;h=a.getAttribute(c);return h===null?b:h},removeAttr:function(a,b){var c;a.nodeType===1&&(b=f.attrFix[b]||b,f.support.getSetAttribute?a.removeAttribute(b):(f.attr(a,b,""),a.removeAttributeNode(a.getAttributeNode(b))),t.test(b)&&(c=f.propFix[b]||b)in a&&(a[c]=!1))},attrHooks:{type:{set:function(a,b){if(q.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},tabIndex:{get:function(a){var c=a.getAttributeNode("tabIndex");return c&&c.specified?parseInt(c.value,10):r.test(a.nodeName)||s.test(a.nodeName)&&a.href?0:b}},value:{get:function(a,b){if(v&&f.nodeName(a,"button"))return v.get(a,b);return b in a?a.value:null},set:function(a,b,c){if(v&&f.nodeName(a,"button"))return v.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e=a.nodeType;if(!a||e===3||e===8||e===2)return b;var g,h,i=e!==1||!f.isXMLDoc(a);i&&(c=f.propFix[c]||c,h=f.propHooks[c]);return d!==b?h&&"set"in h&&(g=h.set(a,d,c))!==b?g:a[c]=d:h&&"get"in h&&(g=h.get(a,c))!==b?g:a[c]},propHooks:{}}),w={get:function(a,c){return f.prop(a,c)?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},f.support.getSetAttribute||(f.attrFix=f.propFix,v=f.attrHooks.name=f.attrHooks.title=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&d.nodeValue!==""?d.nodeValue:b},set:function(a,b,c){var d=a.getAttributeNode(c);if(d){d.nodeValue=b;return b}}},f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})})),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}})),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var x=/\.(.*)$/,y=/^(?:textarea|input|select)$/i,z=/\./g,A=/ /g,B=/[^\w\s.|`]/g,C=function(a){return a.replace(B,"\\$&")};f.event={add:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){if(d===!1)d=D;else if(!d)return;var g,h;d.handler&&(g=d,d=g.handler),d.guid||(d.guid=f.guid++);var i=f._data(a);if(!i)return;var j=i.events,k=i.handle;j||(i.events=j={}),k||(i.handle=k=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.handle.apply(k.elem,arguments):b}),k.elem=a,c=c.split(" ");var l,m=0,n;while(l=c[m++]){h=g?f.extend({},g):{handler:d,data:e},l.indexOf(".")>-1?(n=l.split("."),l=n.shift(),h.namespace=n.slice(0).sort().join(".")):(n=[],h.namespace=""),h.type=l,h.guid||(h.guid=d.guid);var o=j[l],p=f.event.special[l]||{};if(!o){o=j[l]=[];if(!p.setup||p.setup.call(a,e,n,k)===!1)a.addEventListener?a.addEventListener(l,k,!1):a.attachEvent&&a.attachEvent("on"+l,k)}p.add&&(p.add.call(a,h),h.handler.guid||(h.handler.guid=d.guid)),o.push(h),f.event.global[l]=!0}a=null}},global:{},remove:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){d===!1&&(d=D);var g,h,i,j,k=0,l,m,n,o,p,q,r,s=f.hasData(a)&&f._data(a),t=s&&s.events;if(!s||!t)return;c&&c.type&&(d=c.handler,c=c.type);if(!c||typeof c=="string"&&c.charAt(0)==="."){c=c||"";for(h in t)f.event.remove(a,h+c);return}c=c.split(" ");while(h=c[k++]){r=h,q=null,l=h.indexOf(".")<0,m=[],l||(m=h.split("."),h=m.shift(),n=new RegExp("(^|\\.)"+f.map(m.slice(0).sort(),C).join("\\.(?:.*\\.)?")+"(\\.|$)")),p=t[h];if(!p)continue;if(!d){for(j=0;j=0&&(h=h.slice(0,-1),j=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i. -shift(),i.sort());if(!!e&&!f.event.customEvent[h]||!!f.event.global[h]){c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.exclusive=j,c.namespace=i.join("."),c.namespace_re=new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)");if(g||!e)c.preventDefault(),c.stopPropagation();if(!e){f.each(f.cache,function(){var a=f.expando,b=this[a];b&&b.events&&b.events[h]&&f.event.trigger(c,d,b.handle.elem)});return}if(e.nodeType===3||e.nodeType===8)return;c.result=b,c.target=e,d=d!=null?f.makeArray(d):[],d.unshift(c);var k=e,l=h.indexOf(":")<0?"on"+h:"";do{var m=f._data(k,"handle");c.currentTarget=k,m&&m.apply(k,d),l&&f.acceptData(k)&&k[l]&&k[l].apply(k,d)===!1&&(c.result=!1,c.preventDefault()),k=k.parentNode||k.ownerDocument||k===c.target.ownerDocument&&a}while(k&&!c.isPropagationStopped());if(!c.isDefaultPrevented()){var n,o=f.event.special[h]||{};if((!o._default||o._default.call(e.ownerDocument,c)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)){try{l&&e[h]&&(n=e[l],n&&(e[l]=null),f.event.triggered=h,e[h]())}catch(p){}n&&(e[l]=n),f.event.triggered=b}}return c.result}},handle:function(c){c=f.event.fix(c||a.event);var d=((f._data(this,"events")||{})[c.type]||[]).slice(0),e=!c.exclusive&&!c.namespace,g=Array.prototype.slice.call(arguments,0);g[0]=c,c.currentTarget=this;for(var h=0,i=d.length;h-1?f.map(a.options,function(a){return a.selected}).join("-"):"":f.nodeName(a,"select")&&(c=a.selectedIndex);return c},J=function(c){var d=c.target,e,g;if(!!y.test(d.nodeName)&&!d.readOnly){e=f._data(d,"_change_data"),g=I(d),(c.type!=="focusout"||d.type!=="radio")&&f._data(d,"_change_data",g);if(e===b||g===e)return;if(e!=null||g)c.type="change",c.liveFired=b,f.event.trigger(c,arguments[1],d)}};f.event.special.change={filters:{focusout:J,beforedeactivate:J,click:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(c==="radio"||c==="checkbox"||f.nodeName(b,"select"))&&J.call(this,a)},keydown:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(a.keyCode===13&&!f.nodeName(b,"textarea")||a.keyCode===32&&(c==="checkbox"||c==="radio")||c==="select-multiple")&&J.call(this,a)},beforeactivate:function(a){var b=a.target;f._data(b,"_change_data",I(b))}},setup:function(a,b){if(this.type==="file")return!1;for(var c in H)f.event.add(this,c+".specialChange",H[c]);return y.test(this.nodeName)},teardown:function(a){f.event.remove(this,".specialChange");return y.test(this.nodeName)}},H=f.event.special.change.filters,H.focus=H.beforeactivate}f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){function e(a){var c=f.event.fix(a);c.type=b,c.originalEvent={},f.event.trigger(c,null,c.target),c.isDefaultPrevented()&&a.preventDefault()}var d=0;f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.each(["bind","one"],function(a,c){f.fn[c]=function(a,d,e){var g;if(typeof a=="object"){for(var h in a)this[c](h,d,a[h],e);return this}if(arguments.length===2||d===!1)e=d,d=b;c==="one"?(g=function(a){f(this).unbind(a,g);return e.apply(this,arguments)},g.guid=e.guid||f.guid++):g=e;if(a==="unload"&&c!=="one")this.one(a,d,e);else for(var i=0,j=this.length;i0?this.bind(b,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0)}),function(){function u(a,b,c,d,e,f){for(var g=0,h=d.length;g0){j=i;break}}i=i[a]}d[g]=j}}}function t(a,b,c,d,e,f){for(var g=0,h=d.length;g+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d=0,e=Object.prototype.toString,g=!1,h=!0,i=/\\/g,j=/\W/;[0,0].sort(function(){h=!1;return 0});var k=function(b,d,f,g){f=f||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return f;var i,j,n,o,q,r,s,t,u=!0,w=k.isXML(d),x=[],y=b;do{a.exec(""),i=a.exec(y);if(i){y=i[3],x.push(i[1]);if(i[2]){o=i[3];break}}}while(i);if(x.length>1&&m.exec(b))if(x.length===2&&l.relative[x[0]])j=v(x[0]+x[1],d);else{j=l.relative[x[0]]?[d]:k(x.shift(),d);while(x.length)b=x.shift(),l.relative[b]&&(b+=x.shift()),j=v(b,j)}else{!g&&x.length>1&&d.nodeType===9&&!w&&l.match.ID.test(x[0])&&!l.match.ID.test(x[x.length-1])&&(q=k.find(x.shift(),d,w),d=q.expr?k.filter(q.expr,q.set)[0]:q.set[0]);if(d){q=g?{expr:x.pop(),set:p(g)}:k.find(x.pop(),x.length===1&&(x[0]==="~"||x[0]==="+")&&d.parentNode?d.parentNode:d,w),j=q.expr?k.filter(q.expr,q.set):q.set,x.length>0?n=p(j):u=!1;while(x.length)r=x.pop(),s=r,l.relative[r]?s=x.pop():r="",s==null&&(s=d),l.relative[r](n,s,w)}else n=x=[]}n||(n=j),n||k.error(r||b);if(e.call(n)==="[object Array]")if(!u)f.push.apply(f,n);else if(d&&d.nodeType===1)for(t=0;n[t]!=null;t++)n[t]&&(n[t]===!0||n[t].nodeType===1&&k.contains(d,n[t]))&&f.push(j[t]);else for(t=0;n[t]!=null;t++)n[t]&&n[t].nodeType===1&&f.push(j[t]);else p(n,f);o&&(k(o,h,f,g),k.uniqueSort(f));return f};k.uniqueSort=function(a){if(r){g=h,a.sort(r);if(g)for(var b=1;b0},k.find=function(a,b,c){var d;if(!a)return[];for(var e=0,f=l.order.length;e":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!j.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(i,"")},TAG:function(a,b){return a[1].replace(i,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||k.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&k.error(a[0]);a[0]=d++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(i,"");!f&&l.attrMap[g]&&(a[1]=l.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(i,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=k(b[3],null,null,c);else{var g=k.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(l.match.POS.test(b[0])||l.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!k(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=l.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||k.getText([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=l.attrHandle[c]?l.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=l.setFilters[e];if(f)return f(a,c,b,d)}}},m=l.match.POS,n=function(a,b){return"\\"+(b-0+1)};for(var o in l.match)l.match[o]=new RegExp(l.match[o].source+/(?![^\[]*\])(?![^\(]*\))/.source),l.leftMatch[o]=new RegExp(/(^(?:.|\r|\n)*?)/.source+l.match[o].source.replace(/\\(\d+)/g,n));var p=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(q){p=function(a,b){var c=0,d=b||[];if(e.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var f=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(l.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},l.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(l.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(l.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=k,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){k=function(b,e,f,g){e=e||c;if(!g&&!k.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return p(e.getElementsByTagName(b),f);if(h[2]&&l.find.CLASS&&e.getElementsByClassName)return p(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return p([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return p([],f);if(i.id===h[3])return p([i],f)}try{return p(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var m=e,n=e.getAttribute("id"),o=n||d,q=e.parentNode,r=/^\s*[+~]/.test(b);n?o=o.replace(/'/g,"\\$&"):e.setAttribute("id",o),r&&q&&(e=e.parentNode);try{if(!r||q)return p(e.querySelectorAll("[id='"+o+"'] "+b),f)}catch(s){}finally{n||m.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)k[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}k.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!k.isXML(a))try{if(e||!l.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return k(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="
";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;l.order.splice(1,0,"CLASS"),l.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?k.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?k.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:k.contains=function(){return!1},k.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var v=function(a,b){var c,d=[],e="",f=b.nodeType?[b]:b;while(c=l.match.PSEUDO.exec(a))e+=c[0],a=a.replace(l.match.PSEUDO,"");a=l.relative[a]?a+"*":a;for(var g=0,h=f.length;g0)for(h=g;h0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h,i,j={},k=1;if(g&&a.length){for(d=0,e=a.length;d-1:f(g).is(h))&&c.push({selector:i,elem:g,level:k});g=g.parentNode,k++}}return c}var l=T.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a||typeof a=="string")return f.inArray(this[0],a?f(a):this.parent().children());return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(V(c[0])||V(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c),g=S.call(arguments);O.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!U[a]?f.unique(e):e,(this.length>1||Q.test(d))&&P.test(a)&&(e=e.reverse());return this.pushStack(e,a,g.join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var X=/ jQuery\d+="(?:\d+|null)"/g,Y=/^\s+/,Z=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,$=/<([\w:]+)/,_=/",""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]};bf.optgroup=bf.option,bf.tbody=bf.tfoot=bf.colgroup=bf.caption=bf.thead,bf.th=bf.td,f.support.htmlSerialize||(bf._default=[1,"div
","
"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){f(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f(arguments[0]).toArray());return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(X,""):null;if(typeof a=="string"&&!bb.test(a)&&(f.support.leadingWhitespace||!Y.test(a))&&!bf[($.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Z,"<$1>");try{for(var c=0,d=this.length;c1&&l0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j -)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d=a.cloneNode(!0),e,g,h;if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bi(a,d),e=bj(a),g=bj(d);for(h=0;e[h];++h)bi(e[h],g[h])}if(b){bh(a,d);if(c){e=bj(a),g=bj(d);for(h=0;e[h];++h)bh(e[h],g[h])}}e=g=null;return d},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!ba.test(k))k=b.createTextNode(k);else{k=k.replace(Z,"<$1>");var l=($.exec(k)||["",""])[1].toLowerCase(),m=bf[l]||bf._default,n=m[0],o=b.createElement("div");o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=_.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]===""&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&Y.test(k)&&o.insertBefore(b.createTextNode(Y.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bo.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle;c.zoom=1;var e=f.isNaN(b)?"":"alpha(opacity="+b*100+")",g=d&&d.filter||c.filter||"";c.filter=bn.test(g)?g.replace(bn,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bx(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(by=function(a,c){var d,e,g;c=c.replace(bp,"-$1").toLowerCase();if(!(e=a.ownerDocument.defaultView))return b;if(g=e.getComputedStyle(a,null))d=g.getPropertyValue(c),d===""&&!f.contains(a.ownerDocument.documentElement,a)&&(d=f.style(a,c));return d}),c.documentElement.currentStyle&&(bz=function(a,b){var c,d=a.currentStyle&&a.currentStyle[b],e=a.runtimeStyle&&a.runtimeStyle[b],f=a.style;!bq.test(d)&&br.test(d)&&(c=f.left,e&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":d||0,d=f.pixelLeft+"px",f.left=c,e&&(a.runtimeStyle.left=e));return d===""?"auto":d}),bx=by||bz,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bB=/%20/g,bC=/\[\]$/,bD=/\r?\n/g,bE=/#.*$/,bF=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bG=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bH=/^(?:about|app|app\-storage|.+\-extension|file|widget):$/,bI=/^(?:GET|HEAD)$/,bJ=/^\/\//,bK=/\?/,bL=/)<[^<]*)*<\/script>/gi,bM=/^(?:select|textarea)/i,bN=/\s+/,bO=/([?&])_=[^&]*/,bP=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bQ=f.fn.load,bR={},bS={},bT,bU;try{bT=e.href}catch(bV){bT=c.createElement("a"),bT.href="",bT=bT.href}bU=bP.exec(bT.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bQ)return bQ.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("
").append(c.replace(bL,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bM.test(this.nodeName)||bG.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bD,"\r\n")}}):{name:b.name,value:c.replace(bD,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.bind(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?f.extend(!0,a,f.ajaxSettings,b):(b=a,a=f.extend(!0,f.ajaxSettings,b));for(var c in{context:1,url:1})c in b?a[c]=b[c]:c in f.ajaxSettings&&(a[c]=f.ajaxSettings[c]);return a},ajaxSettings:{url:bT,isLocal:bH.test(bU[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":"*/*"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML}},ajaxPrefilter:bW(bR),ajaxTransport:bW(bS),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a?4:0;var o,r,u,w=l?bZ(d,v,l):b,x,y;if(a>=200&&a<300||a===304){if(d.ifModified){if(x=v.getResponseHeader("Last-Modified"))f.lastModified[k]=x;if(y=v.getResponseHeader("Etag"))f.etag[k]=y}if(a===304)c="notmodified",o=!0;else try{r=b$(d,w),c="success",o=!0}catch(z){c="parsererror",u=z}}else{u=c;if(!c||a)c="error",a<0&&(a=0)}v.status=a,v.statusText=c,o?h.resolveWith(e,[r,c,v]):h.rejectWith(e,[v,c,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.resolveWith(e,[v,c]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f._Deferred(),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bF.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.done,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bE,"").replace(bJ,bU[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bN),d.crossDomain==null&&(r=bP.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bU[1]&&r[2]==bU[2]&&(r[3]||(r[1]==="http:"?80:443))==(bU[3]||(bU[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bX(bR,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bI.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bK.test(d.url)?"&":"?")+d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bO,"$1_="+x);d.url=y+(y===d.url?(bK.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", */*; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bX(bS,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){status<2?w(-1,z):f.error(z)}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)bY(g,a[g],c,e);return d.join("&").replace(bB,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var b_=f.now(),ca=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+b_++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ca.test(b.url)||e&&ca.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ca,l),b.url===j&&(e&&(k=k.replace(ca,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cb=a.ActiveXObject?function(){for(var a in cd)cd[a](0,1)}:!1,cc=0,cd;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ce()||cf()}:ce,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cb&&delete cd[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cc,cb&&(cd||(cd={},f(a).unload(cb)),cd[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cg={},ch,ci,cj=/^(?:toggle|show|hide)$/,ck=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cl,cm=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cn,co=a.webkitRequestAnimationFrame||a.mozRequestAnimationFrame||a.oRequestAnimationFrame;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cr("show",3),a,b,c);for(var g=0,h=this.length;g=e.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),e.animatedProperties[this.prop]=!0;for(g in e.animatedProperties)e.animatedProperties[g]!==!0&&(c=!1);if(c){e.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){d.style["overflow"+b]=e.overflow[a]}),e.hide&&f(d).hide();if(e.hide||e.show)for(var i in e.animatedProperties)f.style(d,i,e.orig[i]);e.complete.call(d)}return!1}e.duration==Infinity?this.now=b:(h=b-this.startTime,this.state=h/e.duration,this.pos=f.easing[e.animatedProperties[this.prop]](this.state,h,0,1,e.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){for(var a=f.timers,b=0;b
";f.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"}),b.innerHTML=j,a.insertBefore(b,a.firstChild),d=b.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,this.doesNotAddBorder=e.offsetTop!==5,this.doesAddBorderForTableAndCells=h.offsetTop===5,e.style.position="fixed",e.style.top="20px",this.supportsFixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",this.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==i,a.removeChild(b),f.offset.initialize=f.noop},bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.offset.initialize(),f.offset.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cu.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cu.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cv(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cv(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a&&a.style?parseFloat(f.css(a,d,"padding")):null},f.fn["outer"+c]=function(a){var b=this[0];return b&&b.style?parseFloat(f.css(b,d,a?"margin":"border")):null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c];return e.document.compatMode==="CSS1Compat"&&g||e.document.body["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var h=f.css(e,d),i=parseFloat(h);return f.isNaN(i)?h:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f})(window); \ No newline at end of file diff --git a/resources/startupHints/js/jquery-ui-1.8.9.custom.min.js b/resources/startupHints/js/jquery-ui-1.8.9.custom.min.js deleted file mode 100644 index 145ba1fef..000000000 --- a/resources/startupHints/js/jquery-ui-1.8.9.custom.min.js +++ /dev/null @@ -1,781 +0,0 @@ -/*! - * jQuery UI 1.8.9 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI - */ -(function(c,j){function k(a){return!c(a).parents().andSelf().filter(function(){return c.curCSS(this,"visibility")==="hidden"||c.expr.filters.hidden(this)}).length}c.ui=c.ui||{};if(!c.ui.version){c.extend(c.ui,{version:"1.8.9",keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106, -NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}});c.fn.extend({_focus:c.fn.focus,focus:function(a,b){return typeof a==="number"?this.each(function(){var d=this;setTimeout(function(){c(d).focus();b&&b.call(d)},a)}):this._focus.apply(this,arguments)},scrollParent:function(){var a;a=c.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(c.curCSS(this, -"position",1))&&/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0);return/fixed/.test(this.css("position"))||!a.length?c(document):a},zIndex:function(a){if(a!==j)return this.css("zIndex",a);if(this.length){a=c(this[0]);for(var b;a.length&&a[0]!==document;){b=a.css("position"); -if(b==="absolute"||b==="relative"||b==="fixed"){b=parseInt(a.css("zIndex"),10);if(!isNaN(b)&&b!==0)return b}a=a.parent()}}return 0},disableSelection:function(){return this.bind((c.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}});c.each(["Width","Height"],function(a,b){function d(f,g,l,m){c.each(e,function(){g-=parseFloat(c.curCSS(f,"padding"+this,true))||0;if(l)g-=parseFloat(c.curCSS(f, -"border"+this+"Width",true))||0;if(m)g-=parseFloat(c.curCSS(f,"margin"+this,true))||0});return g}var e=b==="Width"?["Left","Right"]:["Top","Bottom"],h=b.toLowerCase(),i={innerWidth:c.fn.innerWidth,innerHeight:c.fn.innerHeight,outerWidth:c.fn.outerWidth,outerHeight:c.fn.outerHeight};c.fn["inner"+b]=function(f){if(f===j)return i["inner"+b].call(this);return this.each(function(){c(this).css(h,d(this,f)+"px")})};c.fn["outer"+b]=function(f,g){if(typeof f!=="number")return i["outer"+b].call(this,f);return this.each(function(){c(this).css(h, -d(this,f,true,g)+"px")})}});c.extend(c.expr[":"],{data:function(a,b,d){return!!c.data(a,d[3])},focusable:function(a){var b=a.nodeName.toLowerCase(),d=c.attr(a,"tabindex");if("area"===b){b=a.parentNode;d=b.name;if(!a.href||!d||b.nodeName.toLowerCase()!=="map")return false;a=c("img[usemap=#"+d+"]")[0];return!!a&&k(a)}return(/input|select|textarea|button|object/.test(b)?!a.disabled:"a"==b?a.href||!isNaN(d):!isNaN(d))&&k(a)},tabbable:function(a){var b=c.attr(a,"tabindex");return(isNaN(b)||b>=0)&&c(a).is(":focusable")}}); -c(function(){var a=document.body,b=a.appendChild(b=document.createElement("div"));c.extend(b.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0});c.support.minHeight=b.offsetHeight===100;c.support.selectstart="onselectstart"in b;a.removeChild(b).style.display="none"});c.extend(c.ui,{plugin:{add:function(a,b,d){a=c.ui[a].prototype;for(var e in d){a.plugins[e]=a.plugins[e]||[];a.plugins[e].push([b,d[e]])}},call:function(a,b,d){if((b=a.plugins[b])&&a.element[0].parentNode)for(var e=0;e0)return true;a[b]=1;d=a[b]>0;a[b]=0;return d},isOverAxis:function(a,b,d){return a>b&&a=9)&&!a.button)return this._mouseUp(a);if(this._mouseStarted){this._mouseDrag(a); -return a.preventDefault()}if(this._mouseDistanceMet(a)&&this._mouseDelayMet(a))(this._mouseStarted=this._mouseStart(this._mouseDownEvent,a)!==false)?this._mouseDrag(a):this._mouseUp(a);return!this._mouseStarted},_mouseUp:function(a){c(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate);if(this._mouseStarted){this._mouseStarted=false;a.target==this._mouseDownEvent.target&&c.data(a.target,this.widgetName+".preventClickEvent", -true);this._mouseStop(a)}return false},_mouseDistanceMet:function(a){return Math.max(Math.abs(this._mouseDownEvent.pageX-a.pageX),Math.abs(this._mouseDownEvent.pageY-a.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return true}})})(jQuery); -;/* - * jQuery UI Position 1.8.9 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Position - */ -(function(c){c.ui=c.ui||{};var n=/left|center|right/,o=/top|center|bottom/,t=c.fn.position,u=c.fn.offset;c.fn.position=function(b){if(!b||!b.of)return t.apply(this,arguments);b=c.extend({},b);var a=c(b.of),d=a[0],g=(b.collision||"flip").split(" "),e=b.offset?b.offset.split(" "):[0,0],h,k,j;if(d.nodeType===9){h=a.width();k=a.height();j={top:0,left:0}}else if(d.setTimeout){h=a.width();k=a.height();j={top:a.scrollTop(),left:a.scrollLeft()}}else if(d.preventDefault){b.at="left top";h=k=0;j={top:b.of.pageY, -left:b.of.pageX}}else{h=a.outerWidth();k=a.outerHeight();j=a.offset()}c.each(["my","at"],function(){var f=(b[this]||"").split(" ");if(f.length===1)f=n.test(f[0])?f.concat(["center"]):o.test(f[0])?["center"].concat(f):["center","center"];f[0]=n.test(f[0])?f[0]:"center";f[1]=o.test(f[1])?f[1]:"center";b[this]=f});if(g.length===1)g[1]=g[0];e[0]=parseInt(e[0],10)||0;if(e.length===1)e[1]=e[0];e[1]=parseInt(e[1],10)||0;if(b.at[0]==="right")j.left+=h;else if(b.at[0]==="center")j.left+=h/2;if(b.at[1]==="bottom")j.top+= -k;else if(b.at[1]==="center")j.top+=k/2;j.left+=e[0];j.top+=e[1];return this.each(function(){var f=c(this),l=f.outerWidth(),m=f.outerHeight(),p=parseInt(c.curCSS(this,"marginLeft",true))||0,q=parseInt(c.curCSS(this,"marginTop",true))||0,v=l+p+(parseInt(c.curCSS(this,"marginRight",true))||0),w=m+q+(parseInt(c.curCSS(this,"marginBottom",true))||0),i=c.extend({},j),r;if(b.my[0]==="right")i.left-=l;else if(b.my[0]==="center")i.left-=l/2;if(b.my[1]==="bottom")i.top-=m;else if(b.my[1]==="center")i.top-= -m/2;i.left=Math.round(i.left);i.top=Math.round(i.top);r={left:i.left-p,top:i.top-q};c.each(["left","top"],function(s,x){c.ui.position[g[s]]&&c.ui.position[g[s]][x](i,{targetWidth:h,targetHeight:k,elemWidth:l,elemHeight:m,collisionPosition:r,collisionWidth:v,collisionHeight:w,offset:e,my:b.my,at:b.at})});c.fn.bgiframe&&f.bgiframe();f.offset(c.extend(i,{using:b.using}))})};c.ui.position={fit:{left:function(b,a){var d=c(window);d=a.collisionPosition.left+a.collisionWidth-d.width()-d.scrollLeft();b.left= -d>0?b.left-d:Math.max(b.left-a.collisionPosition.left,b.left)},top:function(b,a){var d=c(window);d=a.collisionPosition.top+a.collisionHeight-d.height()-d.scrollTop();b.top=d>0?b.top-d:Math.max(b.top-a.collisionPosition.top,b.top)}},flip:{left:function(b,a){if(a.at[0]!=="center"){var d=c(window);d=a.collisionPosition.left+a.collisionWidth-d.width()-d.scrollLeft();var g=a.my[0]==="left"?-a.elemWidth:a.my[0]==="right"?a.elemWidth:0,e=a.at[0]==="left"?a.targetWidth:-a.targetWidth,h=-2*a.offset[0];b.left+= -a.collisionPosition.left<0?g+e+h:d>0?g+e+h:0}},top:function(b,a){if(a.at[1]!=="center"){var d=c(window);d=a.collisionPosition.top+a.collisionHeight-d.height()-d.scrollTop();var g=a.my[1]==="top"?-a.elemHeight:a.my[1]==="bottom"?a.elemHeight:0,e=a.at[1]==="top"?a.targetHeight:-a.targetHeight,h=-2*a.offset[1];b.top+=a.collisionPosition.top<0?g+e+h:d>0?g+e+h:0}}}};if(!c.offset.setOffset){c.offset.setOffset=function(b,a){if(/static/.test(c.curCSS(b,"position")))b.style.position="relative";var d=c(b), -g=d.offset(),e=parseInt(c.curCSS(b,"top",true),10)||0,h=parseInt(c.curCSS(b,"left",true),10)||0;g={top:a.top-g.top+e,left:a.left-g.left+h};"using"in a?a.using.call(b,g):d.css(g)};c.fn.offset=function(b){var a=this[0];if(!a||!a.ownerDocument)return null;if(b)return this.each(function(){c.offset.setOffset(this,b)});return u.call(this)}}})(jQuery); -;/* - * jQuery UI Draggable 1.8.9 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Draggables - * - * Depends: - * jquery.ui.core.js - * jquery.ui.mouse.js - * jquery.ui.widget.js - */ -(function(d){d.widget("ui.draggable",d.ui.mouse,{widgetEventPrefix:"drag",options:{addClasses:true,appendTo:"parent",axis:false,connectToSortable:false,containment:false,cursor:"auto",cursorAt:false,grid:false,handle:false,helper:"original",iframeFix:false,opacity:false,refreshPositions:false,revert:false,revertDuration:500,scope:"default",scroll:true,scrollSensitivity:20,scrollSpeed:20,snap:false,snapMode:"both",snapTolerance:20,stack:false,zIndex:false},_create:function(){if(this.options.helper== -"original"&&!/^(?:r|a|f)/.test(this.element.css("position")))this.element[0].style.position="relative";this.options.addClasses&&this.element.addClass("ui-draggable");this.options.disabled&&this.element.addClass("ui-draggable-disabled");this._mouseInit()},destroy:function(){if(this.element.data("draggable")){this.element.removeData("draggable").unbind(".draggable").removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled");this._mouseDestroy();return this}},_mouseCapture:function(a){var b= -this.options;if(this.helper||b.disabled||d(a.target).is(".ui-resizable-handle"))return false;this.handle=this._getHandle(a);if(!this.handle)return false;return true},_mouseStart:function(a){var b=this.options;this.helper=this._createHelper(a);this._cacheHelperProportions();if(d.ui.ddmanager)d.ui.ddmanager.current=this;this._cacheMargins();this.cssPosition=this.helper.css("position");this.scrollParent=this.helper.scrollParent();this.offset=this.positionAbs=this.element.offset();this.offset={top:this.offset.top- -this.margins.top,left:this.offset.left-this.margins.left};d.extend(this.offset,{click:{left:a.pageX-this.offset.left,top:a.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});this.originalPosition=this.position=this._generatePosition(a);this.originalPageX=a.pageX;this.originalPageY=a.pageY;b.cursorAt&&this._adjustOffsetFromHelper(b.cursorAt);b.containment&&this._setContainment();if(this._trigger("start",a)===false){this._clear();return false}this._cacheHelperProportions(); -d.ui.ddmanager&&!b.dropBehaviour&&d.ui.ddmanager.prepareOffsets(this,a);this.helper.addClass("ui-draggable-dragging");this._mouseDrag(a,true);return true},_mouseDrag:function(a,b){this.position=this._generatePosition(a);this.positionAbs=this._convertPositionTo("absolute");if(!b){b=this._uiHash();if(this._trigger("drag",a,b)===false){this._mouseUp({});return false}this.position=b.position}if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis|| -this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";d.ui.ddmanager&&d.ui.ddmanager.drag(this,a);return false},_mouseStop:function(a){var b=false;if(d.ui.ddmanager&&!this.options.dropBehaviour)b=d.ui.ddmanager.drop(this,a);if(this.dropped){b=this.dropped;this.dropped=false}if((!this.element[0]||!this.element[0].parentNode)&&this.options.helper=="original")return false;if(this.options.revert=="invalid"&&!b||this.options.revert=="valid"&&b||this.options.revert===true||d.isFunction(this.options.revert)&& -this.options.revert.call(this.element,b)){var c=this;d(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){c._trigger("stop",a)!==false&&c._clear()})}else this._trigger("stop",a)!==false&&this._clear();return false},cancel:function(){this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear();return this},_getHandle:function(a){var b=!this.options.handle||!d(this.options.handle,this.element).length?true:false;d(this.options.handle,this.element).find("*").andSelf().each(function(){if(this== -a.target)b=true});return b},_createHelper:function(a){var b=this.options;a=d.isFunction(b.helper)?d(b.helper.apply(this.element[0],[a])):b.helper=="clone"?this.element.clone():this.element;a.parents("body").length||a.appendTo(b.appendTo=="parent"?this.element[0].parentNode:b.appendTo);a[0]!=this.element[0]&&!/(fixed|absolute)/.test(a.css("position"))&&a.css("position","absolute");return a},_adjustOffsetFromHelper:function(a){if(typeof a=="string")a=a.split(" ");if(d.isArray(a))a={left:+a[0],top:+a[1]|| -0};if("left"in a)this.offset.click.left=a.left+this.margins.left;if("right"in a)this.offset.click.left=this.helperProportions.width-a.right+this.margins.left;if("top"in a)this.offset.click.top=a.top+this.margins.top;if("bottom"in a)this.offset.click.top=this.helperProportions.height-a.bottom+this.margins.top},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var a=this.offsetParent.offset();if(this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0], -this.offsetParent[0])){a.left+=this.scrollParent.scrollLeft();a.top+=this.scrollParent.scrollTop()}if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&d.browser.msie)a={top:0,left:0};return{top:a.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:a.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var a=this.element.position();return{top:a.top- -(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:a.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}else return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var a=this.options;if(a.containment== -"parent")a.containment=this.helper[0].parentNode;if(a.containment=="document"||a.containment=="window")this.containment=[(a.containment=="document"?0:d(window).scrollLeft())-this.offset.relative.left-this.offset.parent.left,(a.containment=="document"?0:d(window).scrollTop())-this.offset.relative.top-this.offset.parent.top,(a.containment=="document"?0:d(window).scrollLeft())+d(a.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(a.containment=="document"? -0:d(window).scrollTop())+(d(a.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(a.containment)&&a.containment.constructor!=Array){var b=d(a.containment)[0];if(b){a=d(a.containment).offset();var c=d(b).css("overflow")!="hidden";this.containment=[a.left+(parseInt(d(b).css("borderLeftWidth"),10)||0)+(parseInt(d(b).css("paddingLeft"),10)||0)-this.margins.left,a.top+(parseInt(d(b).css("borderTopWidth"), -10)||0)+(parseInt(d(b).css("paddingTop"),10)||0)-this.margins.top,a.left+(c?Math.max(b.scrollWidth,b.offsetWidth):b.offsetWidth)-(parseInt(d(b).css("borderLeftWidth"),10)||0)-(parseInt(d(b).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,a.top+(c?Math.max(b.scrollHeight,b.offsetHeight):b.offsetHeight)-(parseInt(d(b).css("borderTopWidth"),10)||0)-(parseInt(d(b).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top]}}else if(a.containment.constructor== -Array)this.containment=a.containment},_convertPositionTo:function(a,b){if(!b)b=this.position;a=a=="absolute"?1:-1;var c=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,f=/(html|body)/i.test(c[0].tagName);return{top:b.top+this.offset.relative.top*a+this.offset.parent.top*a-(d.browser.safari&&d.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop(): -f?0:c.scrollTop())*a),left:b.left+this.offset.relative.left*a+this.offset.parent.left*a-(d.browser.safari&&d.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():f?0:c.scrollLeft())*a)}},_generatePosition:function(a){var b=this.options,c=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,f=/(html|body)/i.test(c[0].tagName),e=a.pageX,g=a.pageY; -if(this.originalPosition){if(this.containment){if(a.pageX-this.offset.click.leftthis.containment[2])e=this.containment[2]+this.offset.click.left;if(a.pageY-this.offset.click.top>this.containment[3])g=this.containment[3]+this.offset.click.top}if(b.grid){g=this.originalPageY+Math.round((g-this.originalPageY)/ -b.grid[1])*b.grid[1];g=this.containment?!(g-this.offset.click.topthis.containment[3])?g:!(g-this.offset.click.topthis.containment[2])?e:!(e-this.offset.click.left').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1E3}).css(d(this).offset()).appendTo("body")})}, -stop:function(){d("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)})}});d.ui.plugin.add("draggable","opacity",{start:function(a,b){a=d(b.helper);b=d(this).data("draggable").options;if(a.css("opacity"))b._opacity=a.css("opacity");a.css("opacity",b.opacity)},stop:function(a,b){a=d(this).data("draggable").options;a._opacity&&d(b.helper).css("opacity",a._opacity)}});d.ui.plugin.add("draggable","scroll",{start:function(){var a=d(this).data("draggable");if(a.scrollParent[0]!= -document&&a.scrollParent[0].tagName!="HTML")a.overflowOffset=a.scrollParent.offset()},drag:function(a){var b=d(this).data("draggable"),c=b.options,f=false;if(b.scrollParent[0]!=document&&b.scrollParent[0].tagName!="HTML"){if(!c.axis||c.axis!="x")if(b.overflowOffset.top+b.scrollParent[0].offsetHeight-a.pageY=0;h--){var i=c.snapElements[h].left,k=i+c.snapElements[h].width,j=c.snapElements[h].top,l=j+c.snapElements[h].height;if(i-e=j&&f<=l||h>=j&&h<=l||fl)&&(e>= -i&&e<=k||g>=i&&g<=k||ek);default:return false}};d.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(a,b){var c=d.ui.ddmanager.droppables[a.options.scope]||[],e=b?b.type:null,g=(a.currentItem||a.element).find(":data(droppable)").andSelf(),f=0;a:for(;f').css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(), -top:this.element.css("top"),left:this.element.css("left")}));this.element=this.element.parent().data("resizable",this.element.data("resizable"));this.elementIsWrapper=true;this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")});this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0});this.originalResizeStyle= -this.originalElement.css("resize");this.originalElement.css("resize","none");this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"}));this.originalElement.css({margin:this.originalElement.css("margin")});this._proportionallyResize()}this.handles=a.handles||(!e(".ui-resizable-handle",this.element).length?"e,s,se":{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne", -nw:".ui-resizable-nw"});if(this.handles.constructor==String){if(this.handles=="all")this.handles="n,e,s,w,se,sw,ne,nw";var c=this.handles.split(",");this.handles={};for(var d=0;d');/sw|se|ne|nw/.test(f)&&g.css({zIndex:++a.zIndex});"se"==f&&g.addClass("ui-icon ui-icon-gripsmall-diagonal-se");this.handles[f]=".ui-resizable-"+f;this.element.append(g)}}this._renderAxis=function(h){h=h||this.element;for(var i in this.handles){if(this.handles[i].constructor== -String)this.handles[i]=e(this.handles[i],this.element).show();if(this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)){var j=e(this.handles[i],this.element),k=0;k=/sw|ne|nw|se|n|s/.test(i)?j.outerHeight():j.outerWidth();j=["padding",/ne|nw|n/.test(i)?"Top":/se|sw|s/.test(i)?"Bottom":/^e$/.test(i)?"Right":"Left"].join("");h.css(j,k);this._proportionallyResize()}e(this.handles[i])}};this._renderAxis(this.element);this._handles=e(".ui-resizable-handle",this.element).disableSelection(); -this._handles.mouseover(function(){if(!b.resizing){if(this.className)var h=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);b.axis=h&&h[1]?h[1]:"se"}});if(a.autoHide){this._handles.hide();e(this.element).addClass("ui-resizable-autohide").hover(function(){e(this).removeClass("ui-resizable-autohide");b._handles.show()},function(){if(!b.resizing){e(this).addClass("ui-resizable-autohide");b._handles.hide()}})}this._mouseInit()},destroy:function(){this._mouseDestroy();var b=function(c){e(c).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").unbind(".resizable").find(".ui-resizable-handle").remove()}; -if(this.elementIsWrapper){b(this.element);var a=this.element;a.after(this.originalElement.css({position:a.css("position"),width:a.outerWidth(),height:a.outerHeight(),top:a.css("top"),left:a.css("left")})).remove()}this.originalElement.css("resize",this.originalResizeStyle);b(this.originalElement);return this},_mouseCapture:function(b){var a=false;for(var c in this.handles)if(e(this.handles[c])[0]==b.target)a=true;return!this.options.disabled&&a},_mouseStart:function(b){var a=this.options,c=this.element.position(), -d=this.element;this.resizing=true;this.documentScroll={top:e(document).scrollTop(),left:e(document).scrollLeft()};if(d.is(".ui-draggable")||/absolute/.test(d.css("position")))d.css({position:"absolute",top:c.top,left:c.left});e.browser.opera&&/relative/.test(d.css("position"))&&d.css({position:"relative",top:"auto",left:"auto"});this._renderProxy();c=m(this.helper.css("left"));var f=m(this.helper.css("top"));if(a.containment){c+=e(a.containment).scrollLeft()||0;f+=e(a.containment).scrollTop()||0}this.offset= -this.helper.offset();this.position={left:c,top:f};this.size=this._helper?{width:d.outerWidth(),height:d.outerHeight()}:{width:d.width(),height:d.height()};this.originalSize=this._helper?{width:d.outerWidth(),height:d.outerHeight()}:{width:d.width(),height:d.height()};this.originalPosition={left:c,top:f};this.sizeDiff={width:d.outerWidth()-d.width(),height:d.outerHeight()-d.height()};this.originalMousePosition={left:b.pageX,top:b.pageY};this.aspectRatio=typeof a.aspectRatio=="number"?a.aspectRatio: -this.originalSize.width/this.originalSize.height||1;a=e(".ui-resizable-"+this.axis).css("cursor");e("body").css("cursor",a=="auto"?this.axis+"-resize":a);d.addClass("ui-resizable-resizing");this._propagate("start",b);return true},_mouseDrag:function(b){var a=this.helper,c=this.originalMousePosition,d=this._change[this.axis];if(!d)return false;c=d.apply(this,[b,b.pageX-c.left||0,b.pageY-c.top||0]);if(this._aspectRatio||b.shiftKey)c=this._updateRatio(c,b);c=this._respectSize(c,b);this._propagate("resize", -b);a.css({top:this.position.top+"px",left:this.position.left+"px",width:this.size.width+"px",height:this.size.height+"px"});!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize();this._updateCache(c);this._trigger("resize",b,this.ui());return false},_mouseStop:function(b){this.resizing=false;var a=this.options,c=this;if(this._helper){var d=this._proportionallyResizeElements,f=d.length&&/textarea/i.test(d[0].nodeName);d=f&&e.ui.hasScroll(d[0],"left")?0:c.sizeDiff.height; -f={width:c.size.width-(f?0:c.sizeDiff.width),height:c.size.height-d};d=parseInt(c.element.css("left"),10)+(c.position.left-c.originalPosition.left)||null;var g=parseInt(c.element.css("top"),10)+(c.position.top-c.originalPosition.top)||null;a.animate||this.element.css(e.extend(f,{top:g,left:d}));c.helper.height(c.size.height);c.helper.width(c.size.width);this._helper&&!a.animate&&this._proportionallyResize()}e("body").css("cursor","auto");this.element.removeClass("ui-resizable-resizing");this._propagate("stop", -b);this._helper&&this.helper.remove();return false},_updateCache:function(b){this.offset=this.helper.offset();if(l(b.left))this.position.left=b.left;if(l(b.top))this.position.top=b.top;if(l(b.height))this.size.height=b.height;if(l(b.width))this.size.width=b.width},_updateRatio:function(b){var a=this.position,c=this.size,d=this.axis;if(b.height)b.width=c.height*this.aspectRatio;else if(b.width)b.height=c.width/this.aspectRatio;if(d=="sw"){b.left=a.left+(c.width-b.width);b.top=null}if(d=="nw"){b.top= -a.top+(c.height-b.height);b.left=a.left+(c.width-b.width)}return b},_respectSize:function(b){var a=this.options,c=this.axis,d=l(b.width)&&a.maxWidth&&a.maxWidthb.width,h=l(b.height)&&a.minHeight&&a.minHeight>b.height;if(g)b.width=a.minWidth;if(h)b.height=a.minHeight;if(d)b.width=a.maxWidth;if(f)b.height=a.maxHeight;var i=this.originalPosition.left+this.originalSize.width,j=this.position.top+this.size.height, -k=/sw|nw|w/.test(c);c=/nw|ne|n/.test(c);if(g&&k)b.left=i-a.minWidth;if(d&&k)b.left=i-a.maxWidth;if(h&&c)b.top=j-a.minHeight;if(f&&c)b.top=j-a.maxHeight;if((a=!b.width&&!b.height)&&!b.left&&b.top)b.top=null;else if(a&&!b.top&&b.left)b.left=null;return b},_proportionallyResize:function(){if(this._proportionallyResizeElements.length)for(var b=this.helper||this.element,a=0;a');var a=e.browser.msie&&e.browser.version<7,c=a?1:0;a=a?2:-1;this.helper.addClass(this._helper).css({width:this.element.outerWidth()+a,height:this.element.outerHeight()+a,position:"absolute",left:this.elementOffset.left-c+"px",top:this.elementOffset.top-c+"px",zIndex:++b.zIndex});this.helper.appendTo("body").disableSelection()}else this.helper=this.element},_change:{e:function(b,a){return{width:this.originalSize.width+ -a}},w:function(b,a){return{left:this.originalPosition.left+a,width:this.originalSize.width-a}},n:function(b,a,c){return{top:this.originalPosition.top+c,height:this.originalSize.height-c}},s:function(b,a,c){return{height:this.originalSize.height+c}},se:function(b,a,c){return e.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[b,a,c]))},sw:function(b,a,c){return e.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[b,a,c]))},ne:function(b,a,c){return e.extend(this._change.n.apply(this, -arguments),this._change.e.apply(this,[b,a,c]))},nw:function(b,a,c){return e.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[b,a,c]))}},_propagate:function(b,a){e.ui.plugin.call(this,b,[a,this.ui()]);b!="resize"&&this._trigger(b,a,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}});e.extend(e.ui.resizable, -{version:"1.8.9"});e.ui.plugin.add("resizable","alsoResize",{start:function(){var b=e(this).data("resizable").options,a=function(c){e(c).each(function(){var d=e(this);d.data("resizable-alsoresize",{width:parseInt(d.width(),10),height:parseInt(d.height(),10),left:parseInt(d.css("left"),10),top:parseInt(d.css("top"),10),position:d.css("position")})})};if(typeof b.alsoResize=="object"&&!b.alsoResize.parentNode)if(b.alsoResize.length){b.alsoResize=b.alsoResize[0];a(b.alsoResize)}else e.each(b.alsoResize, -function(c){a(c)});else a(b.alsoResize)},resize:function(b,a){var c=e(this).data("resizable");b=c.options;var d=c.originalSize,f=c.originalPosition,g={height:c.size.height-d.height||0,width:c.size.width-d.width||0,top:c.position.top-f.top||0,left:c.position.left-f.left||0},h=function(i,j){e(i).each(function(){var k=e(this),q=e(this).data("resizable-alsoresize"),p={},r=j&&j.length?j:k.parents(a.originalElement[0]).length?["width","height"]:["width","height","top","left"];e.each(r,function(n,o){if((n= -(q[o]||0)+(g[o]||0))&&n>=0)p[o]=n||null});if(e.browser.opera&&/relative/.test(k.css("position"))){c._revertToRelativePosition=true;k.css({position:"absolute",top:"auto",left:"auto"})}k.css(p)})};typeof b.alsoResize=="object"&&!b.alsoResize.nodeType?e.each(b.alsoResize,function(i,j){h(i,j)}):h(b.alsoResize)},stop:function(){var b=e(this).data("resizable"),a=b.options,c=function(d){e(d).each(function(){var f=e(this);f.css({position:f.data("resizable-alsoresize").position})})};if(b._revertToRelativePosition){b._revertToRelativePosition= -false;typeof a.alsoResize=="object"&&!a.alsoResize.nodeType?e.each(a.alsoResize,function(d){c(d)}):c(a.alsoResize)}e(this).removeData("resizable-alsoresize")}});e.ui.plugin.add("resizable","animate",{stop:function(b){var a=e(this).data("resizable"),c=a.options,d=a._proportionallyResizeElements,f=d.length&&/textarea/i.test(d[0].nodeName),g=f&&e.ui.hasScroll(d[0],"left")?0:a.sizeDiff.height;f={width:a.size.width-(f?0:a.sizeDiff.width),height:a.size.height-g};g=parseInt(a.element.css("left"),10)+(a.position.left- -a.originalPosition.left)||null;var h=parseInt(a.element.css("top"),10)+(a.position.top-a.originalPosition.top)||null;a.element.animate(e.extend(f,h&&g?{top:h,left:g}:{}),{duration:c.animateDuration,easing:c.animateEasing,step:function(){var i={width:parseInt(a.element.css("width"),10),height:parseInt(a.element.css("height"),10),top:parseInt(a.element.css("top"),10),left:parseInt(a.element.css("left"),10)};d&&d.length&&e(d[0]).css({width:i.width,height:i.height});a._updateCache(i);a._propagate("resize", -b)}})}});e.ui.plugin.add("resizable","containment",{start:function(){var b=e(this).data("resizable"),a=b.element,c=b.options.containment;if(a=c instanceof e?c.get(0):/parent/.test(c)?a.parent().get(0):c){b.containerElement=e(a);if(/document/.test(c)||c==document){b.containerOffset={left:0,top:0};b.containerPosition={left:0,top:0};b.parentData={element:e(document),left:0,top:0,width:e(document).width(),height:e(document).height()||document.body.parentNode.scrollHeight}}else{var d=e(a),f=[];e(["Top", -"Right","Left","Bottom"]).each(function(i,j){f[i]=m(d.css("padding"+j))});b.containerOffset=d.offset();b.containerPosition=d.position();b.containerSize={height:d.innerHeight()-f[3],width:d.innerWidth()-f[1]};c=b.containerOffset;var g=b.containerSize.height,h=b.containerSize.width;h=e.ui.hasScroll(a,"left")?a.scrollWidth:h;g=e.ui.hasScroll(a)?a.scrollHeight:g;b.parentData={element:a,left:c.left,top:c.top,width:h,height:g}}}},resize:function(b){var a=e(this).data("resizable"),c=a.options,d=a.containerOffset, -f=a.position;b=a._aspectRatio||b.shiftKey;var g={top:0,left:0},h=a.containerElement;if(h[0]!=document&&/static/.test(h.css("position")))g=d;if(f.left<(a._helper?d.left:0)){a.size.width+=a._helper?a.position.left-d.left:a.position.left-g.left;if(b)a.size.height=a.size.width/c.aspectRatio;a.position.left=c.helper?d.left:0}if(f.top<(a._helper?d.top:0)){a.size.height+=a._helper?a.position.top-d.top:a.position.top;if(b)a.size.width=a.size.height*c.aspectRatio;a.position.top=a._helper?d.top:0}a.offset.left= -a.parentData.left+a.position.left;a.offset.top=a.parentData.top+a.position.top;c=Math.abs((a._helper?a.offset.left-g.left:a.offset.left-g.left)+a.sizeDiff.width);d=Math.abs((a._helper?a.offset.top-g.top:a.offset.top-d.top)+a.sizeDiff.height);f=a.containerElement.get(0)==a.element.parent().get(0);g=/relative|absolute/.test(a.containerElement.css("position"));if(f&&g)c-=a.parentData.left;if(c+a.size.width>=a.parentData.width){a.size.width=a.parentData.width-c;if(b)a.size.height=a.size.width/a.aspectRatio}if(d+ -a.size.height>=a.parentData.height){a.size.height=a.parentData.height-d;if(b)a.size.width=a.size.height*a.aspectRatio}},stop:function(){var b=e(this).data("resizable"),a=b.options,c=b.containerOffset,d=b.containerPosition,f=b.containerElement,g=e(b.helper),h=g.offset(),i=g.outerWidth()-b.sizeDiff.width;g=g.outerHeight()-b.sizeDiff.height;b._helper&&!a.animate&&/relative/.test(f.css("position"))&&e(this).css({left:h.left-d.left-c.left,width:i,height:g});b._helper&&!a.animate&&/static/.test(f.css("position"))&& -e(this).css({left:h.left-d.left-c.left,width:i,height:g})}});e.ui.plugin.add("resizable","ghost",{start:function(){var b=e(this).data("resizable"),a=b.options,c=b.size;b.ghost=b.originalElement.clone();b.ghost.css({opacity:0.25,display:"block",position:"relative",height:c.height,width:c.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass(typeof a.ghost=="string"?a.ghost:"");b.ghost.appendTo(b.helper)},resize:function(){var b=e(this).data("resizable");b.ghost&&b.ghost.css({position:"relative", -height:b.size.height,width:b.size.width})},stop:function(){var b=e(this).data("resizable");b.ghost&&b.helper&&b.helper.get(0).removeChild(b.ghost.get(0))}});e.ui.plugin.add("resizable","grid",{resize:function(){var b=e(this).data("resizable"),a=b.options,c=b.size,d=b.originalSize,f=b.originalPosition,g=b.axis;a.grid=typeof a.grid=="number"?[a.grid,a.grid]:a.grid;var h=Math.round((c.width-d.width)/(a.grid[0]||1))*(a.grid[0]||1);a=Math.round((c.height-d.height)/(a.grid[1]||1))*(a.grid[1]||1);if(/^(se|s|e)$/.test(g)){b.size.width= -d.width+h;b.size.height=d.height+a}else if(/^(ne)$/.test(g)){b.size.width=d.width+h;b.size.height=d.height+a;b.position.top=f.top-a}else{if(/^(sw)$/.test(g)){b.size.width=d.width+h;b.size.height=d.height+a}else{b.size.width=d.width+h;b.size.height=d.height+a;b.position.top=f.top-a}b.position.left=f.left-h}}});var m=function(b){return parseInt(b,10)||0},l=function(b){return!isNaN(parseInt(b,10))}})(jQuery); -;/* - * jQuery UI Selectable 1.8.9 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Selectables - * - * Depends: - * jquery.ui.core.js - * jquery.ui.mouse.js - * jquery.ui.widget.js - */ -(function(e){e.widget("ui.selectable",e.ui.mouse,{options:{appendTo:"body",autoRefresh:true,distance:0,filter:"*",tolerance:"touch"},_create:function(){var c=this;this.element.addClass("ui-selectable");this.dragged=false;var f;this.refresh=function(){f=e(c.options.filter,c.element[0]);f.each(function(){var d=e(this),b=d.offset();e.data(this,"selectable-item",{element:this,$element:d,left:b.left,top:b.top,right:b.left+d.outerWidth(),bottom:b.top+d.outerHeight(),startselected:false,selected:d.hasClass("ui-selected"), -selecting:d.hasClass("ui-selecting"),unselecting:d.hasClass("ui-unselecting")})})};this.refresh();this.selectees=f.addClass("ui-selectee");this._mouseInit();this.helper=e("
")},destroy:function(){this.selectees.removeClass("ui-selectee").removeData("selectable-item");this.element.removeClass("ui-selectable ui-selectable-disabled").removeData("selectable").unbind(".selectable");this._mouseDestroy();return this},_mouseStart:function(c){var f=this;this.opos=[c.pageX, -c.pageY];if(!this.options.disabled){var d=this.options;this.selectees=e(d.filter,this.element[0]);this._trigger("start",c);e(d.appendTo).append(this.helper);this.helper.css({left:c.clientX,top:c.clientY,width:0,height:0});d.autoRefresh&&this.refresh();this.selectees.filter(".ui-selected").each(function(){var b=e.data(this,"selectable-item");b.startselected=true;if(!c.metaKey){b.$element.removeClass("ui-selected");b.selected=false;b.$element.addClass("ui-unselecting");b.unselecting=true;f._trigger("unselecting", -c,{unselecting:b.element})}});e(c.target).parents().andSelf().each(function(){var b=e.data(this,"selectable-item");if(b){var g=!c.metaKey||!b.$element.hasClass("ui-selected");b.$element.removeClass(g?"ui-unselecting":"ui-selected").addClass(g?"ui-selecting":"ui-unselecting");b.unselecting=!g;b.selecting=g;(b.selected=g)?f._trigger("selecting",c,{selecting:b.element}):f._trigger("unselecting",c,{unselecting:b.element});return false}})}},_mouseDrag:function(c){var f=this;this.dragged=true;if(!this.options.disabled){var d= -this.options,b=this.opos[0],g=this.opos[1],h=c.pageX,i=c.pageY;if(b>h){var j=h;h=b;b=j}if(g>i){j=i;i=g;g=j}this.helper.css({left:b,top:g,width:h-b,height:i-g});this.selectees.each(function(){var a=e.data(this,"selectable-item");if(!(!a||a.element==f.element[0])){var k=false;if(d.tolerance=="touch")k=!(a.left>h||a.righti||a.bottomb&&a.rightg&&a.bottom *",opacity:false,placeholder:false,revert:false,scroll:true,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1E3},_create:function(){this.containerCache={};this.element.addClass("ui-sortable"); -this.refresh();this.floating=this.items.length?/left|right/.test(this.items[0].item.css("float")):false;this.offset=this.element.offset();this._mouseInit()},destroy:function(){this.element.removeClass("ui-sortable ui-sortable-disabled").removeData("sortable").unbind(".sortable");this._mouseDestroy();for(var a=this.items.length-1;a>=0;a--)this.items[a].item.removeData("sortable-item");return this},_setOption:function(a,b){if(a==="disabled"){this.options[a]=b;this.widget()[b?"addClass":"removeClass"]("ui-sortable-disabled")}else d.Widget.prototype._setOption.apply(this, -arguments)},_mouseCapture:function(a,b){if(this.reverting)return false;if(this.options.disabled||this.options.type=="static")return false;this._refreshItems(a);var c=null,e=this;d(a.target).parents().each(function(){if(d.data(this,"sortable-item")==e){c=d(this);return false}});if(d.data(a.target,"sortable-item")==e)c=d(a.target);if(!c)return false;if(this.options.handle&&!b){var f=false;d(this.options.handle,c).find("*").andSelf().each(function(){if(this==a.target)f=true});if(!f)return false}this.currentItem= -c;this._removeCurrentsFromItems();return true},_mouseStart:function(a,b,c){b=this.options;var e=this;this.currentContainer=this;this.refreshPositions();this.helper=this._createHelper(a);this._cacheHelperProportions();this._cacheMargins();this.scrollParent=this.helper.scrollParent();this.offset=this.currentItem.offset();this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left};this.helper.css("position","absolute");this.cssPosition=this.helper.css("position");d.extend(this.offset, -{click:{left:a.pageX-this.offset.left,top:a.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});this.originalPosition=this._generatePosition(a);this.originalPageX=a.pageX;this.originalPageY=a.pageY;b.cursorAt&&this._adjustOffsetFromHelper(b.cursorAt);this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]};this.helper[0]!=this.currentItem[0]&&this.currentItem.hide();this._createPlaceholder();b.containment&&this._setContainment(); -if(b.cursor){if(d("body").css("cursor"))this._storedCursor=d("body").css("cursor");d("body").css("cursor",b.cursor)}if(b.opacity){if(this.helper.css("opacity"))this._storedOpacity=this.helper.css("opacity");this.helper.css("opacity",b.opacity)}if(b.zIndex){if(this.helper.css("zIndex"))this._storedZIndex=this.helper.css("zIndex");this.helper.css("zIndex",b.zIndex)}if(this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML")this.overflowOffset=this.scrollParent.offset();this._trigger("start", -a,this._uiHash());this._preserveHelperProportions||this._cacheHelperProportions();if(!c)for(c=this.containers.length-1;c>=0;c--)this.containers[c]._trigger("activate",a,e._uiHash(this));if(d.ui.ddmanager)d.ui.ddmanager.current=this;d.ui.ddmanager&&!b.dropBehaviour&&d.ui.ddmanager.prepareOffsets(this,a);this.dragging=true;this.helper.addClass("ui-sortable-helper");this._mouseDrag(a);return true},_mouseDrag:function(a){this.position=this._generatePosition(a);this.positionAbs=this._convertPositionTo("absolute"); -if(!this.lastPositionAbs)this.lastPositionAbs=this.positionAbs;if(this.options.scroll){var b=this.options,c=false;if(this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"){if(this.overflowOffset.top+this.scrollParent[0].offsetHeight-a.pageY=0;b--){c=this.items[b];var e=c.item[0],f=this._intersectsWithPointer(c);if(f)if(e!=this.currentItem[0]&&this.placeholder[f==1?"next":"prev"]()[0]!=e&&!d.ui.contains(this.placeholder[0],e)&&(this.options.type=="semi-dynamic"?!d.ui.contains(this.element[0],e):true)){this.direction=f==1?"down":"up";if(this.options.tolerance=="pointer"||this._intersectsWithSides(c))this._rearrange(a, -c);else break;this._trigger("change",a,this._uiHash());break}}this._contactContainers(a);d.ui.ddmanager&&d.ui.ddmanager.drag(this,a);this._trigger("sort",a,this._uiHash());this.lastPositionAbs=this.positionAbs;return false},_mouseStop:function(a,b){if(a){d.ui.ddmanager&&!this.options.dropBehaviour&&d.ui.ddmanager.drop(this,a);if(this.options.revert){var c=this;b=c.placeholder.offset();c.reverting=true;d(this.helper).animate({left:b.left-this.offset.parent.left-c.margins.left+(this.offsetParent[0]== -document.body?0:this.offsetParent[0].scrollLeft),top:b.top-this.offset.parent.top-c.margins.top+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollTop)},parseInt(this.options.revert,10)||500,function(){c._clear(a)})}else this._clear(a,b);return false}},cancel:function(){var a=this;if(this.dragging){this._mouseUp({target:null});this.options.helper=="original"?this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"):this.currentItem.show();for(var b=this.containers.length- -1;b>=0;b--){this.containers[b]._trigger("deactivate",null,a._uiHash(this));if(this.containers[b].containerCache.over){this.containers[b]._trigger("out",null,a._uiHash(this));this.containers[b].containerCache.over=0}}}if(this.placeholder){this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]);this.options.helper!="original"&&this.helper&&this.helper[0].parentNode&&this.helper.remove();d.extend(this,{helper:null,dragging:false,reverting:false,_noFinalSort:null}); -this.domPosition.prev?d(this.domPosition.prev).after(this.currentItem):d(this.domPosition.parent).prepend(this.currentItem)}return this},serialize:function(a){var b=this._getItemsAsjQuery(a&&a.connected),c=[];a=a||{};d(b).each(function(){var e=(d(a.item||this).attr(a.attribute||"id")||"").match(a.expression||/(.+)[-=_](.+)/);if(e)c.push((a.key||e[1]+"[]")+"="+(a.key&&a.expression?e[1]:e[2]))});!c.length&&a.key&&c.push(a.key+"=");return c.join("&")},toArray:function(a){var b=this._getItemsAsjQuery(a&& -a.connected),c=[];a=a||{};b.each(function(){c.push(d(a.item||this).attr(a.attribute||"id")||"")});return c},_intersectsWith:function(a){var b=this.positionAbs.left,c=b+this.helperProportions.width,e=this.positionAbs.top,f=e+this.helperProportions.height,g=a.left,h=g+a.width,i=a.top,k=i+a.height,j=this.offset.click.top,l=this.offset.click.left;j=e+j>i&&e+jg&&b+la[this.floating?"width":"height"]?j:g0?"down":"up")},_getDragHorizontalDirection:function(){var a=this.positionAbs.left-this.lastPositionAbs.left;return a!=0&&(a>0?"right":"left")},refresh:function(a){this._refreshItems(a);this.refreshPositions();return this},_connectWith:function(){var a=this.options;return a.connectWith.constructor==String?[a.connectWith]:a.connectWith},_getItemsAsjQuery:function(a){var b=[],c=[],e=this._connectWith();if(e&&a)for(a=e.length-1;a>=0;a--)for(var f=d(e[a]),g=f.length-1;g>=0;g--){var h= -d.data(f[g],"sortable");if(h&&h!=this&&!h.options.disabled)c.push([d.isFunction(h.options.items)?h.options.items.call(h.element):d(h.options.items,h.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),h])}c.push([d.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):d(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]);for(a=c.length-1;a>=0;a--)c[a][0].each(function(){b.push(this)}); -return d(b)},_removeCurrentsFromItems:function(){for(var a=this.currentItem.find(":data(sortable-item)"),b=0;b=0;f--)for(var g= -d(e[f]),h=g.length-1;h>=0;h--){var i=d.data(g[h],"sortable");if(i&&i!=this&&!i.options.disabled){c.push([d.isFunction(i.options.items)?i.options.items.call(i.element[0],a,{item:this.currentItem}):d(i.options.items,i.element),i]);this.containers.push(i)}}for(f=c.length-1;f>=0;f--){a=c[f][1];e=c[f][0];h=0;for(g=e.length;h=0;b--){var c=this.items[b],e=this.options.toleranceElement?d(this.options.toleranceElement,c.item):c.item;if(!a){c.width=e.outerWidth();c.height=e.outerHeight()}e=e.offset();c.left=e.left;c.top=e.top}if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(b=this.containers.length-1;b>=0;b--){e=this.containers[b].element.offset();this.containers[b].containerCache.left=e.left;this.containers[b].containerCache.top= -e.top;this.containers[b].containerCache.width=this.containers[b].element.outerWidth();this.containers[b].containerCache.height=this.containers[b].element.outerHeight()}return this},_createPlaceholder:function(a){var b=a||this,c=b.options;if(!c.placeholder||c.placeholder.constructor==String){var e=c.placeholder;c.placeholder={element:function(){var f=d(document.createElement(b.currentItem[0].nodeName)).addClass(e||b.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper")[0]; -if(!e)f.style.visibility="hidden";return f},update:function(f,g){if(!(e&&!c.forcePlaceholderSize)){g.height()||g.height(b.currentItem.innerHeight()-parseInt(b.currentItem.css("paddingTop")||0,10)-parseInt(b.currentItem.css("paddingBottom")||0,10));g.width()||g.width(b.currentItem.innerWidth()-parseInt(b.currentItem.css("paddingLeft")||0,10)-parseInt(b.currentItem.css("paddingRight")||0,10))}}}}b.placeholder=d(c.placeholder.element.call(b.element,b.currentItem));b.currentItem.after(b.placeholder); -c.placeholder.update(b,b.placeholder)},_contactContainers:function(a){for(var b=null,c=null,e=this.containers.length-1;e>=0;e--)if(!d.ui.contains(this.currentItem[0],this.containers[e].element[0]))if(this._intersectsWith(this.containers[e].containerCache)){if(!(b&&d.ui.contains(this.containers[e].element[0],b.element[0]))){b=this.containers[e];c=e}}else if(this.containers[e].containerCache.over){this.containers[e]._trigger("out",a,this._uiHash(this));this.containers[e].containerCache.over=0}if(b)if(this.containers.length=== -1){this.containers[c]._trigger("over",a,this._uiHash(this));this.containers[c].containerCache.over=1}else if(this.currentContainer!=this.containers[c]){b=1E4;e=null;for(var f=this.positionAbs[this.containers[c].floating?"left":"top"],g=this.items.length-1;g>=0;g--)if(d.ui.contains(this.containers[c].element[0],this.items[g].item[0])){var h=this.items[g][this.containers[c].floating?"left":"top"];if(Math.abs(h-f)this.containment[2])f=this.containment[2]+this.offset.click.left;if(a.pageY-this.offset.click.top>this.containment[3])g=this.containment[3]+this.offset.click.top}if(b.grid){g=this.originalPageY+Math.round((g-this.originalPageY)/b.grid[1])*b.grid[1];g=this.containment?!(g-this.offset.click.top< -this.containment[1]||g-this.offset.click.top>this.containment[3])?g:!(g-this.offset.click.topthis.containment[2])?f:!(f-this.offset.click.left=0;e--)if(d.ui.contains(this.containers[e].element[0], -this.currentItem[0])&&!b){c.push(function(f){return function(g){f._trigger("receive",g,this._uiHash(this))}}.call(this,this.containers[e]));c.push(function(f){return function(g){f._trigger("update",g,this._uiHash(this))}}.call(this,this.containers[e]))}}for(e=this.containers.length-1;e>=0;e--){b||c.push(function(f){return function(g){f._trigger("deactivate",g,this._uiHash(this))}}.call(this,this.containers[e]));if(this.containers[e].containerCache.over){c.push(function(f){return function(g){f._trigger("out", -g,this._uiHash(this))}}.call(this,this.containers[e]));this.containers[e].containerCache.over=0}}this._storedCursor&&d("body").css("cursor",this._storedCursor);this._storedOpacity&&this.helper.css("opacity",this._storedOpacity);if(this._storedZIndex)this.helper.css("zIndex",this._storedZIndex=="auto"?"":this._storedZIndex);this.dragging=false;if(this.cancelHelperRemoval){if(!b){this._trigger("beforeStop",a,this._uiHash());for(e=0;e li > :first-child,> :not(li):even",icons:{header:"ui-icon-triangle-1-e",headerSelected:"ui-icon-triangle-1-s"},navigation:false,navigationFilter:function(){return this.href.toLowerCase()===location.href.toLowerCase()}},_create:function(){var a=this,b=a.options;a.running=0;a.element.addClass("ui-accordion ui-widget ui-helper-reset").children("li").addClass("ui-accordion-li-fix"); -a.headers=a.element.find(b.header).addClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all").bind("mouseenter.accordion",function(){b.disabled||c(this).addClass("ui-state-hover")}).bind("mouseleave.accordion",function(){b.disabled||c(this).removeClass("ui-state-hover")}).bind("focus.accordion",function(){b.disabled||c(this).addClass("ui-state-focus")}).bind("blur.accordion",function(){b.disabled||c(this).removeClass("ui-state-focus")});a.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom"); -if(b.navigation){var d=a.element.find("a").filter(b.navigationFilter).eq(0);if(d.length){var h=d.closest(".ui-accordion-header");a.active=h.length?h:d.closest(".ui-accordion-content").prev()}}a.active=a._findActive(a.active||b.active).addClass("ui-state-default ui-state-active").toggleClass("ui-corner-all").toggleClass("ui-corner-top");a.active.next().addClass("ui-accordion-content-active");a._createIcons();a.resize();a.element.attr("role","tablist");a.headers.attr("role","tab").bind("keydown.accordion", -function(f){return a._keydown(f)}).next().attr("role","tabpanel");a.headers.not(a.active||"").attr({"aria-expanded":"false",tabIndex:-1}).next().hide();a.active.length?a.active.attr({"aria-expanded":"true",tabIndex:0}):a.headers.eq(0).attr("tabIndex",0);c.browser.safari||a.headers.find("a").attr("tabIndex",-1);b.event&&a.headers.bind(b.event.split(" ").join(".accordion ")+".accordion",function(f){a._clickHandler.call(a,f,this);f.preventDefault()})},_createIcons:function(){var a=this.options;if(a.icons){c("").addClass("ui-icon "+ -a.icons.header).prependTo(this.headers);this.active.children(".ui-icon").toggleClass(a.icons.header).toggleClass(a.icons.headerSelected);this.element.addClass("ui-accordion-icons")}},_destroyIcons:function(){this.headers.children(".ui-icon").remove();this.element.removeClass("ui-accordion-icons")},destroy:function(){var a=this.options;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role");this.headers.unbind(".accordion").removeClass("ui-accordion-header ui-accordion-disabled ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top").removeAttr("role").removeAttr("aria-expanded").removeAttr("tabIndex"); -this.headers.find("a").removeAttr("tabIndex");this._destroyIcons();var b=this.headers.next().css("display","").removeAttr("role").removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-accordion-disabled ui-state-disabled");if(a.autoHeight||a.fillHeight)b.css("height","");return c.Widget.prototype.destroy.call(this)},_setOption:function(a,b){c.Widget.prototype._setOption.apply(this,arguments);a=="active"&&this.activate(b);if(a=="icons"){this._destroyIcons(); -b&&this._createIcons()}if(a=="disabled")this.headers.add(this.headers.next())[b?"addClass":"removeClass"]("ui-accordion-disabled ui-state-disabled")},_keydown:function(a){if(!(this.options.disabled||a.altKey||a.ctrlKey)){var b=c.ui.keyCode,d=this.headers.length,h=this.headers.index(a.target),f=false;switch(a.keyCode){case b.RIGHT:case b.DOWN:f=this.headers[(h+1)%d];break;case b.LEFT:case b.UP:f=this.headers[(h-1+d)%d];break;case b.SPACE:case b.ENTER:this._clickHandler({target:a.target},a.target); -a.preventDefault()}if(f){c(a.target).attr("tabIndex",-1);c(f).attr("tabIndex",0);f.focus();return false}return true}},resize:function(){var a=this.options,b;if(a.fillSpace){if(c.browser.msie){var d=this.element.parent().css("overflow");this.element.parent().css("overflow","hidden")}b=this.element.parent().height();c.browser.msie&&this.element.parent().css("overflow",d);this.headers.each(function(){b-=c(this).outerHeight(true)});this.headers.next().each(function(){c(this).height(Math.max(0,b-c(this).innerHeight()+ -c(this).height()))}).css("overflow","auto")}else if(a.autoHeight){b=0;this.headers.next().each(function(){b=Math.max(b,c(this).height("").height())}).height(b)}return this},activate:function(a){this.options.active=a;a=this._findActive(a)[0];this._clickHandler({target:a},a);return this},_findActive:function(a){return a?typeof a==="number"?this.headers.filter(":eq("+a+")"):this.headers.not(this.headers.not(a)):a===false?c([]):this.headers.filter(":eq(0)")},_clickHandler:function(a,b){var d=this.options; -if(!d.disabled)if(a.target){a=c(a.currentTarget||b);b=a[0]===this.active[0];d.active=d.collapsible&&b?false:this.headers.index(a);if(!(this.running||!d.collapsible&&b)){var h=this.active;j=a.next();g=this.active.next();e={options:d,newHeader:b&&d.collapsible?c([]):a,oldHeader:this.active,newContent:b&&d.collapsible?c([]):j,oldContent:g};var f=this.headers.index(this.active[0])>this.headers.index(a[0]);this.active=b?c([]):a;this._toggle(j,g,e,b,f);h.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").children(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header); -if(!b){a.removeClass("ui-state-default ui-corner-all").addClass("ui-state-active ui-corner-top").children(".ui-icon").removeClass(d.icons.header).addClass(d.icons.headerSelected);a.next().addClass("ui-accordion-content-active")}}}else if(d.collapsible){this.active.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").children(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header);this.active.next().addClass("ui-accordion-content-active");var g=this.active.next(), -e={options:d,newHeader:c([]),oldHeader:d.active,newContent:c([]),oldContent:g},j=this.active=c([]);this._toggle(j,g,e)}},_toggle:function(a,b,d,h,f){var g=this,e=g.options;g.toShow=a;g.toHide=b;g.data=d;var j=function(){if(g)return g._completed.apply(g,arguments)};g._trigger("changestart",null,g.data);g.running=b.size()===0?a.size():b.size();if(e.animated){d={};d=e.collapsible&&h?{toShow:c([]),toHide:b,complete:j,down:f,autoHeight:e.autoHeight||e.fillSpace}:{toShow:a,toHide:b,complete:j,down:f,autoHeight:e.autoHeight|| -e.fillSpace};if(!e.proxied)e.proxied=e.animated;if(!e.proxiedDuration)e.proxiedDuration=e.duration;e.animated=c.isFunction(e.proxied)?e.proxied(d):e.proxied;e.duration=c.isFunction(e.proxiedDuration)?e.proxiedDuration(d):e.proxiedDuration;h=c.ui.accordion.animations;var i=e.duration,k=e.animated;if(k&&!h[k]&&!c.easing[k])k="slide";h[k]||(h[k]=function(l){this.slide(l,{easing:k,duration:i||700})});h[k](d)}else{if(e.collapsible&&h)a.toggle();else{b.hide();a.show()}j(true)}b.prev().attr({"aria-expanded":"false", -tabIndex:-1}).blur();a.prev().attr({"aria-expanded":"true",tabIndex:0}).focus()},_completed:function(a){this.running=a?0:--this.running;if(!this.running){this.options.clearStyle&&this.toShow.add(this.toHide).css({height:"",overflow:""});this.toHide.removeClass("ui-accordion-content-active");if(this.toHide.length)this.toHide.parent()[0].className=this.toHide.parent()[0].className;this._trigger("change",null,this.data)}}});c.extend(c.ui.accordion,{version:"1.8.9",animations:{slide:function(a,b){a= -c.extend({easing:"swing",duration:300},a,b);if(a.toHide.size())if(a.toShow.size()){var d=a.toShow.css("overflow"),h=0,f={},g={},e;b=a.toShow;e=b[0].style.width;b.width(parseInt(b.parent().width(),10)-parseInt(b.css("paddingLeft"),10)-parseInt(b.css("paddingRight"),10)-(parseInt(b.css("borderLeftWidth"),10)||0)-(parseInt(b.css("borderRightWidth"),10)||0));c.each(["height","paddingTop","paddingBottom"],function(j,i){g[i]="hide";j=(""+c.css(a.toShow[0],i)).match(/^([\d+-.]+)(.*)$/);f[i]={value:j[1], -unit:j[2]||"px"}});a.toShow.css({height:0,overflow:"hidden"}).show();a.toHide.filter(":hidden").each(a.complete).end().filter(":visible").animate(g,{step:function(j,i){if(i.prop=="height")h=i.end-i.start===0?0:(i.now-i.start)/(i.end-i.start);a.toShow[0].style[i.prop]=h*f[i.prop].value+f[i.prop].unit},duration:a.duration,easing:a.easing,complete:function(){a.autoHeight||a.toShow.css("height","");a.toShow.css({width:e,overflow:d});a.complete()}})}else a.toHide.animate({height:"hide",paddingTop:"hide", -paddingBottom:"hide"},a);else a.toShow.animate({height:"show",paddingTop:"show",paddingBottom:"show"},a)},bounceslide:function(a){this.slide(a,{easing:a.down?"easeOutBounce":"swing",duration:a.down?1E3:200})}}})})(jQuery); -;/* - * jQuery UI Autocomplete 1.8.9 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Autocomplete - * - * Depends: - * jquery.ui.core.js - * jquery.ui.widget.js - * jquery.ui.position.js - */ -(function(d){d.widget("ui.autocomplete",{options:{appendTo:"body",delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null},pending:0,_create:function(){var a=this,b=this.element[0].ownerDocument,f;this.element.addClass("ui-autocomplete-input").attr("autocomplete","off").attr({role:"textbox","aria-autocomplete":"list","aria-haspopup":"true"}).bind("keydown.autocomplete",function(c){if(!(a.options.disabled||a.element.attr("readonly"))){f=false;var e=d.ui.keyCode; -switch(c.keyCode){case e.PAGE_UP:a._move("previousPage",c);break;case e.PAGE_DOWN:a._move("nextPage",c);break;case e.UP:a._move("previous",c);c.preventDefault();break;case e.DOWN:a._move("next",c);c.preventDefault();break;case e.ENTER:case e.NUMPAD_ENTER:if(a.menu.active){f=true;c.preventDefault()}case e.TAB:if(!a.menu.active)return;a.menu.select(c);break;case e.ESCAPE:a.element.val(a.term);a.close(c);break;default:clearTimeout(a.searching);a.searching=setTimeout(function(){if(a.term!=a.element.val()){a.selectedItem= -null;a.search(null,c)}},a.options.delay);break}}}).bind("keypress.autocomplete",function(c){if(f){f=false;c.preventDefault()}}).bind("focus.autocomplete",function(){if(!a.options.disabled){a.selectedItem=null;a.previous=a.element.val()}}).bind("blur.autocomplete",function(c){if(!a.options.disabled){clearTimeout(a.searching);a.closing=setTimeout(function(){a.close(c);a._change(c)},150)}});this._initSource();this.response=function(){return a._response.apply(a,arguments)};this.menu=d("
    ").addClass("ui-autocomplete").appendTo(d(this.options.appendTo|| -"body",b)[0]).mousedown(function(c){var e=a.menu.element[0];d(c.target).closest(".ui-menu-item").length||setTimeout(function(){d(document).one("mousedown",function(g){g.target!==a.element[0]&&g.target!==e&&!d.ui.contains(e,g.target)&&a.close()})},1);setTimeout(function(){clearTimeout(a.closing)},13)}).menu({focus:function(c,e){e=e.item.data("item.autocomplete");false!==a._trigger("focus",c,{item:e})&&/^key/.test(c.originalEvent.type)&&a.element.val(e.value)},selected:function(c,e){var g=e.item.data("item.autocomplete"), -h=a.previous;if(a.element[0]!==b.activeElement){a.element.focus();a.previous=h;setTimeout(function(){a.previous=h;a.selectedItem=g},1)}false!==a._trigger("select",c,{item:g})&&a.element.val(g.value);a.term=a.element.val();a.close(c);a.selectedItem=g},blur:function(){a.menu.element.is(":visible")&&a.element.val()!==a.term&&a.element.val(a.term)}}).zIndex(this.element.zIndex()+1).css({top:0,left:0}).hide().data("menu");d.fn.bgiframe&&this.menu.element.bgiframe()},destroy:function(){this.element.removeClass("ui-autocomplete-input").removeAttr("autocomplete").removeAttr("role").removeAttr("aria-autocomplete").removeAttr("aria-haspopup"); -this.menu.element.remove();d.Widget.prototype.destroy.call(this)},_setOption:function(a,b){d.Widget.prototype._setOption.apply(this,arguments);a==="source"&&this._initSource();if(a==="appendTo")this.menu.element.appendTo(d(b||"body",this.element[0].ownerDocument)[0]);a==="disabled"&&b&&this.xhr&&this.xhr.abort()},_initSource:function(){var a=this,b,f;if(d.isArray(this.options.source)){b=this.options.source;this.source=function(c,e){e(d.ui.autocomplete.filter(b,c.term))}}else if(typeof this.options.source=== -"string"){f=this.options.source;this.source=function(c,e){a.xhr&&a.xhr.abort();a.xhr=d.ajax({url:f,data:c,dataType:"json",success:function(g,h,i){i===a.xhr&&e(g);a.xhr=null},error:function(g){g===a.xhr&&e([]);a.xhr=null}})}}else this.source=this.options.source},search:function(a,b){a=a!=null?a:this.element.val();this.term=this.element.val();if(a.length").data("item.autocomplete",b).append(d("").text(b.label)).appendTo(a)},_move:function(a,b){if(this.menu.element.is(":visible"))if(this.menu.first()&&/^previous/.test(a)||this.menu.last()&&/^next/.test(a)){this.element.val(this.term);this.menu.deactivate()}else this.menu[a](b); -else this.search(null,b)},widget:function(){return this.menu.element}});d.extend(d.ui.autocomplete,{escapeRegex:function(a){return a.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&")},filter:function(a,b){var f=new RegExp(d.ui.autocomplete.escapeRegex(b),"i");return d.grep(a,function(c){return f.test(c.label||c.value||c)})}})})(jQuery); -(function(d){d.widget("ui.menu",{_create:function(){var a=this;this.element.addClass("ui-menu ui-widget ui-widget-content ui-corner-all").attr({role:"listbox","aria-activedescendant":"ui-active-menuitem"}).click(function(b){if(d(b.target).closest(".ui-menu-item a").length){b.preventDefault();a.select(b)}});this.refresh()},refresh:function(){var a=this;this.element.children("li:not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","menuitem").children("a").addClass("ui-corner-all").attr("tabindex", --1).mouseenter(function(b){a.activate(b,d(this).parent())}).mouseleave(function(){a.deactivate()})},activate:function(a,b){this.deactivate();if(this.hasScroll()){var f=b.offset().top-this.element.offset().top,c=this.element.attr("scrollTop"),e=this.element.height();if(f<0)this.element.attr("scrollTop",c+f);else f>=e&&this.element.attr("scrollTop",c+f-e+b.height())}this.active=b.eq(0).children("a").addClass("ui-state-hover").attr("id","ui-active-menuitem").end();this._trigger("focus",a,{item:b})}, -deactivate:function(){if(this.active){this.active.children("a").removeClass("ui-state-hover").removeAttr("id");this._trigger("blur");this.active=null}},next:function(a){this.move("next",".ui-menu-item:first",a)},previous:function(a){this.move("prev",".ui-menu-item:last",a)},first:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},last:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},move:function(a,b,f){if(this.active){a=this.active[a+"All"](".ui-menu-item").eq(0); -a.length?this.activate(f,a):this.activate(f,this.element.children(b))}else this.activate(f,this.element.children(b))},nextPage:function(a){if(this.hasScroll())if(!this.active||this.last())this.activate(a,this.element.children(".ui-menu-item:first"));else{var b=this.active.offset().top,f=this.element.height(),c=this.element.children(".ui-menu-item").filter(function(){var e=d(this).offset().top-b-f+d(this).height();return e<10&&e>-10});c.length||(c=this.element.children(".ui-menu-item:last"));this.activate(a, -c)}else this.activate(a,this.element.children(".ui-menu-item").filter(!this.active||this.last()?":first":":last"))},previousPage:function(a){if(this.hasScroll())if(!this.active||this.first())this.activate(a,this.element.children(".ui-menu-item:last"));else{var b=this.active.offset().top,f=this.element.height();result=this.element.children(".ui-menu-item").filter(function(){var c=d(this).offset().top-b+f-d(this).height();return c<10&&c>-10});result.length||(result=this.element.children(".ui-menu-item:first")); -this.activate(a,result)}else this.activate(a,this.element.children(".ui-menu-item").filter(!this.active||this.first()?":last":":first"))},hasScroll:function(){return this.element.height()").addClass("ui-button-text").html(this.options.label).appendTo(b.empty()).text(),d=this.options.icons,e=d.primary&&d.secondary;if(d.primary||d.secondary){b.addClass("ui-button-text-icon"+(e?"s":d.primary?"-primary":"-secondary"));d.primary&&b.prepend("");d.secondary&&b.append("");if(!this.options.text){b.addClass(e?"ui-button-icons-only":"ui-button-icon-only").removeClass("ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary"); -this.hasTitle||b.attr("title",c)}}else b.addClass("ui-button-text-only")}}});a.widget("ui.buttonset",{options:{items:":button, :submit, :reset, :checkbox, :radio, a, :data(button)"},_create:function(){this.element.addClass("ui-buttonset")},_init:function(){this.refresh()},_setOption:function(b,c){b==="disabled"&&this.buttons.button("option",b,c);a.Widget.prototype._setOption.apply(this,arguments)},refresh:function(){this.buttons=this.element.find(this.options.items).filter(":ui-button").button("refresh").end().not(":ui-button").button().end().map(function(){return a(this).button("widget")[0]}).removeClass("ui-corner-all ui-corner-left ui-corner-right").filter(":first").addClass("ui-corner-left").end().filter(":last").addClass("ui-corner-right").end().end()}, -destroy:function(){this.element.removeClass("ui-buttonset");this.buttons.map(function(){return a(this).button("widget")[0]}).removeClass("ui-corner-left ui-corner-right").end().button("destroy");a.Widget.prototype.destroy.call(this)}})})(jQuery); -;/* - * jQuery UI Dialog 1.8.9 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Dialog - * - * Depends: - * jquery.ui.core.js - * jquery.ui.widget.js - * jquery.ui.button.js - * jquery.ui.draggable.js - * jquery.ui.mouse.js - * jquery.ui.position.js - * jquery.ui.resizable.js - */ -(function(c,j){var k={buttons:true,height:true,maxHeight:true,maxWidth:true,minHeight:true,minWidth:true,width:true},l={maxHeight:true,maxWidth:true,minHeight:true,minWidth:true};c.widget("ui.dialog",{options:{autoOpen:true,buttons:{},closeOnEscape:true,closeText:"close",dialogClass:"",draggable:true,hide:null,height:"auto",maxHeight:false,maxWidth:false,minHeight:150,minWidth:150,modal:false,position:{my:"center",at:"center",collision:"fit",using:function(a){var b=c(this).css(a).offset().top;b<0&& -c(this).css("top",a.top-b)}},resizable:true,show:null,stack:true,title:"",width:300,zIndex:1E3},_create:function(){this.originalTitle=this.element.attr("title");if(typeof this.originalTitle!=="string")this.originalTitle="";this.options.title=this.options.title||this.originalTitle;var a=this,b=a.options,d=b.title||" ",e=c.ui.dialog.getTitleId(a.element),g=(a.uiDialog=c("
    ")).appendTo(document.body).hide().addClass("ui-dialog ui-widget ui-widget-content ui-corner-all "+b.dialogClass).css({zIndex:b.zIndex}).attr("tabIndex", --1).css("outline",0).keydown(function(i){if(b.closeOnEscape&&i.keyCode&&i.keyCode===c.ui.keyCode.ESCAPE){a.close(i);i.preventDefault()}}).attr({role:"dialog","aria-labelledby":e}).mousedown(function(i){a.moveToTop(false,i)});a.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(g);var f=(a.uiDialogTitlebar=c("
    ")).addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix").prependTo(g),h=c('').addClass("ui-dialog-titlebar-close ui-corner-all").attr("role", -"button").hover(function(){h.addClass("ui-state-hover")},function(){h.removeClass("ui-state-hover")}).focus(function(){h.addClass("ui-state-focus")}).blur(function(){h.removeClass("ui-state-focus")}).click(function(i){a.close(i);return false}).appendTo(f);(a.uiDialogTitlebarCloseText=c("")).addClass("ui-icon ui-icon-closethick").text(b.closeText).appendTo(h);c("").addClass("ui-dialog-title").attr("id",e).html(d).prependTo(f);if(c.isFunction(b.beforeclose)&&!c.isFunction(b.beforeClose))b.beforeClose= -b.beforeclose;f.find("*").add(f).disableSelection();b.draggable&&c.fn.draggable&&a._makeDraggable();b.resizable&&c.fn.resizable&&a._makeResizable();a._createButtons(b.buttons);a._isOpen=false;c.fn.bgiframe&&g.bgiframe()},_init:function(){this.options.autoOpen&&this.open()},destroy:function(){var a=this;a.overlay&&a.overlay.destroy();a.uiDialog.hide();a.element.unbind(".dialog").removeData("dialog").removeClass("ui-dialog-content ui-widget-content").hide().appendTo("body");a.uiDialog.remove();a.originalTitle&& -a.element.attr("title",a.originalTitle);return a},widget:function(){return this.uiDialog},close:function(a){var b=this,d,e;if(false!==b._trigger("beforeClose",a)){b.overlay&&b.overlay.destroy();b.uiDialog.unbind("keypress.ui-dialog");b._isOpen=false;if(b.options.hide)b.uiDialog.hide(b.options.hide,function(){b._trigger("close",a)});else{b.uiDialog.hide();b._trigger("close",a)}c.ui.dialog.overlay.resize();if(b.options.modal){d=0;c(".ui-dialog").each(function(){if(this!==b.uiDialog[0]){e=c(this).css("z-index"); -isNaN(e)||(d=Math.max(d,e))}});c.ui.dialog.maxZ=d}return b}},isOpen:function(){return this._isOpen},moveToTop:function(a,b){var d=this,e=d.options;if(e.modal&&!a||!e.stack&&!e.modal)return d._trigger("focus",b);if(e.zIndex>c.ui.dialog.maxZ)c.ui.dialog.maxZ=e.zIndex;if(d.overlay){c.ui.dialog.maxZ+=1;d.overlay.$el.css("z-index",c.ui.dialog.overlay.maxZ=c.ui.dialog.maxZ)}a={scrollTop:d.element.attr("scrollTop"),scrollLeft:d.element.attr("scrollLeft")};c.ui.dialog.maxZ+=1;d.uiDialog.css("z-index",c.ui.dialog.maxZ); -d.element.attr(a);d._trigger("focus",b);return d},open:function(){if(!this._isOpen){var a=this,b=a.options,d=a.uiDialog;a.overlay=b.modal?new c.ui.dialog.overlay(a):null;a._size();a._position(b.position);d.show(b.show);a.moveToTop(true);b.modal&&d.bind("keypress.ui-dialog",function(e){if(e.keyCode===c.ui.keyCode.TAB){var g=c(":tabbable",this),f=g.filter(":first");g=g.filter(":last");if(e.target===g[0]&&!e.shiftKey){f.focus(1);return false}else if(e.target===f[0]&&e.shiftKey){g.focus(1);return false}}}); -c(a.element.find(":tabbable").get().concat(d.find(".ui-dialog-buttonpane :tabbable").get().concat(d.get()))).eq(0).focus();a._isOpen=true;a._trigger("open");return a}},_createButtons:function(a){var b=this,d=false,e=c("
    ").addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"),g=c("
    ").addClass("ui-dialog-buttonset").appendTo(e);b.uiDialog.find(".ui-dialog-buttonpane").remove();typeof a==="object"&&a!==null&&c.each(a,function(){return!(d=true)});if(d){c.each(a,function(f, -h){h=c.isFunction(h)?{click:h,text:f}:h;f=c('').attr(h,true).unbind("click").click(function(){h.click.apply(b.element[0],arguments)}).appendTo(g);c.fn.button&&f.button()});e.appendTo(b.uiDialog)}},_makeDraggable:function(){function a(f){return{position:f.position,offset:f.offset}}var b=this,d=b.options,e=c(document),g;b.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",handle:".ui-dialog-titlebar",containment:"document",start:function(f,h){g= -d.height==="auto"?"auto":c(this).height();c(this).height(c(this).height()).addClass("ui-dialog-dragging");b._trigger("dragStart",f,a(h))},drag:function(f,h){b._trigger("drag",f,a(h))},stop:function(f,h){d.position=[h.position.left-e.scrollLeft(),h.position.top-e.scrollTop()];c(this).removeClass("ui-dialog-dragging").height(g);b._trigger("dragStop",f,a(h));c.ui.dialog.overlay.resize()}})},_makeResizable:function(a){function b(f){return{originalPosition:f.originalPosition,originalSize:f.originalSize, -position:f.position,size:f.size}}a=a===j?this.options.resizable:a;var d=this,e=d.options,g=d.uiDialog.css("position");a=typeof a==="string"?a:"n,e,s,w,se,sw,ne,nw";d.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:d.element,maxWidth:e.maxWidth,maxHeight:e.maxHeight,minWidth:e.minWidth,minHeight:d._minHeight(),handles:a,start:function(f,h){c(this).addClass("ui-dialog-resizing");d._trigger("resizeStart",f,b(h))},resize:function(f,h){d._trigger("resize",f,b(h))},stop:function(f, -h){c(this).removeClass("ui-dialog-resizing");e.height=c(this).height();e.width=c(this).width();d._trigger("resizeStop",f,b(h));c.ui.dialog.overlay.resize()}}).css("position",g).find(".ui-resizable-se").addClass("ui-icon ui-icon-grip-diagonal-se")},_minHeight:function(){var a=this.options;return a.height==="auto"?a.minHeight:Math.min(a.minHeight,a.height)},_position:function(a){var b=[],d=[0,0],e;if(a){if(typeof a==="string"||typeof a==="object"&&"0"in a){b=a.split?a.split(" "):[a[0],a[1]];if(b.length=== -1)b[1]=b[0];c.each(["left","top"],function(g,f){if(+b[g]===b[g]){d[g]=b[g];b[g]=f}});a={my:b.join(" "),at:b.join(" "),offset:d.join(" ")}}a=c.extend({},c.ui.dialog.prototype.options.position,a)}else a=c.ui.dialog.prototype.options.position;(e=this.uiDialog.is(":visible"))||this.uiDialog.show();this.uiDialog.css({top:0,left:0}).position(c.extend({of:window},a));e||this.uiDialog.hide()},_setOptions:function(a){var b=this,d={},e=false;c.each(a,function(g,f){b._setOption(g,f);if(g in k)e=true;if(g in -l)d[g]=f});e&&this._size();this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option",d)},_setOption:function(a,b){var d=this,e=d.uiDialog;switch(a){case "beforeclose":a="beforeClose";break;case "buttons":d._createButtons(b);break;case "closeText":d.uiDialogTitlebarCloseText.text(""+b);break;case "dialogClass":e.removeClass(d.options.dialogClass).addClass("ui-dialog ui-widget ui-widget-content ui-corner-all "+b);break;case "disabled":b?e.addClass("ui-dialog-disabled"):e.removeClass("ui-dialog-disabled"); -break;case "draggable":var g=e.is(":data(draggable)");g&&!b&&e.draggable("destroy");!g&&b&&d._makeDraggable();break;case "position":d._position(b);break;case "resizable":(g=e.is(":data(resizable)"))&&!b&&e.resizable("destroy");g&&typeof b==="string"&&e.resizable("option","handles",b);!g&&b!==false&&d._makeResizable(b);break;case "title":c(".ui-dialog-title",d.uiDialogTitlebar).html(""+(b||" "));break}c.Widget.prototype._setOption.apply(d,arguments)},_size:function(){var a=this.options,b,d,e= -this.uiDialog.is(":visible");this.element.show().css({width:"auto",minHeight:0,height:0});if(a.minWidth>a.width)a.width=a.minWidth;b=this.uiDialog.css({height:"auto",width:a.width}).height();d=Math.max(0,a.minHeight-b);if(a.height==="auto")if(c.support.minHeight)this.element.css({minHeight:d,height:"auto"});else{this.uiDialog.show();a=this.element.css("height","auto").height();e||this.uiDialog.hide();this.element.height(Math.max(a,d))}else this.element.height(Math.max(a.height-b,0));this.uiDialog.is(":data(resizable)")&& -this.uiDialog.resizable("option","minHeight",this._minHeight())}});c.extend(c.ui.dialog,{version:"1.8.9",uuid:0,maxZ:0,getTitleId:function(a){a=a.attr("id");if(!a){this.uuid+=1;a=this.uuid}return"ui-dialog-title-"+a},overlay:function(a){this.$el=c.ui.dialog.overlay.create(a)}});c.extend(c.ui.dialog.overlay,{instances:[],oldInstances:[],maxZ:0,events:c.map("focus,mousedown,mouseup,keydown,keypress,click".split(","),function(a){return a+".dialog-overlay"}).join(" "),create:function(a){if(this.instances.length=== -0){setTimeout(function(){c.ui.dialog.overlay.instances.length&&c(document).bind(c.ui.dialog.overlay.events,function(d){if(c(d.target).zIndex()").addClass("ui-widget-overlay")).appendTo(document.body).css({width:this.width(), -height:this.height()});c.fn.bgiframe&&b.bgiframe();this.instances.push(b);return b},destroy:function(a){var b=c.inArray(a,this.instances);b!=-1&&this.oldInstances.push(this.instances.splice(b,1)[0]);this.instances.length===0&&c([document,window]).unbind(".dialog-overlay");a.remove();var d=0;c.each(this.instances,function(){d=Math.max(d,this.css("z-index"))});this.maxZ=d},height:function(){var a,b;if(c.browser.msie&&c.browser.version<7){a=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight); -b=Math.max(document.documentElement.offsetHeight,document.body.offsetHeight);return a");if(!a.values)a.values=[this._valueMin(),this._valueMin()];if(a.values.length&&a.values.length!==2)a.values=[a.values[0],a.values[0]]}else this.range=d("
    ");this.range.appendTo(this.element).addClass("ui-slider-range");if(a.range==="min"||a.range==="max")this.range.addClass("ui-slider-range-"+a.range);this.range.addClass("ui-widget-header")}d(".ui-slider-handle",this.element).length===0&&d("").appendTo(this.element).addClass("ui-slider-handle"); -if(a.values&&a.values.length)for(;d(".ui-slider-handle",this.element).length").appendTo(this.element).addClass("ui-slider-handle");this.handles=d(".ui-slider-handle",this.element).addClass("ui-state-default ui-corner-all");this.handle=this.handles.eq(0);this.handles.add(this.range).filter("a").click(function(c){c.preventDefault()}).hover(function(){a.disabled||d(this).addClass("ui-state-hover")},function(){d(this).removeClass("ui-state-hover")}).focus(function(){if(a.disabled)d(this).blur(); -else{d(".ui-slider .ui-state-focus").removeClass("ui-state-focus");d(this).addClass("ui-state-focus")}}).blur(function(){d(this).removeClass("ui-state-focus")});this.handles.each(function(c){d(this).data("index.ui-slider-handle",c)});this.handles.keydown(function(c){var e=true,f=d(this).data("index.ui-slider-handle"),h,g,i;if(!b.options.disabled){switch(c.keyCode){case d.ui.keyCode.HOME:case d.ui.keyCode.END:case d.ui.keyCode.PAGE_UP:case d.ui.keyCode.PAGE_DOWN:case d.ui.keyCode.UP:case d.ui.keyCode.RIGHT:case d.ui.keyCode.DOWN:case d.ui.keyCode.LEFT:e= -false;if(!b._keySliding){b._keySliding=true;d(this).addClass("ui-state-active");h=b._start(c,f);if(h===false)return}break}i=b.options.step;h=b.options.values&&b.options.values.length?(g=b.values(f)):(g=b.value());switch(c.keyCode){case d.ui.keyCode.HOME:g=b._valueMin();break;case d.ui.keyCode.END:g=b._valueMax();break;case d.ui.keyCode.PAGE_UP:g=b._trimAlignValue(h+(b._valueMax()-b._valueMin())/5);break;case d.ui.keyCode.PAGE_DOWN:g=b._trimAlignValue(h-(b._valueMax()-b._valueMin())/5);break;case d.ui.keyCode.UP:case d.ui.keyCode.RIGHT:if(h=== -b._valueMax())return;g=b._trimAlignValue(h+i);break;case d.ui.keyCode.DOWN:case d.ui.keyCode.LEFT:if(h===b._valueMin())return;g=b._trimAlignValue(h-i);break}b._slide(c,f,g);return e}}).keyup(function(c){var e=d(this).data("index.ui-slider-handle");if(b._keySliding){b._keySliding=false;b._stop(c,e);b._change(c,e);d(this).removeClass("ui-state-active")}});this._refreshValue();this._animateOff=false},destroy:function(){this.handles.remove();this.range.remove();this.element.removeClass("ui-slider ui-slider-horizontal ui-slider-vertical ui-slider-disabled ui-widget ui-widget-content ui-corner-all").removeData("slider").unbind(".slider"); -this._mouseDestroy();return this},_mouseCapture:function(b){var a=this.options,c,e,f,h,g;if(a.disabled)return false;this.elementSize={width:this.element.outerWidth(),height:this.element.outerHeight()};this.elementOffset=this.element.offset();c=this._normValueFromMouse({x:b.pageX,y:b.pageY});e=this._valueMax()-this._valueMin()+1;h=this;this.handles.each(function(i){var j=Math.abs(c-h.values(i));if(e>j){e=j;f=d(this);g=i}});if(a.range===true&&this.values(1)===a.min){g+=1;f=d(this.handles[g])}if(this._start(b, -g)===false)return false;this._mouseSliding=true;h._handleIndex=g;f.addClass("ui-state-active").focus();a=f.offset();this._clickOffset=!d(b.target).parents().andSelf().is(".ui-slider-handle")?{left:0,top:0}:{left:b.pageX-a.left-f.width()/2,top:b.pageY-a.top-f.height()/2-(parseInt(f.css("borderTopWidth"),10)||0)-(parseInt(f.css("borderBottomWidth"),10)||0)+(parseInt(f.css("marginTop"),10)||0)};this.handles.hasClass("ui-state-hover")||this._slide(b,g,c);return this._animateOff=true},_mouseStart:function(){return true}, -_mouseDrag:function(b){var a=this._normValueFromMouse({x:b.pageX,y:b.pageY});this._slide(b,this._handleIndex,a);return false},_mouseStop:function(b){this.handles.removeClass("ui-state-active");this._mouseSliding=false;this._stop(b,this._handleIndex);this._change(b,this._handleIndex);this._clickOffset=this._handleIndex=null;return this._animateOff=false},_detectOrientation:function(){this.orientation=this.options.orientation==="vertical"?"vertical":"horizontal"},_normValueFromMouse:function(b){var a; -if(this.orientation==="horizontal"){a=this.elementSize.width;b=b.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)}else{a=this.elementSize.height;b=b.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)}a=b/a;if(a>1)a=1;if(a<0)a=0;if(this.orientation==="vertical")a=1-a;b=this._valueMax()-this._valueMin();return this._trimAlignValue(this._valueMin()+a*b)},_start:function(b,a){var c={handle:this.handles[a],value:this.value()};if(this.options.values&&this.options.values.length){c.value= -this.values(a);c.values=this.values()}return this._trigger("start",b,c)},_slide:function(b,a,c){var e;if(this.options.values&&this.options.values.length){e=this.values(a?0:1);if(this.options.values.length===2&&this.options.range===true&&(a===0&&c>e||a===1&&c1){this.options.values[b]=this._trimAlignValue(a);this._refreshValue();this._change(null,b)}if(arguments.length)if(d.isArray(arguments[0])){c=this.options.values;e=arguments[0];for(f=0;f=this._valueMax())return this._valueMax();var a=this.options.step>0?this.options.step:1,c=(b-this._valueMin())%a;alignValue=b-c;if(Math.abs(c)*2>=a)alignValue+=c>0?a:-a;return parseFloat(alignValue.toFixed(5))},_valueMin:function(){return this.options.min},_valueMax:function(){return this.options.max}, -_refreshValue:function(){var b=this.options.range,a=this.options,c=this,e=!this._animateOff?a.animate:false,f,h={},g,i,j,l;if(this.options.values&&this.options.values.length)this.handles.each(function(k){f=(c.values(k)-c._valueMin())/(c._valueMax()-c._valueMin())*100;h[c.orientation==="horizontal"?"left":"bottom"]=f+"%";d(this).stop(1,1)[e?"animate":"css"](h,a.animate);if(c.options.range===true)if(c.orientation==="horizontal"){if(k===0)c.range.stop(1,1)[e?"animate":"css"]({left:f+"%"},a.animate); -if(k===1)c.range[e?"animate":"css"]({width:f-g+"%"},{queue:false,duration:a.animate})}else{if(k===0)c.range.stop(1,1)[e?"animate":"css"]({bottom:f+"%"},a.animate);if(k===1)c.range[e?"animate":"css"]({height:f-g+"%"},{queue:false,duration:a.animate})}g=f});else{i=this.value();j=this._valueMin();l=this._valueMax();f=l!==j?(i-j)/(l-j)*100:0;h[c.orientation==="horizontal"?"left":"bottom"]=f+"%";this.handle.stop(1,1)[e?"animate":"css"](h,a.animate);if(b==="min"&&this.orientation==="horizontal")this.range.stop(1, -1)[e?"animate":"css"]({width:f+"%"},a.animate);if(b==="max"&&this.orientation==="horizontal")this.range[e?"animate":"css"]({width:100-f+"%"},{queue:false,duration:a.animate});if(b==="min"&&this.orientation==="vertical")this.range.stop(1,1)[e?"animate":"css"]({height:f+"%"},a.animate);if(b==="max"&&this.orientation==="vertical")this.range[e?"animate":"css"]({height:100-f+"%"},{queue:false,duration:a.animate})}}});d.extend(d.ui.slider,{version:"1.8.9"})})(jQuery); -;/* - * jQuery UI Tabs 1.8.9 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Tabs - * - * Depends: - * jquery.ui.core.js - * jquery.ui.widget.js - */ -(function(d,p){function u(){return++v}function w(){return++x}var v=0,x=0;d.widget("ui.tabs",{options:{add:null,ajaxOptions:null,cache:false,cookie:null,collapsible:false,disable:null,disabled:[],enable:null,event:"click",fx:null,idPrefix:"ui-tabs-",load:null,panelTemplate:"
    ",remove:null,select:null,show:null,spinner:"Loading…",tabTemplate:"
  • #{label}
  • "},_create:function(){this._tabify(true)},_setOption:function(b,e){if(b=="selected")this.options.collapsible&& -e==this.options.selected||this.select(e);else{this.options[b]=e;this._tabify()}},_tabId:function(b){return b.title&&b.title.replace(/\s/g,"_").replace(/[^\w\u00c0-\uFFFF-]/g,"")||this.options.idPrefix+u()},_sanitizeSelector:function(b){return b.replace(/:/g,"\\:")},_cookie:function(){var b=this.cookie||(this.cookie=this.options.cookie.name||"ui-tabs-"+w());return d.cookie.apply(null,[b].concat(d.makeArray(arguments)))},_ui:function(b,e){return{tab:b,panel:e,index:this.anchors.index(b)}},_cleanup:function(){this.lis.filter(".ui-state-processing").removeClass("ui-state-processing").find("span:data(label.tabs)").each(function(){var b= -d(this);b.html(b.data("label.tabs")).removeData("label.tabs")})},_tabify:function(b){function e(g,f){g.css("display","");!d.support.opacity&&f.opacity&&g[0].style.removeAttribute("filter")}var a=this,c=this.options,h=/^#.+/;this.list=this.element.find("ol,ul").eq(0);this.lis=d(" > li:has(a[href])",this.list);this.anchors=this.lis.map(function(){return d("a",this)[0]});this.panels=d([]);this.anchors.each(function(g,f){var i=d(f).attr("href"),l=i.split("#")[0],q;if(l&&(l===location.toString().split("#")[0]|| -(q=d("base")[0])&&l===q.href)){i=f.hash;f.href=i}if(h.test(i))a.panels=a.panels.add(a.element.find(a._sanitizeSelector(i)));else if(i&&i!=="#"){d.data(f,"href.tabs",i);d.data(f,"load.tabs",i.replace(/#.*$/,""));i=a._tabId(f);f.href="#"+i;f=a.element.find("#"+i);if(!f.length){f=d(c.panelTemplate).attr("id",i).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").insertAfter(a.panels[g-1]||a.list);f.data("destroy.tabs",true)}a.panels=a.panels.add(f)}else c.disabled.push(g)});if(b){this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all"); -this.list.addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all");this.lis.addClass("ui-state-default ui-corner-top");this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom");if(c.selected===p){location.hash&&this.anchors.each(function(g,f){if(f.hash==location.hash){c.selected=g;return false}});if(typeof c.selected!=="number"&&c.cookie)c.selected=parseInt(a._cookie(),10);if(typeof c.selected!=="number"&&this.lis.filter(".ui-tabs-selected").length)c.selected= -this.lis.index(this.lis.filter(".ui-tabs-selected"));c.selected=c.selected||(this.lis.length?0:-1)}else if(c.selected===null)c.selected=-1;c.selected=c.selected>=0&&this.anchors[c.selected]||c.selected<0?c.selected:0;c.disabled=d.unique(c.disabled.concat(d.map(this.lis.filter(".ui-state-disabled"),function(g){return a.lis.index(g)}))).sort();d.inArray(c.selected,c.disabled)!=-1&&c.disabled.splice(d.inArray(c.selected,c.disabled),1);this.panels.addClass("ui-tabs-hide");this.lis.removeClass("ui-tabs-selected ui-state-active"); -if(c.selected>=0&&this.anchors.length){a.element.find(a._sanitizeSelector(a.anchors[c.selected].hash)).removeClass("ui-tabs-hide");this.lis.eq(c.selected).addClass("ui-tabs-selected ui-state-active");a.element.queue("tabs",function(){a._trigger("show",null,a._ui(a.anchors[c.selected],a.element.find(a._sanitizeSelector(a.anchors[c.selected].hash))[0]))});this.load(c.selected)}d(window).bind("unload",function(){a.lis.add(a.anchors).unbind(".tabs");a.lis=a.anchors=a.panels=null})}else c.selected=this.lis.index(this.lis.filter(".ui-tabs-selected")); -this.element[c.collapsible?"addClass":"removeClass"]("ui-tabs-collapsible");c.cookie&&this._cookie(c.selected,c.cookie);b=0;for(var j;j=this.lis[b];b++)d(j)[d.inArray(b,c.disabled)!=-1&&!d(j).hasClass("ui-tabs-selected")?"addClass":"removeClass"]("ui-state-disabled");c.cache===false&&this.anchors.removeData("cache.tabs");this.lis.add(this.anchors).unbind(".tabs");if(c.event!=="mouseover"){var k=function(g,f){f.is(":not(.ui-state-disabled)")&&f.addClass("ui-state-"+g)},n=function(g,f){f.removeClass("ui-state-"+ -g)};this.lis.bind("mouseover.tabs",function(){k("hover",d(this))});this.lis.bind("mouseout.tabs",function(){n("hover",d(this))});this.anchors.bind("focus.tabs",function(){k("focus",d(this).closest("li"))});this.anchors.bind("blur.tabs",function(){n("focus",d(this).closest("li"))})}var m,o;if(c.fx)if(d.isArray(c.fx)){m=c.fx[0];o=c.fx[1]}else m=o=c.fx;var r=o?function(g,f){d(g).closest("li").addClass("ui-tabs-selected ui-state-active");f.hide().removeClass("ui-tabs-hide").animate(o,o.duration||"normal", -function(){e(f,o);a._trigger("show",null,a._ui(g,f[0]))})}:function(g,f){d(g).closest("li").addClass("ui-tabs-selected ui-state-active");f.removeClass("ui-tabs-hide");a._trigger("show",null,a._ui(g,f[0]))},s=m?function(g,f){f.animate(m,m.duration||"normal",function(){a.lis.removeClass("ui-tabs-selected ui-state-active");f.addClass("ui-tabs-hide");e(f,m);a.element.dequeue("tabs")})}:function(g,f){a.lis.removeClass("ui-tabs-selected ui-state-active");f.addClass("ui-tabs-hide");a.element.dequeue("tabs")}; -this.anchors.bind(c.event+".tabs",function(){var g=this,f=d(g).closest("li"),i=a.panels.filter(":not(.ui-tabs-hide)"),l=a.element.find(a._sanitizeSelector(g.hash));if(f.hasClass("ui-tabs-selected")&&!c.collapsible||f.hasClass("ui-state-disabled")||f.hasClass("ui-state-processing")||a.panels.filter(":animated").length||a._trigger("select",null,a._ui(this,l[0]))===false){this.blur();return false}c.selected=a.anchors.index(this);a.abort();if(c.collapsible)if(f.hasClass("ui-tabs-selected")){c.selected= --1;c.cookie&&a._cookie(c.selected,c.cookie);a.element.queue("tabs",function(){s(g,i)}).dequeue("tabs");this.blur();return false}else if(!i.length){c.cookie&&a._cookie(c.selected,c.cookie);a.element.queue("tabs",function(){r(g,l)});a.load(a.anchors.index(this));this.blur();return false}c.cookie&&a._cookie(c.selected,c.cookie);if(l.length){i.length&&a.element.queue("tabs",function(){s(g,i)});a.element.queue("tabs",function(){r(g,l)});a.load(a.anchors.index(this))}else throw"jQuery UI Tabs: Mismatching fragment identifier."; -d.browser.msie&&this.blur()});this.anchors.bind("click.tabs",function(){return false})},_getIndex:function(b){if(typeof b=="string")b=this.anchors.index(this.anchors.filter("[href$="+b+"]"));return b},destroy:function(){var b=this.options;this.abort();this.element.unbind(".tabs").removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible").removeData("tabs");this.list.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all");this.anchors.each(function(){var e= -d.data(this,"href.tabs");if(e)this.href=e;var a=d(this).unbind(".tabs");d.each(["href","load","cache"],function(c,h){a.removeData(h+".tabs")})});this.lis.unbind(".tabs").add(this.panels).each(function(){d.data(this,"destroy.tabs")?d(this).remove():d(this).removeClass("ui-state-default ui-corner-top ui-tabs-selected ui-state-active ui-state-hover ui-state-focus ui-state-disabled ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide")});b.cookie&&this._cookie(null,b.cookie);return this},add:function(b, -e,a){if(a===p)a=this.anchors.length;var c=this,h=this.options;e=d(h.tabTemplate.replace(/#\{href\}/g,b).replace(/#\{label\}/g,e));b=!b.indexOf("#")?b.replace("#",""):this._tabId(d("a",e)[0]);e.addClass("ui-state-default ui-corner-top").data("destroy.tabs",true);var j=c.element.find("#"+b);j.length||(j=d(h.panelTemplate).attr("id",b).data("destroy.tabs",true));j.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide");if(a>=this.lis.length){e.appendTo(this.list);j.appendTo(this.list[0].parentNode)}else{e.insertBefore(this.lis[a]); -j.insertBefore(this.panels[a])}h.disabled=d.map(h.disabled,function(k){return k>=a?++k:k});this._tabify();if(this.anchors.length==1){h.selected=0;e.addClass("ui-tabs-selected ui-state-active");j.removeClass("ui-tabs-hide");this.element.queue("tabs",function(){c._trigger("show",null,c._ui(c.anchors[0],c.panels[0]))});this.load(0)}this._trigger("add",null,this._ui(this.anchors[a],this.panels[a]));return this},remove:function(b){b=this._getIndex(b);var e=this.options,a=this.lis.eq(b).remove(),c=this.panels.eq(b).remove(); -if(a.hasClass("ui-tabs-selected")&&this.anchors.length>1)this.select(b+(b+1=b?--h:h});this._tabify();this._trigger("remove",null,this._ui(a.find("a")[0],c[0]));return this},enable:function(b){b=this._getIndex(b);var e=this.options;if(d.inArray(b,e.disabled)!=-1){this.lis.eq(b).removeClass("ui-state-disabled");e.disabled=d.grep(e.disabled,function(a){return a!=b});this._trigger("enable",null, -this._ui(this.anchors[b],this.panels[b]));return this}},disable:function(b){b=this._getIndex(b);var e=this.options;if(b!=e.selected){this.lis.eq(b).addClass("ui-state-disabled");e.disabled.push(b);e.disabled.sort();this._trigger("disable",null,this._ui(this.anchors[b],this.panels[b]))}return this},select:function(b){b=this._getIndex(b);if(b==-1)if(this.options.collapsible&&this.options.selected!=-1)b=this.options.selected;else return this;this.anchors.eq(b).trigger(this.options.event+".tabs");return this}, -load:function(b){b=this._getIndex(b);var e=this,a=this.options,c=this.anchors.eq(b)[0],h=d.data(c,"load.tabs");this.abort();if(!h||this.element.queue("tabs").length!==0&&d.data(c,"cache.tabs"))this.element.dequeue("tabs");else{this.lis.eq(b).addClass("ui-state-processing");if(a.spinner){var j=d("span",c);j.data("label.tabs",j.html()).html(a.spinner)}this.xhr=d.ajax(d.extend({},a.ajaxOptions,{url:h,success:function(k,n){e.element.find(e._sanitizeSelector(c.hash)).html(k);e._cleanup();a.cache&&d.data(c, -"cache.tabs",true);e._trigger("load",null,e._ui(e.anchors[b],e.panels[b]));try{a.ajaxOptions.success(k,n)}catch(m){}},error:function(k,n){e._cleanup();e._trigger("load",null,e._ui(e.anchors[b],e.panels[b]));try{a.ajaxOptions.error(k,n,b,c)}catch(m){}}}));e.element.dequeue("tabs");return this}},abort:function(){this.element.queue([]);this.panels.stop(false,true);this.element.queue("tabs",this.element.queue("tabs").splice(-2,2));if(this.xhr){this.xhr.abort();delete this.xhr}this._cleanup();return this}, -url:function(b,e){this.anchors.eq(b).removeData("cache.tabs").data("load.tabs",e);return this},length:function(){return this.anchors.length}});d.extend(d.ui.tabs,{version:"1.8.9"});d.extend(d.ui.tabs.prototype,{rotation:null,rotate:function(b,e){var a=this,c=this.options,h=a._rotate||(a._rotate=function(j){clearTimeout(a.rotation);a.rotation=setTimeout(function(){var k=c.selected;a.select(++k')}function E(a,b){d.extend(a,b);for(var c in b)if(b[c]== -null||b[c]==G)a[c]=b[c];return a}d.extend(d.ui,{datepicker:{version:"1.8.9"}});var y=(new Date).getTime();d.extend(K.prototype,{markerClassName:"hasDatepicker",log:function(){this.debug&&console.log.apply("",arguments)},_widgetDatepicker:function(){return this.dpDiv},setDefaults:function(a){E(this._defaults,a||{});return this},_attachDatepicker:function(a,b){var c=null;for(var e in this._defaults){var f=a.getAttribute("date:"+e);if(f){c=c||{};try{c[e]=eval(f)}catch(h){c[e]=f}}}e=a.nodeName.toLowerCase(); -f=e=="div"||e=="span";if(!a.id){this.uuid+=1;a.id="dp"+this.uuid}var i=this._newInst(d(a),f);i.settings=d.extend({},b||{},c||{});if(e=="input")this._connectDatepicker(a,i);else f&&this._inlineDatepicker(a,i)},_newInst:function(a,b){return{id:a[0].id.replace(/([^A-Za-z0-9_-])/g,"\\\\$1"),input:a,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:b,dpDiv:!b?this.dpDiv:d('
    ')}}, -_connectDatepicker:function(a,b){var c=d(a);b.append=d([]);b.trigger=d([]);if(!c.hasClass(this.markerClassName)){this._attachments(c,b);c.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress).keyup(this._doKeyUp).bind("setData.datepicker",function(e,f,h){b.settings[f]=h}).bind("getData.datepicker",function(e,f){return this._get(b,f)});this._autoSize(b);d.data(a,"datepicker",b)}},_attachments:function(a,b){var c=this._get(b,"appendText"),e=this._get(b,"isRTL");b.append&& -b.append.remove();if(c){b.append=d(''+c+"");a[e?"before":"after"](b.append)}a.unbind("focus",this._showDatepicker);b.trigger&&b.trigger.remove();c=this._get(b,"showOn");if(c=="focus"||c=="both")a.focus(this._showDatepicker);if(c=="button"||c=="both"){c=this._get(b,"buttonText");var f=this._get(b,"buttonImage");b.trigger=d(this._get(b,"buttonImageOnly")?d("").addClass(this._triggerClass).attr({src:f,alt:c,title:c}):d('').addClass(this._triggerClass).html(f== -""?c:d("").attr({src:f,alt:c,title:c})));a[e?"before":"after"](b.trigger);b.trigger.click(function(){d.datepicker._datepickerShowing&&d.datepicker._lastInput==a[0]?d.datepicker._hideDatepicker():d.datepicker._showDatepicker(a[0]);return false})}},_autoSize:function(a){if(this._get(a,"autoSize")&&!a.inline){var b=new Date(2009,11,20),c=this._get(a,"dateFormat");if(c.match(/[DM]/)){var e=function(f){for(var h=0,i=0,g=0;gh){h=f[g].length;i=g}return i};b.setMonth(e(this._get(a, -c.match(/MM/)?"monthNames":"monthNamesShort")));b.setDate(e(this._get(a,c.match(/DD/)?"dayNames":"dayNamesShort"))+20-b.getDay())}a.input.attr("size",this._formatDate(a,b).length)}},_inlineDatepicker:function(a,b){var c=d(a);if(!c.hasClass(this.markerClassName)){c.addClass(this.markerClassName).append(b.dpDiv).bind("setData.datepicker",function(e,f,h){b.settings[f]=h}).bind("getData.datepicker",function(e,f){return this._get(b,f)});d.data(a,"datepicker",b);this._setDate(b,this._getDefaultDate(b), -true);this._updateDatepicker(b);this._updateAlternate(b);b.dpDiv.show()}},_dialogDatepicker:function(a,b,c,e,f){a=this._dialogInst;if(!a){this.uuid+=1;this._dialogInput=d('');this._dialogInput.keydown(this._doKeyDown);d("body").append(this._dialogInput);a=this._dialogInst=this._newInst(this._dialogInput,false);a.settings={};d.data(this._dialogInput[0],"datepicker",a)}E(a.settings,e||{}); -b=b&&b.constructor==Date?this._formatDate(a,b):b;this._dialogInput.val(b);this._pos=f?f.length?f:[f.pageX,f.pageY]:null;if(!this._pos)this._pos=[document.documentElement.clientWidth/2-100+(document.documentElement.scrollLeft||document.body.scrollLeft),document.documentElement.clientHeight/2-150+(document.documentElement.scrollTop||document.body.scrollTop)];this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+"px");a.settings.onSelect=c;this._inDialog=true;this.dpDiv.addClass(this._dialogClass); -this._showDatepicker(this._dialogInput[0]);d.blockUI&&d.blockUI(this.dpDiv);d.data(this._dialogInput[0],"datepicker",a);return this},_destroyDatepicker:function(a){var b=d(a),c=d.data(a,"datepicker");if(b.hasClass(this.markerClassName)){var e=a.nodeName.toLowerCase();d.removeData(a,"datepicker");if(e=="input"){c.append.remove();c.trigger.remove();b.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress).unbind("keyup", -this._doKeyUp)}else if(e=="div"||e=="span")b.removeClass(this.markerClassName).empty()}},_enableDatepicker:function(a){var b=d(a),c=d.data(a,"datepicker");if(b.hasClass(this.markerClassName)){var e=a.nodeName.toLowerCase();if(e=="input"){a.disabled=false;c.trigger.filter("button").each(function(){this.disabled=false}).end().filter("img").css({opacity:"1.0",cursor:""})}else if(e=="div"||e=="span")b.children("."+this._inlineClass).children().removeClass("ui-state-disabled");this._disabledInputs=d.map(this._disabledInputs, -function(f){return f==a?null:f})}},_disableDatepicker:function(a){var b=d(a),c=d.data(a,"datepicker");if(b.hasClass(this.markerClassName)){var e=a.nodeName.toLowerCase();if(e=="input"){a.disabled=true;c.trigger.filter("button").each(function(){this.disabled=true}).end().filter("img").css({opacity:"0.5",cursor:"default"})}else if(e=="div"||e=="span")b.children("."+this._inlineClass).children().addClass("ui-state-disabled");this._disabledInputs=d.map(this._disabledInputs,function(f){return f==a?null: -f});this._disabledInputs[this._disabledInputs.length]=a}},_isDisabledDatepicker:function(a){if(!a)return false;for(var b=0;b-1}},_doKeyUp:function(a){a=d.datepicker._getInst(a.target);if(a.input.val()!=a.lastVal)try{if(d.datepicker.parseDate(d.datepicker._get(a,"dateFormat"),a.input?a.input.val():null,d.datepicker._getFormatConfig(a))){d.datepicker._setDateFromField(a);d.datepicker._updateAlternate(a);d.datepicker._updateDatepicker(a)}}catch(b){d.datepicker.log(b)}return true}, -_showDatepicker:function(a){a=a.target||a;if(a.nodeName.toLowerCase()!="input")a=d("input",a.parentNode)[0];if(!(d.datepicker._isDisabledDatepicker(a)||d.datepicker._lastInput==a)){var b=d.datepicker._getInst(a);d.datepicker._curInst&&d.datepicker._curInst!=b&&d.datepicker._curInst.dpDiv.stop(true,true);var c=d.datepicker._get(b,"beforeShow");E(b.settings,c?c.apply(a,[a,b]):{});b.lastVal=null;d.datepicker._lastInput=a;d.datepicker._setDateFromField(b);if(d.datepicker._inDialog)a.value="";if(!d.datepicker._pos){d.datepicker._pos= -d.datepicker._findPos(a);d.datepicker._pos[1]+=a.offsetHeight}var e=false;d(a).parents().each(function(){e|=d(this).css("position")=="fixed";return!e});if(e&&d.browser.opera){d.datepicker._pos[0]-=document.documentElement.scrollLeft;d.datepicker._pos[1]-=document.documentElement.scrollTop}c={left:d.datepicker._pos[0],top:d.datepicker._pos[1]};d.datepicker._pos=null;b.dpDiv.empty();b.dpDiv.css({position:"absolute",display:"block",top:"-1000px"});d.datepicker._updateDatepicker(b);c=d.datepicker._checkOffset(b, -c,e);b.dpDiv.css({position:d.datepicker._inDialog&&d.blockUI?"static":e?"fixed":"absolute",display:"none",left:c.left+"px",top:c.top+"px"});if(!b.inline){c=d.datepicker._get(b,"showAnim");var f=d.datepicker._get(b,"duration"),h=function(){d.datepicker._datepickerShowing=true;var i=b.dpDiv.find("iframe.ui-datepicker-cover");if(i.length){var g=d.datepicker._getBorders(b.dpDiv);i.css({left:-g[0],top:-g[1],width:b.dpDiv.outerWidth(),height:b.dpDiv.outerHeight()})}};b.dpDiv.zIndex(d(a).zIndex()+1);d.effects&& -d.effects[c]?b.dpDiv.show(c,d.datepicker._get(b,"showOptions"),f,h):b.dpDiv[c||"show"](c?f:null,h);if(!c||!f)h();b.input.is(":visible")&&!b.input.is(":disabled")&&b.input.focus();d.datepicker._curInst=b}}},_updateDatepicker:function(a){var b=this,c=d.datepicker._getBorders(a.dpDiv);a.dpDiv.empty().append(this._generateHTML(a));var e=a.dpDiv.find("iframe.ui-datepicker-cover");e.length&&e.css({left:-c[0],top:-c[1],width:a.dpDiv.outerWidth(),height:a.dpDiv.outerHeight()});a.dpDiv.find("button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a").bind("mouseout", -function(){d(this).removeClass("ui-state-hover");this.className.indexOf("ui-datepicker-prev")!=-1&&d(this).removeClass("ui-datepicker-prev-hover");this.className.indexOf("ui-datepicker-next")!=-1&&d(this).removeClass("ui-datepicker-next-hover")}).bind("mouseover",function(){if(!b._isDisabledDatepicker(a.inline?a.dpDiv.parent()[0]:a.input[0])){d(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover");d(this).addClass("ui-state-hover");this.className.indexOf("ui-datepicker-prev")!= --1&&d(this).addClass("ui-datepicker-prev-hover");this.className.indexOf("ui-datepicker-next")!=-1&&d(this).addClass("ui-datepicker-next-hover")}}).end().find("."+this._dayOverClass+" a").trigger("mouseover").end();c=this._getNumberOfMonths(a);e=c[1];e>1?a.dpDiv.addClass("ui-datepicker-multi-"+e).css("width",17*e+"em"):a.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width("");a.dpDiv[(c[0]!=1||c[1]!=1?"add":"remove")+"Class"]("ui-datepicker-multi");a.dpDiv[(this._get(a, -"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl");a==d.datepicker._curInst&&d.datepicker._datepickerShowing&&a.input&&a.input.is(":visible")&&!a.input.is(":disabled")&&a.input.focus();if(a.yearshtml){var f=a.yearshtml;setTimeout(function(){f===a.yearshtml&&a.dpDiv.find("select.ui-datepicker-year:first").replaceWith(a.yearshtml);f=a.yearshtml=null},0)}},_getBorders:function(a){var b=function(c){return{thin:1,medium:2,thick:3}[c]||c};return[parseFloat(b(a.css("border-left-width"))),parseFloat(b(a.css("border-top-width")))]}, -_checkOffset:function(a,b,c){var e=a.dpDiv.outerWidth(),f=a.dpDiv.outerHeight(),h=a.input?a.input.outerWidth():0,i=a.input?a.input.outerHeight():0,g=document.documentElement.clientWidth+d(document).scrollLeft(),j=document.documentElement.clientHeight+d(document).scrollTop();b.left-=this._get(a,"isRTL")?e-h:0;b.left-=c&&b.left==a.input.offset().left?d(document).scrollLeft():0;b.top-=c&&b.top==a.input.offset().top+i?d(document).scrollTop():0;b.left-=Math.min(b.left,b.left+e>g&&g>e?Math.abs(b.left+e- -g):0);b.top-=Math.min(b.top,b.top+f>j&&j>f?Math.abs(f+i):0);return b},_findPos:function(a){for(var b=this._get(this._getInst(a),"isRTL");a&&(a.type=="hidden"||a.nodeType!=1);)a=a[b?"previousSibling":"nextSibling"];a=d(a).offset();return[a.left,a.top]},_hideDatepicker:function(a){var b=this._curInst;if(!(!b||a&&b!=d.data(a,"datepicker")))if(this._datepickerShowing){a=this._get(b,"showAnim");var c=this._get(b,"duration"),e=function(){d.datepicker._tidyDialog(b);this._curInst=null};d.effects&&d.effects[a]? -b.dpDiv.hide(a,d.datepicker._get(b,"showOptions"),c,e):b.dpDiv[a=="slideDown"?"slideUp":a=="fadeIn"?"fadeOut":"hide"](a?c:null,e);a||e();if(a=this._get(b,"onClose"))a.apply(b.input?b.input[0]:null,[b.input?b.input.val():"",b]);this._datepickerShowing=false;this._lastInput=null;if(this._inDialog){this._dialogInput.css({position:"absolute",left:"0",top:"-100px"});if(d.blockUI){d.unblockUI();d("body").append(this.dpDiv)}}this._inDialog=false}},_tidyDialog:function(a){a.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")}, -_checkExternalClick:function(a){if(d.datepicker._curInst){a=d(a.target);a[0].id!=d.datepicker._mainDivId&&a.parents("#"+d.datepicker._mainDivId).length==0&&!a.hasClass(d.datepicker.markerClassName)&&!a.hasClass(d.datepicker._triggerClass)&&d.datepicker._datepickerShowing&&!(d.datepicker._inDialog&&d.blockUI)&&d.datepicker._hideDatepicker()}},_adjustDate:function(a,b,c){a=d(a);var e=this._getInst(a[0]);if(!this._isDisabledDatepicker(a[0])){this._adjustInstDate(e,b+(c=="M"?this._get(e,"showCurrentAtPos"): -0),c);this._updateDatepicker(e)}},_gotoToday:function(a){a=d(a);var b=this._getInst(a[0]);if(this._get(b,"gotoCurrent")&&b.currentDay){b.selectedDay=b.currentDay;b.drawMonth=b.selectedMonth=b.currentMonth;b.drawYear=b.selectedYear=b.currentYear}else{var c=new Date;b.selectedDay=c.getDate();b.drawMonth=b.selectedMonth=c.getMonth();b.drawYear=b.selectedYear=c.getFullYear()}this._notifyChange(b);this._adjustDate(a)},_selectMonthYear:function(a,b,c){a=d(a);var e=this._getInst(a[0]);e._selectingMonthYear= -false;e["selected"+(c=="M"?"Month":"Year")]=e["draw"+(c=="M"?"Month":"Year")]=parseInt(b.options[b.selectedIndex].value,10);this._notifyChange(e);this._adjustDate(a)},_clickMonthYear:function(a){var b=this._getInst(d(a)[0]);b.input&&b._selectingMonthYear&&setTimeout(function(){b.input.focus()},0);b._selectingMonthYear=!b._selectingMonthYear},_selectDay:function(a,b,c,e){var f=d(a);if(!(d(e).hasClass(this._unselectableClass)||this._isDisabledDatepicker(f[0]))){f=this._getInst(f[0]);f.selectedDay=f.currentDay= -d("a",e).html();f.selectedMonth=f.currentMonth=b;f.selectedYear=f.currentYear=c;this._selectDate(a,this._formatDate(f,f.currentDay,f.currentMonth,f.currentYear))}},_clearDate:function(a){a=d(a);this._getInst(a[0]);this._selectDate(a,"")},_selectDate:function(a,b){a=this._getInst(d(a)[0]);b=b!=null?b:this._formatDate(a);a.input&&a.input.val(b);this._updateAlternate(a);var c=this._get(a,"onSelect");if(c)c.apply(a.input?a.input[0]:null,[b,a]);else a.input&&a.input.trigger("change");if(a.inline)this._updateDatepicker(a); -else{this._hideDatepicker();this._lastInput=a.input[0];typeof a.input[0]!="object"&&a.input.focus();this._lastInput=null}},_updateAlternate:function(a){var b=this._get(a,"altField");if(b){var c=this._get(a,"altFormat")||this._get(a,"dateFormat"),e=this._getDate(a),f=this.formatDate(c,e,this._getFormatConfig(a));d(b).each(function(){d(this).val(f)})}},noWeekends:function(a){a=a.getDay();return[a>0&&a<6,""]},iso8601Week:function(a){a=new Date(a.getTime());a.setDate(a.getDate()+4-(a.getDay()||7));var b= -a.getTime();a.setMonth(0);a.setDate(1);return Math.floor(Math.round((b-a)/864E5)/7)+1},parseDate:function(a,b,c){if(a==null||b==null)throw"Invalid arguments";b=typeof b=="object"?b.toString():b+"";if(b=="")return null;var e=(c?c.shortYearCutoff:null)||this._defaults.shortYearCutoff;e=typeof e!="string"?e:(new Date).getFullYear()%100+parseInt(e,10);for(var f=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,h=(c?c.dayNames:null)||this._defaults.dayNames,i=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort, -g=(c?c.monthNames:null)||this._defaults.monthNames,j=c=-1,l=-1,u=-1,k=false,o=function(p){(p=z+1-1){j=1;l=u;do{e=this._getDaysInMonth(c,j-1);if(l<=e)break;j++;l-=e}while(1)}w=this._daylightSavingAdjust(new Date(c,j-1,l));if(w.getFullYear()!=c||w.getMonth()+1!=j||w.getDate()!=l)throw"Invalid date";return w},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y", -RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925))*24*60*60*1E7,formatDate:function(a,b,c){if(!b)return"";var e=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,f=(c?c.dayNames:null)||this._defaults.dayNames,h=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort;c=(c?c.monthNames:null)||this._defaults.monthNames;var i=function(o){(o=k+112?a.getHours()+2:0);return a},_setDate:function(a,b,c){var e=!b,f=a.selectedMonth,h=a.selectedYear;b=this._restrictMinMax(a,this._determineDate(a,b,new Date));a.selectedDay= -a.currentDay=b.getDate();a.drawMonth=a.selectedMonth=a.currentMonth=b.getMonth();a.drawYear=a.selectedYear=a.currentYear=b.getFullYear();if((f!=a.selectedMonth||h!=a.selectedYear)&&!c)this._notifyChange(a);this._adjustInstDate(a);if(a.input)a.input.val(e?"":this._formatDate(a))},_getDate:function(a){return!a.currentYear||a.input&&a.input.val()==""?null:this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay))},_generateHTML:function(a){var b=new Date;b=this._daylightSavingAdjust(new Date(b.getFullYear(), -b.getMonth(),b.getDate()));var c=this._get(a,"isRTL"),e=this._get(a,"showButtonPanel"),f=this._get(a,"hideIfNoPrevNext"),h=this._get(a,"navigationAsDateFormat"),i=this._getNumberOfMonths(a),g=this._get(a,"showCurrentAtPos"),j=this._get(a,"stepMonths"),l=i[0]!=1||i[1]!=1,u=this._daylightSavingAdjust(!a.currentDay?new Date(9999,9,9):new Date(a.currentYear,a.currentMonth,a.currentDay)),k=this._getMinMaxDate(a,"min"),o=this._getMinMaxDate(a,"max");g=a.drawMonth-g;var m=a.drawYear;if(g<0){g+=12;m--}if(o){var n= -this._daylightSavingAdjust(new Date(o.getFullYear(),o.getMonth()-i[0]*i[1]+1,o.getDate()));for(n=k&&nn;){g--;if(g<0){g=11;m--}}}a.drawMonth=g;a.drawYear=m;n=this._get(a,"prevText");n=!h?n:this.formatDate(n,this._daylightSavingAdjust(new Date(m,g-j,1)),this._getFormatConfig(a));n=this._canAdjustMonth(a,-1,m,g)?''+n+"":f?"":''+n+"";var r=this._get(a,"nextText");r=!h?r:this.formatDate(r,this._daylightSavingAdjust(new Date(m,g+j,1)),this._getFormatConfig(a));f=this._canAdjustMonth(a,+1,m,g)?''+r+"":f?"":''+r+"";j=this._get(a,"currentText");r=this._get(a,"gotoCurrent")&&a.currentDay?u:b;j=!h?j:this.formatDate(j,r,this._getFormatConfig(a));h=!a.inline?'":"";e=e?'
    '+(c?h:"")+(this._isInRange(a,r)?'":"")+(c?"":h)+"
    ":"";h=parseInt(this._get(a,"firstDay"),10);h=isNaN(h)?0:h;j=this._get(a,"showWeek");r=this._get(a,"dayNames");this._get(a,"dayNamesShort");var s=this._get(a,"dayNamesMin"),z= -this._get(a,"monthNames"),w=this._get(a,"monthNamesShort"),p=this._get(a,"beforeShowDay"),v=this._get(a,"showOtherMonths"),H=this._get(a,"selectOtherMonths");this._get(a,"calculateWeek");for(var L=this._getDefaultDate(a),I="",C=0;C1)switch(D){case 0:x+=" ui-datepicker-group-first";t=" ui-corner-"+(c?"right":"left");break;case i[1]- -1:x+=" ui-datepicker-group-last";t=" ui-corner-"+(c?"left":"right");break;default:x+=" ui-datepicker-group-middle";t="";break}x+='">'}x+='
    '+(/all|left/.test(t)&&C==0?c?f:n:"")+(/all|right/.test(t)&&C==0?c?n:f:"")+this._generateMonthYearHeader(a,g,m,k,o,C>0||D>0,z,w)+'
    ';var A=j?'":"";for(t=0;t<7;t++){var q= -(t+h)%7;A+="=5?' class="ui-datepicker-week-end"':"")+'>'+s[q]+""}x+=A+"";A=this._getDaysInMonth(m,g);if(m==a.selectedYear&&g==a.selectedMonth)a.selectedDay=Math.min(a.selectedDay,A);t=(this._getFirstDayOfMonth(m,g)-h+7)%7;A=l?6:Math.ceil((t+A)/7);q=this._daylightSavingAdjust(new Date(m,g,1-t));for(var O=0;O";var P=!j?"":'";for(t=0;t<7;t++){var F= -p?p.apply(a.input?a.input[0]:null,[q]):[true,""],B=q.getMonth()!=g,J=B&&!H||!F[0]||k&&qo;P+='";q.setDate(q.getDate()+1);q=this._daylightSavingAdjust(q)}x+= -P+""}g++;if(g>11){g=0;m++}x+="
    '+this._get(a,"weekHeader")+"
    '+this._get(a,"calculateWeek")(q)+""+(B&&!v?" ":J?''+q.getDate()+"":''+q.getDate()+"")+"
    "+(l?""+(i[0]>0&&D==i[1]-1?'
    ':""):"");M+=x}I+=M}I+=e+(d.browser.msie&&parseInt(d.browser.version,10)<7&&!a.inline?'':"");a._keyEvent=false;return I},_generateMonthYearHeader:function(a,b,c,e,f,h,i,g){var j=this._get(a,"changeMonth"),l=this._get(a,"changeYear"),u=this._get(a,"showMonthAfterYear"),k='
    ', -o="";if(h||!j)o+=''+i[b]+"";else{i=e&&e.getFullYear()==c;var m=f&&f.getFullYear()==c;o+='"}u||(k+=o+(h||!(j&& -l)?" ":""));a.yearshtml="";if(h||!l)k+=''+c+"";else{g=this._get(a,"yearRange").split(":");var r=(new Date).getFullYear();i=function(s){s=s.match(/c[+-].*/)?c+parseInt(s.substring(1),10):s.match(/[+-].*/)?r+parseInt(s,10):parseInt(s,10);return isNaN(s)?r:s};b=i(g[0]);g=Math.max(b,i(g[1]||""));b=e?Math.max(b,e.getFullYear()):b;g=f?Math.min(g,f.getFullYear()):g;for(a.yearshtml+='";if(d.browser.mozilla)k+='";else{k+=a.yearshtml;a.yearshtml=null}}k+=this._get(a,"yearSuffix");if(u)k+=(h||!(j&&l)?" ":"")+o;k+="
    ";return k},_adjustInstDate:function(a,b,c){var e= -a.drawYear+(c=="Y"?b:0),f=a.drawMonth+(c=="M"?b:0);b=Math.min(a.selectedDay,this._getDaysInMonth(e,f))+(c=="D"?b:0);e=this._restrictMinMax(a,this._daylightSavingAdjust(new Date(e,f,b)));a.selectedDay=e.getDate();a.drawMonth=a.selectedMonth=e.getMonth();a.drawYear=a.selectedYear=e.getFullYear();if(c=="M"||c=="Y")this._notifyChange(a)},_restrictMinMax:function(a,b){var c=this._getMinMaxDate(a,"min");a=this._getMinMaxDate(a,"max");b=c&&ba?a:b},_notifyChange:function(a){var b=this._get(a, -"onChangeMonthYear");if(b)b.apply(a.input?a.input[0]:null,[a.selectedYear,a.selectedMonth+1,a])},_getNumberOfMonths:function(a){a=this._get(a,"numberOfMonths");return a==null?[1,1]:typeof a=="number"?[1,a]:a},_getMinMaxDate:function(a,b){return this._determineDate(a,this._get(a,b+"Date"),null)},_getDaysInMonth:function(a,b){return 32-(new Date(a,b,32)).getDate()},_getFirstDayOfMonth:function(a,b){return(new Date(a,b,1)).getDay()},_canAdjustMonth:function(a,b,c,e){var f=this._getNumberOfMonths(a); -c=this._daylightSavingAdjust(new Date(c,e+(b<0?b:f[0]*f[1]),1));b<0&&c.setDate(this._getDaysInMonth(c.getFullYear(),c.getMonth()));return this._isInRange(a,c)},_isInRange:function(a,b){var c=this._getMinMaxDate(a,"min");a=this._getMinMaxDate(a,"max");return(!c||b.getTime()>=c.getTime())&&(!a||b.getTime()<=a.getTime())},_getFormatConfig:function(a){var b=this._get(a,"shortYearCutoff");b=typeof b!="string"?b:(new Date).getFullYear()%100+parseInt(b,10);return{shortYearCutoff:b,dayNamesShort:this._get(a, -"dayNamesShort"),dayNames:this._get(a,"dayNames"),monthNamesShort:this._get(a,"monthNamesShort"),monthNames:this._get(a,"monthNames")}},_formatDate:function(a,b,c,e){if(!b){a.currentDay=a.selectedDay;a.currentMonth=a.selectedMonth;a.currentYear=a.selectedYear}b=b?typeof b=="object"?b:this._daylightSavingAdjust(new Date(e,c,b)):this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay));return this.formatDate(this._get(a,"dateFormat"),b,this._getFormatConfig(a))}});d.fn.datepicker= -function(a){if(!d.datepicker.initialized){d(document).mousedown(d.datepicker._checkExternalClick).find("body").append(d.datepicker.dpDiv);d.datepicker.initialized=true}var b=Array.prototype.slice.call(arguments,1);if(typeof a=="string"&&(a=="isDisabled"||a=="getDate"||a=="widget"))return d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this[0]].concat(b));if(a=="option"&&arguments.length==2&&typeof arguments[1]=="string")return d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this[0]].concat(b)); -return this.each(function(){typeof a=="string"?d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this].concat(b)):d.datepicker._attachDatepicker(this,a)})};d.datepicker=new K;d.datepicker.initialized=false;d.datepicker.uuid=(new Date).getTime();d.datepicker.version="1.8.9";window["DP_jQuery_"+y]=d})(jQuery); -;/* - * jQuery UI Progressbar 1.8.9 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Progressbar - * - * Depends: - * jquery.ui.core.js - * jquery.ui.widget.js - */ -(function(b,d){b.widget("ui.progressbar",{options:{value:0,max:100},min:0,_create:function(){this.element.addClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").attr({role:"progressbar","aria-valuemin":this.min,"aria-valuemax":this.options.max,"aria-valuenow":this._value()});this.valueDiv=b("
    ").appendTo(this.element);this.oldValue=this._value();this._refreshValue()},destroy:function(){this.element.removeClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow"); -this.valueDiv.remove();b.Widget.prototype.destroy.apply(this,arguments)},value:function(a){if(a===d)return this._value();this._setOption("value",a);return this},_setOption:function(a,c){if(a==="value"){this.options.value=c;this._refreshValue();this._value()===this.options.max&&this._trigger("complete")}b.Widget.prototype._setOption.apply(this,arguments)},_value:function(){var a=this.options.value;if(typeof a!=="number")a=0;return Math.min(this.options.max,Math.max(this.min,a))},_percentage:function(){return 100* -this._value()/this.options.max},_refreshValue:function(){var a=this.value(),c=this._percentage();if(this.oldValue!==a){this.oldValue=a;this._trigger("change")}this.valueDiv.toggleClass("ui-corner-right",a===this.options.max).width(c.toFixed(0)+"%");this.element.attr("aria-valuenow",a)}});b.extend(b.ui.progressbar,{version:"1.8.9"})})(jQuery); -;/* - * jQuery UI Effects 1.8.9 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Effects/ - */ -jQuery.effects||function(f,j){function n(c){var a;if(c&&c.constructor==Array&&c.length==3)return c;if(a=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(c))return[parseInt(a[1],10),parseInt(a[2],10),parseInt(a[3],10)];if(a=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(c))return[parseFloat(a[1])*2.55,parseFloat(a[2])*2.55,parseFloat(a[3])*2.55];if(a=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(c))return[parseInt(a[1], -16),parseInt(a[2],16),parseInt(a[3],16)];if(a=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(c))return[parseInt(a[1]+a[1],16),parseInt(a[2]+a[2],16),parseInt(a[3]+a[3],16)];if(/rgba\(0, 0, 0, 0\)/.exec(c))return o.transparent;return o[f.trim(c).toLowerCase()]}function s(c,a){var b;do{b=f.curCSS(c,a);if(b!=""&&b!="transparent"||f.nodeName(c,"body"))break;a="backgroundColor"}while(c=c.parentNode);return n(b)}function p(){var c=document.defaultView?document.defaultView.getComputedStyle(this,null):this.currentStyle, -a={},b,d;if(c&&c.length&&c[0]&&c[c[0]])for(var e=c.length;e--;){b=c[e];if(typeof c[b]=="string"){d=b.replace(/\-(\w)/g,function(g,h){return h.toUpperCase()});a[d]=c[b]}}else for(b in c)if(typeof c[b]==="string")a[b]=c[b];return a}function q(c){var a,b;for(a in c){b=c[a];if(b==null||f.isFunction(b)||a in t||/scrollbar/.test(a)||!/color/i.test(a)&&isNaN(parseFloat(b)))delete c[a]}return c}function u(c,a){var b={_:0},d;for(d in a)if(c[d]!=a[d])b[d]=a[d];return b}function k(c,a,b,d){if(typeof c=="object"){d= -a;b=null;a=c;c=a.effect}if(f.isFunction(a)){d=a;b=null;a={}}if(typeof a=="number"||f.fx.speeds[a]){d=b;b=a;a={}}if(f.isFunction(b)){d=b;b=null}a=a||{};b=b||a.duration;b=f.fx.off?0:typeof b=="number"?b:b in f.fx.speeds?f.fx.speeds[b]:f.fx.speeds._default;d=d||a.complete;return[c,a,b,d]}function m(c){if(!c||typeof c==="number"||f.fx.speeds[c])return true;if(typeof c==="string"&&!f.effects[c])return true;return false}f.effects={};f.each(["backgroundColor","borderBottomColor","borderLeftColor","borderRightColor", -"borderTopColor","borderColor","color","outlineColor"],function(c,a){f.fx.step[a]=function(b){if(!b.colorInit){b.start=s(b.elem,a);b.end=n(b.end);b.colorInit=true}b.elem.style[a]="rgb("+Math.max(Math.min(parseInt(b.pos*(b.end[0]-b.start[0])+b.start[0],10),255),0)+","+Math.max(Math.min(parseInt(b.pos*(b.end[1]-b.start[1])+b.start[1],10),255),0)+","+Math.max(Math.min(parseInt(b.pos*(b.end[2]-b.start[2])+b.start[2],10),255),0)+")"}});var o={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0, -0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211, -211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0],transparent:[255,255,255]},r=["add","remove","toggle"],t={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};f.effects.animateClass=function(c,a,b, -d){if(f.isFunction(b)){d=b;b=null}return this.queue("fx",function(){var e=f(this),g=e.attr("style")||" ",h=q(p.call(this)),l,v=e.attr("className");f.each(r,function(w,i){c[i]&&e[i+"Class"](c[i])});l=q(p.call(this));e.attr("className",v);e.animate(u(h,l),a,b,function(){f.each(r,function(w,i){c[i]&&e[i+"Class"](c[i])});if(typeof e.attr("style")=="object"){e.attr("style").cssText="";e.attr("style").cssText=g}else e.attr("style",g);d&&d.apply(this,arguments)});h=f.queue(this);l=h.splice(h.length-1,1)[0]; -h.splice(1,0,l);f.dequeue(this)})};f.fn.extend({_addClass:f.fn.addClass,addClass:function(c,a,b,d){return a?f.effects.animateClass.apply(this,[{add:c},a,b,d]):this._addClass(c)},_removeClass:f.fn.removeClass,removeClass:function(c,a,b,d){return a?f.effects.animateClass.apply(this,[{remove:c},a,b,d]):this._removeClass(c)},_toggleClass:f.fn.toggleClass,toggleClass:function(c,a,b,d,e){return typeof a=="boolean"||a===j?b?f.effects.animateClass.apply(this,[a?{add:c}:{remove:c},b,d,e]):this._toggleClass(c, -a):f.effects.animateClass.apply(this,[{toggle:c},a,b,d])},switchClass:function(c,a,b,d,e){return f.effects.animateClass.apply(this,[{add:a,remove:c},b,d,e])}});f.extend(f.effects,{version:"1.8.9",save:function(c,a){for(var b=0;b").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent", -border:"none",margin:0,padding:0});c.wrap(b);b=c.parent();if(c.css("position")=="static"){b.css({position:"relative"});c.css({position:"relative"})}else{f.extend(a,{position:c.css("position"),zIndex:c.css("z-index")});f.each(["top","left","bottom","right"],function(d,e){a[e]=c.css(e);if(isNaN(parseInt(a[e],10)))a[e]="auto"});c.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})}return b.css(a).show()},removeWrapper:function(c){if(c.parent().is(".ui-effects-wrapper"))return c.parent().replaceWith(c); -return c},setTransition:function(c,a,b,d){d=d||{};f.each(a,function(e,g){unit=c.cssUnit(g);if(unit[0]>0)d[g]=unit[0]*b+unit[1]});return d}});f.fn.extend({effect:function(c){var a=k.apply(this,arguments),b={options:a[1],duration:a[2],callback:a[3]};a=b.options.mode;var d=f.effects[c];if(f.fx.off||!d)return a?this[a](b.duration,b.callback):this.each(function(){b.callback&&b.callback.call(this)});return d.call(this,b)},_show:f.fn.show,show:function(c){if(m(c))return this._show.apply(this,arguments); -else{var a=k.apply(this,arguments);a[1].mode="show";return this.effect.apply(this,a)}},_hide:f.fn.hide,hide:function(c){if(m(c))return this._hide.apply(this,arguments);else{var a=k.apply(this,arguments);a[1].mode="hide";return this.effect.apply(this,a)}},__toggle:f.fn.toggle,toggle:function(c){if(m(c)||typeof c==="boolean"||f.isFunction(c))return this.__toggle.apply(this,arguments);else{var a=k.apply(this,arguments);a[1].mode="toggle";return this.effect.apply(this,a)}},cssUnit:function(c){var a=this.css(c), -b=[];f.each(["em","px","%","pt"],function(d,e){if(a.indexOf(e)>0)b=[parseFloat(a),e]});return b}});f.easing.jswing=f.easing.swing;f.extend(f.easing,{def:"easeOutQuad",swing:function(c,a,b,d,e){return f.easing[f.easing.def](c,a,b,d,e)},easeInQuad:function(c,a,b,d,e){return d*(a/=e)*a+b},easeOutQuad:function(c,a,b,d,e){return-d*(a/=e)*(a-2)+b},easeInOutQuad:function(c,a,b,d,e){if((a/=e/2)<1)return d/2*a*a+b;return-d/2*(--a*(a-2)-1)+b},easeInCubic:function(c,a,b,d,e){return d*(a/=e)*a*a+b},easeOutCubic:function(c, -a,b,d,e){return d*((a=a/e-1)*a*a+1)+b},easeInOutCubic:function(c,a,b,d,e){if((a/=e/2)<1)return d/2*a*a*a+b;return d/2*((a-=2)*a*a+2)+b},easeInQuart:function(c,a,b,d,e){return d*(a/=e)*a*a*a+b},easeOutQuart:function(c,a,b,d,e){return-d*((a=a/e-1)*a*a*a-1)+b},easeInOutQuart:function(c,a,b,d,e){if((a/=e/2)<1)return d/2*a*a*a*a+b;return-d/2*((a-=2)*a*a*a-2)+b},easeInQuint:function(c,a,b,d,e){return d*(a/=e)*a*a*a*a+b},easeOutQuint:function(c,a,b,d,e){return d*((a=a/e-1)*a*a*a*a+1)+b},easeInOutQuint:function(c, -a,b,d,e){if((a/=e/2)<1)return d/2*a*a*a*a*a+b;return d/2*((a-=2)*a*a*a*a+2)+b},easeInSine:function(c,a,b,d,e){return-d*Math.cos(a/e*(Math.PI/2))+d+b},easeOutSine:function(c,a,b,d,e){return d*Math.sin(a/e*(Math.PI/2))+b},easeInOutSine:function(c,a,b,d,e){return-d/2*(Math.cos(Math.PI*a/e)-1)+b},easeInExpo:function(c,a,b,d,e){return a==0?b:d*Math.pow(2,10*(a/e-1))+b},easeOutExpo:function(c,a,b,d,e){return a==e?b+d:d*(-Math.pow(2,-10*a/e)+1)+b},easeInOutExpo:function(c,a,b,d,e){if(a==0)return b;if(a== -e)return b+d;if((a/=e/2)<1)return d/2*Math.pow(2,10*(a-1))+b;return d/2*(-Math.pow(2,-10*--a)+2)+b},easeInCirc:function(c,a,b,d,e){return-d*(Math.sqrt(1-(a/=e)*a)-1)+b},easeOutCirc:function(c,a,b,d,e){return d*Math.sqrt(1-(a=a/e-1)*a)+b},easeInOutCirc:function(c,a,b,d,e){if((a/=e/2)<1)return-d/2*(Math.sqrt(1-a*a)-1)+b;return d/2*(Math.sqrt(1-(a-=2)*a)+1)+b},easeInElastic:function(c,a,b,d,e){c=1.70158;var g=0,h=d;if(a==0)return b;if((a/=e)==1)return b+d;g||(g=e*0.3);if(h").css({position:"absolute",visibility:"visible",left:-f*(h/d),top:-e*(i/c)}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:h/d,height:i/c,left:g.left+f*(h/d)+(a.options.mode=="show"?(f-Math.floor(d/2))*(h/d):0),top:g.top+e*(i/c)+(a.options.mode=="show"?(e-Math.floor(c/2))*(i/c):0),opacity:a.options.mode=="show"?0:1}).animate({left:g.left+f*(h/d)+(a.options.mode=="show"?0:(f-Math.floor(d/2))*(h/d)),top:g.top+ -e*(i/c)+(a.options.mode=="show"?0:(e-Math.floor(c/2))*(i/c)),opacity:a.options.mode=="show"?1:0},a.duration||500);setTimeout(function(){a.options.mode=="show"?b.css({visibility:"visible"}):b.css({visibility:"visible"}).hide();a.callback&&a.callback.apply(b[0]);b.dequeue();j("div.ui-effects-explode").remove()},a.duration||500)})}})(jQuery); -;/* - * jQuery UI Effects Fade 1.8.9 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Effects/Fade - * - * Depends: - * jquery.effects.core.js - */ -(function(b){b.effects.fade=function(a){return this.queue(function(){var c=b(this),d=b.effects.setMode(c,a.options.mode||"hide");c.animate({opacity:d},{queue:false,duration:a.duration,easing:a.options.easing,complete:function(){a.callback&&a.callback.apply(this,arguments);c.dequeue()}})})}})(jQuery); -;/* - * jQuery UI Effects Fold 1.8.9 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Effects/Fold - * - * Depends: - * jquery.effects.core.js - */ -(function(c){c.effects.fold=function(a){return this.queue(function(){var b=c(this),j=["position","top","bottom","left","right"],d=c.effects.setMode(b,a.options.mode||"hide"),g=a.options.size||15,h=!!a.options.horizFirst,k=a.duration?a.duration/2:c.fx.speeds._default/2;c.effects.save(b,j);b.show();var e=c.effects.createWrapper(b).css({overflow:"hidden"}),f=d=="show"!=h,l=f?["width","height"]:["height","width"];f=f?[e.width(),e.height()]:[e.height(),e.width()];var i=/([0-9]+)%/.exec(g);if(i)g=parseInt(i[1], -10)/100*f[d=="hide"?0:1];if(d=="show")e.css(h?{height:0,width:g}:{height:g,width:0});h={};i={};h[l[0]]=d=="show"?f[0]:g;i[l[1]]=d=="show"?f[1]:0;e.animate(h,k,a.options.easing).animate(i,k,a.options.easing,function(){d=="hide"&&b.hide();c.effects.restore(b,j);c.effects.removeWrapper(b);a.callback&&a.callback.apply(b[0],arguments);b.dequeue()})})}})(jQuery); -;/* - * jQuery UI Effects Highlight 1.8.9 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Effects/Highlight - * - * Depends: - * jquery.effects.core.js - */ -(function(b){b.effects.highlight=function(c){return this.queue(function(){var a=b(this),e=["backgroundImage","backgroundColor","opacity"],d=b.effects.setMode(a,c.options.mode||"show"),f={backgroundColor:a.css("backgroundColor")};if(d=="hide")f.opacity=0;b.effects.save(a,e);a.show().css({backgroundImage:"none",backgroundColor:c.options.color||"#ffff99"}).animate(f,{queue:false,duration:c.duration,easing:c.options.easing,complete:function(){d=="hide"&&a.hide();b.effects.restore(a,e);d=="show"&&!b.support.opacity&& -this.style.removeAttribute("filter");c.callback&&c.callback.apply(this,arguments);a.dequeue()}})})}})(jQuery); -;/* - * jQuery UI Effects Pulsate 1.8.9 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Effects/Pulsate - * - * Depends: - * jquery.effects.core.js - */ -(function(d){d.effects.pulsate=function(a){return this.queue(function(){var b=d(this),c=d.effects.setMode(b,a.options.mode||"show");times=(a.options.times||5)*2-1;duration=a.duration?a.duration/2:d.fx.speeds._default/2;isVisible=b.is(":visible");animateTo=0;if(!isVisible){b.css("opacity",0).show();animateTo=1}if(c=="hide"&&isVisible||c=="show"&&!isVisible)times--;for(c=0;c').appendTo(document.body).addClass(a.options.className).css({top:d.top,left:d.left,height:b.innerHeight(),width:b.innerWidth(),position:"absolute"}).animate(c,a.duration,a.options.easing,function(){f.remove();a.callback&&a.callback.apply(b[0],arguments); -b.dequeue()})})}})(jQuery); -; \ No newline at end of file diff --git a/resources/startupHints/js/theme-loader.js b/resources/startupHints/js/theme-loader.js new file mode 100644 index 000000000..d2fe799cf --- /dev/null +++ b/resources/startupHints/js/theme-loader.js @@ -0,0 +1,14 @@ +// Inject theme stylesheet from URL param (?theme=dark|light) +(function () { + var params = new URLSearchParams(window.location.search); + var theme = params.get("theme"); + if (theme !== "dark" && theme !== "light") { + theme = "light"; + } + + var themeStylesheet = document.createElement("link"); + themeStylesheet.rel = "stylesheet"; + themeStylesheet.type = "text/css"; + themeStylesheet.href = "../../css/" + theme + ".css"; + document.head.appendChild(themeStylesheet); +})(); \ No newline at end of file diff --git a/resources/startupHints/locales/ar/1.html b/resources/startupHints/locales/ar/1.html new file mode 100644 index 000000000..d3c3e38b8 --- /dev/null +++ b/resources/startupHints/locales/ar/1.html @@ -0,0 +1,95 @@ + + + + + مرحباً بك في <bdi>OpenBoard</bdi> + + + + + + + +
    + + +

    + إليك بعض النصائح والإرشادات التي تساعدك على التعرّف إلى OpenBoard. +

    + +

    + OpenBoard هو سبورة تفاعلية صُممت من قبل المعلمين ولأجلهم. + ويوفر أربعة أوضاع متكاملة للاستخدام داخل الصف. +

    + +
    +

    أوضاع OpenBoard الأربعة

    + +
    + +
    +

    لنبدأ بأول الأوضاع وأهمها: وضع السبورة.

    +
    + + +
    + +
    + +
    + + + + diff --git a/resources/startupHints/locales/ar/10.html b/resources/startupHints/locales/ar/10.html new file mode 100644 index 000000000..6485a31ec --- /dev/null +++ b/resources/startupHints/locales/ar/10.html @@ -0,0 +1,94 @@ + + + + + التطبيقات + + + + + + + + + + +
    + +
    +

    الوصول إلى التطبيقات

    +

    + يوفر OpenBoard تطبيقات وأدوات مختلفة. + يمكنك العثور عليها في المكتبة بالنقر على + Applications icon. +

    +
    + + +
    +

    التطبيقات الافتراضية

    +

    + ستجد أدوات هندسية، وآلة حاسبة، وخرائط Google، ومولد رموز QR، وغيرها الكثير. + قم بدمجها لإنشاء تجارب تعليمية ممتعة! +

    + +
    + Example of using integrated applications +
    مثال على استخدام التطبيقات المدمجة في OpenBoard.
    +
    +
    + + +
    +

    إنشاء تطبيقاتك الخاصة

    +

    + كما هو موضح في قسم وضع الويب, يمكنك إنشاء تطبيق من موقع ويب باستخدام المتصفح المدمج أو من خلال نسخ رابط URL ولصقه على اللوحة. +

    +

    + يتيح لك ذلك تعزيز دروسك بأدوات قوية متاحة على الإنترنت ويمكن استخدامها مباشرةً داخل OpenBoard. + فيما يلي بعض الأمثلة المفيدة (اسحب الروابط وأفلتها على اللوحة لتجربتها): +

    + +
      +
    • Scratch — البرمجة المرئية
    • +
    • PhET simulations — تجارب علمية تفاعلية
    • +
    • Google Earth — استكشاف العالم في 3D
    • +
    • Flockmod — الرسم التعاوني عبر الإنترنت
    • +
    • Duolingo — تعلم اللغات
    • +
    + +

    ...وغير ذلك الكثير!

    + + +
    +
    + +
    + +
    + + + + + diff --git a/resources/startupHints/locales/ar/11.html b/resources/startupHints/locales/ar/11.html new file mode 100644 index 000000000..6ffd56704 --- /dev/null +++ b/resources/startupHints/locales/ar/11.html @@ -0,0 +1,89 @@ + + + + + تسجيل الفيديو + + + + + + + + + + +
    + +
    +

    تسجيل دراستك

    +

    + تيح لك OpenBoard حفظ الصوت و الفيديو الخاصين بدرسك. + وهو مثالي لمشاركة تمارين إضافية، أو نشر مقطع توضيحي قصير، أو إتاحة الدرس للطلاب الغائبين. +

    + + +
    + + +
    +

    الوصول إلى أداة التسجيل

    +

    افتح أداة التسجيل كما هو موضح أدناه:

    + +
    + Opening the Video Capture tool in OpenBoard +
    وصول سريع إلى أداة التسجيل من واجهة OpenBoard.
    +
    +
    + + +
    +

    واجهة التحكم

    +

    تظهر الواجهة في الزاوية السفلية اليمنى من اللوحة:

    + +
    + Recorder interface (bottom right controls) +
    + +

    انقر على الزر الأحمر لبدء التسجيل.

    + + +
    + + +
    +

    إنهاء التسجيل واسترجاع الفيديو

    +

    + بعد انتهاء التسجيل، يتم حفظ الفيديو تلقائياً على سطح المكتب في جهازك. +

    + +
    + End recording and save the video to the Desktop +
    إيقاف التسجيل وظهور ملف الفيديو على سطح المكتب.
    +
    +
    +
    + +
    + +
    + + diff --git a/resources/startupHints/locales/ar/12.html b/resources/startupHints/locales/ar/12.html new file mode 100644 index 000000000..588108d3a --- /dev/null +++ b/resources/startupHints/locales/ar/12.html @@ -0,0 +1,91 @@ + + + + + المستندات المفضلة + + + + + + + + + + +
    + +
    +

    إضافة مستندات OpenBoard إلى المفضلة

    +

    + أُضيفت هذه الميزة في الإصدار 1.7, وتتيح لك إضافة مستندات OpenBoard إلى المفضلة. +

    +

    + للقيام بذلك، انتقل إلى وضع المستندات, وحدد مستنداً ثم انقر على + Add to Favorites icon + في شريط الأدوات. +

    + +
    + Adding a OpenBoard document to favorites + +
    +
    + + +
    +

    التنقل بسرعة من مستند إلى آخر

    +

    + تظهر مستنداتك المفضلة في مكتبة OpenBoard, ضمن + Favorites Icon. +

    +

    + كل مستند مفضل + Document Icon OpenBoard + يمكن سحبه وإفلاته مباشرة على اللوحة. +

    + +
    + Viewing Favorites in Library OpenBoard +
    + + + +
    + + +
    +

    المستندات المفتوحة مؤخراً

    +

    + يُضاف كل مستند يتم فتحه مؤقتاً إلى المفضلة، مما يتيح لك التنقل بسرعة بين المستندات خلال الجلسة نفسها دون الحاجة إلى إضافتها يدوياً. +

    + +
    + Example of recently opened documents in OpenBoard + +
    + + +
    +
    + +
    +
    + + diff --git a/resources/startupHints/locales/ar/2.html b/resources/startupHints/locales/ar/2.html new file mode 100644 index 000000000..79237974f --- /dev/null +++ b/resources/startupHints/locales/ar/2.html @@ -0,0 +1,120 @@ + + + + + وضع السبورة + + + + + + + + +
    + +
    +

    لوحة الأدوات القلم

    +

    +تتيح لك لوحة أدوات القلم الوصول إلى الأدوات الأساسية عند العمل على السبورة البيضاء.

    + +
    + OpenBoard stylus palette +
    + +

    + ستجد هنا القلم + Pencil, + الممحاة + Eraser + وقلم التمييز. + Highlighter. + يوفر OpenBoard ميزات لا تتوفر في السبورة التقليدية: +

    + +
      +
    • + + المحدِّد : تحديد الكائنات والتفاعل معها. +
    • +
    • + + الإصبع السحري : التحريك أو التفاعل دون الحاجة إلى التحديد أولاً. +
    • +
    • + + اليد : تحريك الصفحة (مفيد جداً عند استخدام التكبير). +
    • +
    • + + + تكبير / تصغير : انقر على المكان الذي تريد تطبيق التأثير عليه بعد اختيار الأداة. +
    • + + مؤشر الليزر : الإشارة إلى العناصر دون التفاعل معها. +
    • +
    • + + الخط : رسم خطوط مستقيمة بسهولة. +
    • +
    • + + النص : إدراج مربع نص للكتابة على لوحة المفاتيح. +
    • +
    + + +
    + + +
    +

    شريط اللوحة

    +

    + يتيح لك شريط اللوحة تغيير لون و/أو حجم القلم وقلم التمييز، بالإضافة إلى وظائف أخرى. +

    + +
    + OpenBoard table bar +
    + +

    + يمكنك أيضاً تغيير خلفية اللوحة بالنقر على + Change the background. +

    + +
    + Example of changing table background +
    + +

    + تتوفر وظائف أخرى مثل: التراجع/الإعادة، إنشاء صفحات, التنقل بين الصفحات, مسح اللوحة وغيرها. +

    + + +
    +
    + +
    + +
    + + diff --git a/resources/startupHints/locales/ar/3.html b/resources/startupHints/locales/ar/3.html new file mode 100644 index 000000000..576f682d6 --- /dev/null +++ b/resources/startupHints/locales/ar/3.html @@ -0,0 +1,73 @@ + + + + + وضع سطح المكتب + + + + + + + + + +
    + +
    +

    التفاعل مع التطبيقات الأخرى

    +

    + يتيح لك وضع سطح المكتب التفاعل مع نظام التشغيل والتطبيقات المختلفة مع إبقاء أدوات OpenBoard متاحة فوقها. +

    +

    ببساطة ما عليك سوى النقر على الأيقونة التالية:

    + +
    + Icon to activate Desktop Mode +
    + +

    + استخدم أداة التحديد + Selector + للتفاعل مع سطح المكتب والتطبيقات الأخرى. +

    +
    + + +
    +

    إضافة ملاحظات على التطبيقات أو مواقع الويب

    +

    + يمكنك إضافة ملاحظات أو الكتابة فوق أي تطبيق باستخدام القلم + Pencil + أو قلم التمييز. + Marker. +

    + +
    + Annotating an application in Desktop Mode +
    مثال على إضافة ملاحظات على صفحة ويب.
    +
    + + +
    +
    + +
    + +
    + + diff --git a/resources/startupHints/locales/ar/4.html b/resources/startupHints/locales/ar/4.html new file mode 100644 index 000000000..f432e0db7 --- /dev/null +++ b/resources/startupHints/locales/ar/4.html @@ -0,0 +1,79 @@ + + + + + وضع المستندات + + + + + + + + + +
    + +
    +

    الوصول إلى مدير المستندات

    +

    + يوفر OpenBoard مديراً للمستندات. انقر على + Documents icon + لفتحه. +

    +
    + Document manager view +
    +
    + + +
    +

    إنشاء مجلدات وتنظيم عملك

    +

    + أنشئ مجلداً باستخدام + New folder, + ثم قم بتسميته واسحب مستنداتك وأفلتها داخله. + وبالمثل, يمكنك نقل مجلد كامل إلى آخر. +

    +
    + Creating and organizing files +
    مثال على إنشاء المجلدات وتنظيمها.
    +
    +
    + + +
    +

    إدارة الصفحات

    +

    + يمكن لمستند OpenBoard أن يحتوي على.عدة صفحات ومن وضع المستندات، يمكنك: +

    +
      +
    • تكرار الصفحات
    • +
    • إرسالها إلى سلة المهملات
    • +
    • نسخها إلى مستند آخر
    • +
    • إنشاء صفحات جديدة من مجلد الصور
    • +
    +
    + Examples of actions on pages +
    مثال على إدارة المستندات.
    +
    + + +
    +
    + +
    +
    + + diff --git a/resources/startupHints/locales/ar/5.html b/resources/startupHints/locales/ar/5.html new file mode 100644 index 000000000..cc1ab9b80 --- /dev/null +++ b/resources/startupHints/locales/ar/5.html @@ -0,0 +1,102 @@ + + + + + + الاستيراد والتصدير + + + + + + + + +
    + +
    +

    الاستيراد & التصدير

    +

    + في هذا الوضع، تتوفر ميزتان أساسيتان: الاستيراد وَ التصدير. +

    +
    + Import and export area in Documents Mode + +
    +
    + + +
    +

    استيراد المحتوى

    +

    + يمكنك استيراد ملفات PDF (.pdf), + وَ الصور (.png, .jpg), + وَ مستندات OpenBoard (ubz.) أو + OpenBoard مجلدات (.ubx) بالنقر على + Import icon. + +

    + +
    + Example of importing files into OpenBoard +
    مثال على استيراد الملفات إلى OpenBoard.
    +
    + +

    + يمكنك أيضاً استيراد مستند OpenBoard (ubz.) من خلال + النقر المزدوج على ملفه من نظام التشغيل. سيُشغَّل OpenBoard عند الحاجة وسيقترح استيراده (أو استبداله إذا كان موجوداً مسبقاً). +

    + + +
    + + +
    +

    تصدير مستنداتك

    +

    + كما يمكنك يمكنك تصدير مستند OpenBoard بصيغة PDF أو UBZ, + و مجلد OpenBoard بصيغة UBX. على سبيل المثال: +

    +
      +
    • حفظ العمل المنجز أثناء الدرس ومشاركته مع طالب غائب.
    • +
    • مشاركته مع معلمين آخرين أو حفظه على وحدة USB لاستيراده في مكان آخر.
    • +
    • استيراد عمل طالب بصيغة PDF، وإضافة الملاحظات عليه، ثم تصدير النسخة المشروحة وإرسالها مجدداً.
    • +
    + +
    + Examples of exporting documents and folders OpenBoard +
    مثال على تصدير الملفات من OpenBoard.
    +
    + + +
    +
    + +
    +
    + + + diff --git a/resources/startupHints/locales/ar/6.html b/resources/startupHints/locales/ar/6.html new file mode 100644 index 000000000..aeef3c4de --- /dev/null +++ b/resources/startupHints/locales/ar/6.html @@ -0,0 +1,89 @@ + + + + + وضع الويب + + + + + + + + + +
    + +
    +

    المتصفح المدمج

    +

    + وضع الويب هو متصفح مدمج داخل OpenBoard يضيف إلى المتصفح التقليدي ميزات عملية جداً للاستخدام داخل الصف الدراسي. +

    +

    + يتيح لك تصفح الإنترنت دون مغادرة OpenBoard، والتقاط المحتوى (جزء من الشاشة أو الشاشة كاملة)، وحتى إنشاء تطبيقات من مواقع الويب لإعادة استخدامها في مستنداتك. +

    + +
    + مثال على استخدام المتصفح المدمج في OpenBoard +
    +
    + + +
    +

    إنشاء تطبيق من موقع ويب

    +

    + يمكنك إنشاء تطبيق من أي موقع ويب وإضافته إلى اللوحة والتفاعل معه كما تتفاعل مع التطبيقات المدمجة. +

    +

    + انتقل إلى الموقع المطلوب، ثم انقر على + أيقونة الإضافة إلى المكتبة + ثم أكّد العملية باختيار "إنشاء تطبيق". +

    + +
    + إنشاء تطبيق ويب باستخدام المتصفح المدمج +
    إنشاء تطبيق من وضع الويب.
    +
    + +

    + بعد إنشائه، يُحفَظ التطبيق تلقائياً في + مكتبة OpenBoard. ستجده ضمن التطبيقات > الويب + ويمكنك إعادة استخدامه في أي مستند. +

    + +
    + تطبيق محفوظ في المكتبة ضمن مجلد الويب +
    العثور على تطبيق تم إنشاؤه في المكتبة
    +
    +
    + + +
    + +

    + يمكنك اختيار فتح الروابط في متصفح خارجي عن طريق تحديد الخيار المناسب في الإعدادات. انقر على + أيقونة وضع الويب + سينطلق متصفحك المفضل كوضع سطح مكتب. +

    + + +
    + +
    + +
    + +
    + + + diff --git a/resources/startupHints/locales/ar/7.html b/resources/startupHints/locales/ar/7.html new file mode 100644 index 000000000..d7177b9a5 --- /dev/null +++ b/resources/startupHints/locales/ar/7.html @@ -0,0 +1,102 @@ + + + + + لقطة شاشة + + + + + + + + + +
    +
    +

    ميزة بسيطة ومفيدة جداً

    +

    + تتيح لك لقطة الشاشة إنشاء لقطة فورية لحالة اللوحة (أو شاشتك) + أثناء دورتك، لحفظها أو مشاركتها أو إعادة استخدامها. +

    +
    + + +
    +

    في وضع اللوحة

    +

    + في لوحة أدوات القلم، انقر على + Capture Area Icon (Board Mode), + ثم حدد المنطقة التي تريد التقاطها. بعد ذلك يمكنك اختيار: +

    +
      +
    • إضافة إلى الصفحة الحالية
    • +
    • إضافة إلى صفحة جديدة
    • +
    • + إضافة إلى المكتبة — يتم حفظ اللقطة في المجلد الصور + للاستخدام المستقبلي. +
    • +
    + +
    + Example of capturing an area in Board Mode +
    مثال على إدراج لقطة شاشة في OpenBoard.
    +
    + + +
    + + +
    +

    في وضع سطح المكتب

    +

    + انتقل إلى وضع سطح المكتب عبر + Desktop Mode icon + لالتقاط محتوى من تطبيقات أخرى. +

    +

    يتوفر خياران:

    +
      +
    • + Screen Capture icon + التقاط الشاشة كاملة +
    • +
    • + Capture area icon + التقاط منطقة محددة +
    • +
    +

    كما في وضع اللوحة، اختر المكان الذي تريد إضافة اللقطة إليه.

    + +
    + Example of capture in Desktop Mode +
    لقطة شاشة كاملة أو جزئية في وضع سطح المكتب.
    +
    +
    + + +
    +

    في وضع الويب

    +

    + في وضع الويب يمكنك التقاط جزء من الشاشة أو + التقاط علامة التبويب النشطة من المتصفح باستخدام + Capture tab icon. +

    +
    +
    + +
    +
    + + + diff --git a/resources/startupHints/locales/ar/8.html b/resources/startupHints/locales/ar/8.html new file mode 100644 index 000000000..a2ee6f67d --- /dev/null +++ b/resources/startupHints/locales/ar/8.html @@ -0,0 +1,101 @@ + + + + + الصور + + + + + + + + + +
    +
    +

    الوصول إلى الصور وتنظيمها

    +

    + في OpenBoard يمكنك الوصول إلى الصور وإضافتها بطرق مختلفة من خلال المكتبة. تقع المكتبة على الجانب الأيمن من الشاشة، كما يمكنك من خلالها تخزين الأصوات ومقاطع الفيديو والعثورعلى تطبيقات متنوعة. +

    +
    + Import and export area in Documents Mode +
    صورة للمكتبة الموجودة على الجانب الأيمن من البرنامج.
    +
    +
    + + +
    +

    تنظيم مجلد الصور

    +

    + تُجمع لقطات الشاشة المضافة إلى المكتبة داخل مجلد + Icon Images. + يمكنك إنشاء مجلدات فرعية باستخدام + New folder icon + (في أسفل المكتبة)، ثم سحب الصور وإفلاتها داخل المجلدات التي أنشأتها. +

    + +
    + Organizing the Pictures folder with subfolders +
    مثال على تنظيم وتصنيف الصور داخل المكتبة.
    +
    + + +
    + + +
    +

    إضافة صور من جهاز الكمبيوتر

    +

    + يمكنك استيراد ملفات الصور الخاصة بك مباشرة إلى OpenBoard, كما هو موضح أدناه: +

    + +
    + Adding images from the computer +
    استيراد صورة محلية إلى OpenBoard.
    +
    +
    + + +
    +

    إضافة صور من الويب

    +

    + يمكنك أيضاً البحث عن صور مجانية عبر محركات البحث المدمجة, + المواقع في المجلد البحث في الويب + Icon Images. +

    + +
    + Adding images from an integrated search engine +
    مثال على العثور على صور من الويب وإضافتها.
    +
    + + +
    +
    + +
    + +
    + + + diff --git a/resources/startupHints/locales/ar/9.html b/resources/startupHints/locales/ar/9.html new file mode 100644 index 000000000..b5130fab6 --- /dev/null +++ b/resources/startupHints/locales/ar/9.html @@ -0,0 +1,106 @@ + + + + + الأنشطة التفاعلية + + + + + + + + + + +
    + +
    +

    تمارين تفاعلية سهلة التخصيص

    +

    + يوفر OpenBoard مجموعة من الأنشطة التفاعلية القابلة للتخصيص، وهي مناسبة للتلاميذ الأصغر سناً وغيرهم، مثل: التصنيف، والحساب الذهني، والتذكر، وغيرها. +

    +

    + يمكن الوصول إليها في المكتبة: انقر على + Interactivities Icon. +

    +
    + + +
    +

    مثال: " تصنيف الصور"

    +

    + لننشئ تمريناً باستخدام النشاط التفاعلي + " تصنيف الصور ": + Thumbnail Categorize Pictures. +

    + + + +

    1) ضع النشاط التفاعلي وافتح المحرر

    +

    + اسحب النشاط التفاعلي وأفلته على اللوحة، ثم انقر على "تعديل ". +

    + +

    2) تسمية الفئات

    +

    + أعد تسمية الفئات حسب الحاجة. يمكنك أيضاً + إضافة فئات أو حذفها باستخدام أيقونتي “+” و “−”. +

    + +
    + Editing interactivity categories +
    تعديل الفئات: الإضافة، الحذف، وإعادة التسمية.
    +
    + +

    3) إضافة الصور

    +

    + أضف الصور المناسبة لكل فئة (بالسحب والإفلات، أو بالاختيار من المكتبة، وغير ذلك). +

    + +
    + Adding images to categories +
    ربط الصور بالفئات المحددة.
    +
    + +

    4) عرض التمرين وتنفيذه

    +

    + انقر على "عرض" لبدء النشاط في وضع الطالب. +

    + +
    + Performing the Categorize Pictures exercise +
    تنفيذ التمرين: انقل الصور إلى الفئة الصحيحة.
    +
    + +
    +
    + +
    +
    + + + + + diff --git a/resources/startupHints/locales/ar/error.html b/resources/startupHints/locales/ar/error.html new file mode 100644 index 000000000..fc9225577 --- /dev/null +++ b/resources/startupHints/locales/ar/error.html @@ -0,0 +1,20 @@ + + + + + خطأ + + + + + +
    +

    خطأ في التحميل

    +

    + حدث خطأ ما. + تحقق من وجود صفحات الإرشادات في المجلد + /Resources/startupHints/. +

    +
    + + diff --git a/resources/startupHints/locales/de/1.html b/resources/startupHints/locales/de/1.html index 6319ac5cb..44735348e 100644 --- a/resources/startupHints/locales/de/1.html +++ b/resources/startupHints/locales/de/1.html @@ -1,26 +1,95 @@ - + - - Tipps und Tricks - - + + Willkommen + + + - -
    -

    Willkommen bei OpenBoard

    -
    -
    -
    -
    -

    Hier sind einige Tipps und Tricks, die Ihnen helfen sollen, sich mit OpenBoard vertraut zu machen.

    -

    OpenBoard ist ein interaktives Whiteboard, das von Lehrern für Lehrer entwickelt wurde. Es besteht aus vier Modi: dem Boardmodus, dem Desktopmodus, dem Dokumentenmodus und dem Internetmodus.

    -

    Beginnen wir mit dem ersten und wichtigsten: dem Boardmodus.

    -
    -
    - Sie können dieses Fenster jederzeit wieder über die Schaltfläche "Tipps und Tricks" im OpenBoard-Menü (the OpenBoard menu) öffnen. -
    + + +
    + + +

    + Hier sind einige Tipps und Tricks, die Ihnen helfen sollen, sich mit OpenBoard vertraut zu machen. +

    + +

    + OpenBoard ist ein interaktives Whiteboard, das von Lehrern für Lehrer entwickelt wurde. + Es bietet vier Modi ergänzend, um Ihre Verwendungsmöglichkeiten im Unterricht abzudecken. +

    + +
    +

    Die 4 Modi von OpenBoard

    + +
    + +
    +

    Beginnen wir mit dem ersten und wichtigsten: Tafelmodus.

    +
    + + +
    + +
    + +
    + + - diff --git a/resources/startupHints/locales/de/10.html b/resources/startupHints/locales/de/10.html index c72287117..69d41cbbd 100644 --- a/resources/startupHints/locales/de/10.html +++ b/resources/startupHints/locales/de/10.html @@ -1,44 +1,95 @@ - - - - - Applikationen - - - - - -
    -

    Applikationen

    -
    -
    -

    OpenBoard bietet verschiedene Apps und Werkzeuge. Sie können auf diese zugreifen, indem Sie auf in der OpenBoard-Bibliothek klicken

    -

    Standard-Apps

    -

    Hier finden Sie geometrische Werkzeuge, einen Taschenrechner, Google Map, einen QR-Code-Generator und vieles mehr! Kombinieren Sie sie, um anregende Lernerfahrungen zu schaffen!

    -
    -

    Erstellen Sie Apps

    -

    Wie im Abschnitt Internetmodus erläutert, können Sie eine App von einer Website aus erstellen, indem Sie den internen Browser verwenden oder eine URL mit Kopieren und Einfügen auf das Board einfügen.

    -

    Damit können Sie Ihren Unterricht mit leistungsstarken Online-Tools aufpeppen, die Sie direkt auf dem Board verwenden können! Hier sind einige Beispiele interessanter Apps (Ziehen Sie sie auf das Board, um sie zu testen!) :

    - - -

    ... und noch viel mehr!

    - - Benötigen Sie einen leistungsfähigeren Taschenrechner als den, der standardmäßig in OpenBoard enthalten ist? Denken Sie daran! Sie können Ihre eigenen Anwendungen erstellen! Ziehen Sie den folgenden Link per Drag & Drop auf Ihr Board: https://ti89-simulator.com/ -
    -
    - -
    - - - + + + + + Anwendungen + + + + + + + + + + +
    + +
    +

    Auf Apps zugreifen

    +

    + OpenBoard bietet verschiedene Apps und Tools. + Finden Sie sie in der Bibliothek, indem Sie auf klicken + Anwendungssymbol. +

    +
    + + +
    +

    Standard-Apps

    +

    + Sie finden geometrische Werkzeuge, einen Taschenrechner, Google Maps, einen QR-Code-Generator und vieles mehr. + Kombinieren Sie sie, um spannende Bildungserlebnisse zu schaffen! +

    + +
    + Beispiel für die Verwendung integrierter Anwendungen +
    Beispiel für die Verwendung der integrierten Anwendungen OpenBoard.
    +
    +
    + + +
    +

    Erstellen Sie Ihre eigenen Apps

    +

    + Wie im Abschnitt erläutert Webmodus, Sie können eine Anwendung von einer Site aus erstellen, + über den internen Browser oder durch Kopieren und Einfügen einer URL auf die Pinnwand. +

    +

    + Dadurch können Sie Ihre Kurse mit leistungsstarken Online-Tools erweitern, die direkt in OpenBoard verwendet werden können. + Hier sind einige interessante Beispiele (ziehen Sie die Links per Drag-and-Drop auf die Pinnwand, um sie zu testen): +

    + + + +

    ...und noch viel mehr!

    + + +
    +
    + +
    + +
    + + + + + diff --git a/resources/startupHints/locales/de/11.html b/resources/startupHints/locales/de/11.html index 0fba2fac1..66516afae 100644 --- a/resources/startupHints/locales/de/11.html +++ b/resources/startupHints/locales/de/11.html @@ -1,39 +1,90 @@ - - - - - Videoaufnahme - - - - - -
    -

    Videoaufnahme

    -
    -
    -

    Nehmen Sie Ihren Kurs auf

    -

    OpenBoard bietet Ihnen die Möglichkeit, Audio- und Videoaufnahmen von Ihrem Unterricht zu machen.

    -

    Dies kann zum Beispiel nützlich sein, um zusätzliche Übungen oder eine Information über einen Videoclip zu teilen oder eine Unterrichtsstunde aufzuzeichnen, um sie mit abwesenden Schülern zu teilen.

    - - Im Boardmodus werden die Symbolleisten und Paletten nicht im Videoclip erscheinen, sodass Sie sich keine Gedanken darüber machen müssen! Dasselbe gilt für den internen Browser, wo nur die aktive Registerkarte erfasst wird. - -

    Sie können auf dieses Tool wie folgt zugreifen:

    -
    - -

    Sie werden folgende Schnittstelle in der rechten unteren Ecke des Boards sehen:

    -
    - -

    Klicken Sie auf die rote Schaltfläche, um die Aufnahme zu starten.

    - - Sie können die Einstellungen anpassen, indem Sie auf klicken. Dort finden Sie Audioeinstellungen (ein Mikrofon auswählen oder keines), Videoeinstellungen (Auflösung der Videoaufnahme) und konfigurierbare Veröffentlichungsoptionen. - -

    Sobald die Aufnahme beendet ist, wird sie gespeichert und ist auf dem Desktop Ihres Computers verfügbar.

    -
    - -
    -
    - -
    - - + + + + + Videoaufnahme + + + + + + + + + + +
    + +
    +

    Zeichnen Sie Ihren Kurs auf

    +

    + OpenBoard ermöglicht Ihnen das Speichern Audio- und die Video Ihres Kurses. + Ideal, um zusätzliche Übungen zu teilen, einen kurzen Erklärclip zu veröffentlichen oder + eine Lehrveranstaltung abwesenden Studierenden zur Verfügung stellen. +

    + + +
    + + +
    +

    Greifen Sie auf das Registrierungstool zu

    +

    Öffnen Sie das Snipping Tool wie unten gezeigt:

    + +
    + Öffnen des Videoaufnahmetools in OpenBoard +
    Schneller Zugriff auf das Registrierungstool über die OpenBoard-Schnittstelle.
    +
    +
    + + +
    +

    Steuerschnittstelle

    +

    Die Schnittstelle erscheint unten rechts in der Tabelle:

    + +
    + Recorder-Schnittstelle (Bedienelemente unten rechts) +
    + +

    Klicken Sie auf roter Knopf um mit der Aufnahme zu beginnen.

    + + +
    + + +
    +

    Beenden Sie das Video und stellen Sie es wieder her

    +

    + Sobald die Aufnahme abgeschlossen ist, wird das Video automatisch abgespielt auf dem Desktop gespeichert Ihres Computers. +

    + +
    + Beenden Sie die Aufnahme und speichern Sie das Video auf dem Desktop +
    Stoppen Sie die Aufnahme und die Videodatei ist auf dem Desktop verfügbar.
    +
    +
    +
    + +
    + +
    + + diff --git a/resources/startupHints/locales/de/12.html b/resources/startupHints/locales/de/12.html index 66b003714..a07b8c7fb 100644 --- a/resources/startupHints/locales/de/12.html +++ b/resources/startupHints/locales/de/12.html @@ -1,37 +1,94 @@ - - - - - Favoriten-Dokumente - - - - - -
    -

    Favoriten-Dokumente

    -
    -
    -

    OpenBoard-Dokumente zu Favoriten hinzufügen

    -

    Als neue Funktion in Version 1.7 können Sie OpenBoard-Dokumente zu Ihren Favoriten hinzufügen!

    -

    Gehen Sie dazu in den Dokumentenmodus, wählen Sie ein Dokument aus und klicken Sie auf in der Symbolleiste des Dokumentenmodus.

    - -
    - -

    Schnell von einem Dokument zum anderen wechseln

    -

    Ihre Lieblingsdokumente erscheinen in der OpenBoard-Bibliothek unter
    - Jedes dieser OpenBoard-Dokumente () kann dann per Drag & Drop auf das Board gezogen werden!

    -
    - - Sie können die Suchleiste am unteren Rand der OpenBoard Bibliothek verwenden, um ein Dokument anhand seines Namens zu finden - -

    Zuletzt geöffnete Dokumente

    -

    Jedes geöffnete Dokument wird vorübergehend zu den Favoriten hinzugefügt, sodass Sie während einer Sitzung schnell zwischen ihnen wechseln können, ohne sie explizit als Favoriten markieren zu müssen.

    -
    - Wenn Sie ein kürzlich geöffnetes Dokument dauerhaft zu den Favoriten hinzufügen möchten, können Sie dies über den Ordner Favoriten in der OpenBoard Bibliothek tun: Markieren Sie es und klicken Sie auf -
    -
    - -
    - - + + + + + Lieblingsdokumente + + + + + + + + + + +
    + +
    +

    Fügen Sie OpenBoard Dokumente zu Ihren Favoriten hinzu

    +

    + Eingeführt in die Version 1.7Mit dieser Funktion können Sie Ihre OpenBoard-Dokumente hinzufügen + in Favoriten. +

    +

    + Gehen Sie dazu zu Dokumentenmodus, wählen Sie ein Dokument aus und klicken Sie auf + Symbol „Zu Favoriten hinzufügen“. + in der Symbolleiste. +

    + +
    + Hinzufügen eines OpenBoard-Dokuments zu den Favoriten + +
    +
    + + +
    +

    Wechseln Sie schnell von einem Dokument zum anderen

    +

    + Ihre Lieblingsdokumente werden im angezeigt Bibliothek OpenBoard, unten + Favoriten-Symbol. +

    +

    + Jedes Lieblingsdokument + Dokumentsymbol OpenBoard + können per Drag & Drop direkt auf den Tisch gezogen werden. +

    + +
    + Favoriten in der Bibliothek OpenBoard anzeigen +
    + + + +
    + + +
    +

    Kürzlich geöffnete Dokumente

    +

    + Jedes geöffnete Dokument wird vorübergehend zu den Favoriten hinzugefügt, um schnell von einem zum anderen wechseln zu können + während derselben Sitzung, ohne sie explizit markieren zu müssen. +

    + +
    + Beispiel für kürzlich geöffnete Dokumente in OpenBoard + +
    + + +
    +
    + +
    +
    + + diff --git a/resources/startupHints/locales/de/2.html b/resources/startupHints/locales/de/2.html index ce479061f..c0a566106 100644 --- a/resources/startupHints/locales/de/2.html +++ b/resources/startupHints/locales/de/2.html @@ -1,47 +1,122 @@ - - - - - Board-Modus - - - - - -
    -

    Board-Modus

    -
    -
    -

    Die Stiftpalette

    -

    Mit der Stiftpalette haben Sie Zugriff auf die wichtigsten Werkzeuge, wenn Sie an einem Whiteboard arbeiten.

    -
    - -

    Dort finden Sie den Bleistift (), den Radiergummi () und den Textmarker (). OpenBoard bietet außerdem Funktionen, die ein herkömmliches Whiteboard nicht bieten kann :

    -
      -
    •  ist der Selektor. Damit können Sie Objekte auswählen und mit ihnen interagieren.
    • -
    •  ist der magische Finger. Sie können Objekte bewegen oder mit ihnen interagieren, ohne dass Sie sie auswählen müssen.
    • -
    • ist die Hand. Damit können Sie die Seite bewegen. Sehr nützlich, wenn sie mit den Zoomfunktionen kombiniert wird!
    • -
    •  geben Ihnen die Möglichkeit, ein- und auszuzoomen. Wählen Sie einfach den gewünschten Effekt aus und klicken Sie auf die Stelle, auf die Sie ihn anwenden möchten!
    • -
    •  ist ein Laserpointer, der nützlich ist, um auf Elemente auf dem Board zu zeigen, ohne mit diesen zu interagieren.
    • -
    • wird zum einfachen Zeichnen von geraden Linien verwendet.
    • -
    •  ist eine Textbox, die es ermöglicht, mit der Tastatur statt mit einem Bleistift zu schreiben.
    • -
    - - Mit der Maus können Sie den Zoom feiner steuern! Verwenden Sie Strg/Befehl + Mausrad, um genauer hinein-/herauszuzoomen! - -

    Die Werkzeugleiste des Boards

    -

    Mit der Werkzeugleiste können Sie unter anderem die Farbe und Größe des Stifts und Markers ändern.

    -
    - -

    Dort finden Sie auch die Möglichkeit, den Hintergrund des Boards zu ändern, indem Sie auf klicken.

    -
    - -

    Außerdem finden Sie hier Funktionen wie Rückgängigmachen/Wiederherstellen, Erstellen und von Seite zu Seite Navigieren, Löschen des Boards, ...

    - - Sie können spezifischere Elemente des Boards vollständig löschen, indem Sie einen langen Klick auf the erase icon machen. Einige zusätzliche Funktionen erreichen Sie so auch für the new page icon ! -
    -
    - -
    - - + + + + + Tafelmodus + + + + + + + + +
    + +
    +

    Die Stiftpalette

    +

    + Mit der Stiftpalette haben Sie beim Arbeiten an einem Whiteboard Zugriff auf wichtige Werkzeuge. +

    + +
    + OpenBoard Stiftpalette +
    + +

    + Dort finden Sie den Bleistift + Bleistift, + das Zahnfleisch + Radiergummi + und der Textmarker + Textmarker. + OpenBoard bietet außerdem Funktionen, die ein herkömmliches Whiteboard nicht bieten kann: +

    + +
      +
    • + + Wähler : Objekte auswählen und mit ihnen interagieren. +
    • +
    • + + Magischer Finger : Bewegen/Interagieren ohne vorherige Auswahl. +
    • +
    • + + Hand : Seite verschieben (sehr nützlich in Kombination mit Zoom). +
    • +
    • + + + Zoomen / Verkleinern : Klicken Sie auf die Stelle, an der Sie den Effekt anwenden möchten, nachdem Sie ihn ausgewählt haben. +
    • +
    • + + Laserpointer : Zeigen Sie auf Elemente, ohne mit ihnen zu interagieren. +
    • +
    • + + Linie : Einfaches Zeichnen gerader Linien. +
    • +
    • + + Text : Fügen Sie ein Textfeld zum Tippen auf der Tastatur ein. +
    • +
    + + +
    + + +
    +

    Die Tischbar

    +

    + In der Tabellenleiste können Sie die ändern Farbe und/oder die Größe unter anderem Bleistift und Textmarker. +

    + +
    + OpenBoard Tabellenleiste +
    + +

    + Sie können auch die ändern Hintergrund der Tabelle indem Sie auf klicken + Ändern Sie den Hintergrund. +

    + +
    + Beispiel für die Änderung des Tabellenhintergrunds +
    + +

    + Weitere Funktionen stehen zur Verfügung: rückgängig machen/wiederholen, Seiten erstellen, zwischen Seiten navigieren, die Tabelle löschen usw. +

    + + +
    +
    + +
    + +
    + + diff --git a/resources/startupHints/locales/de/3.html b/resources/startupHints/locales/de/3.html index fd31a49f4..ba2604b29 100644 --- a/resources/startupHints/locales/de/3.html +++ b/resources/startupHints/locales/de/3.html @@ -1,30 +1,74 @@ - - - - - Desktop-Modus - - - - - -
    -

    Desktop-Modus

    -
    -
    -

    Mit anderen Apps interagieren

    -

    Wenn Sie den Desktop-Modus verwenden, können Sie mit Ihrem Betriebssystem und anderer Software interagieren, während OpenBoard darüber liegt !

    -

    Klicken Sie einfach auf das folgende Symbol :

    -
    -
    Verwenden Sie den Selektor (), um mit dem Desktop und anderen Anwendungen zu interagieren.
    -

    Apps oder Webseiten annotieren

    -
    Sie können jede Software mit Anmerkungen versehen, indem Sie den Bleistift () oder den Marker () verwenden.
    -
    - - Sie können auf weitere Optionen für the pen, the marker, und the eraser zugreifen, indem Sie sie mit einem langen Klick anklicken oder auf den kleinen schwarzen Pfeil klicken. -
    -
    - -
    - - + + + + + Desktop-Modus + + + + + + + + + +
    + +
    +

    Interagieren Sie mit anderen Apps

    +

    + Der Büromodus ermöglicht Ihnen die Interaktion mit Ihrem Betriebssystem und Ihrer Software, + unter Beibehaltung der OpenBoard-Tools oben. +

    +

    Klicken Sie einfach auf das folgende Symbol:

    + +
    + Symbol zum Aktivieren von Desktop-Modus +
    + +

    + Benutzen Sie die Wähler + Wähler + um mit dem Desktop und anderen Anwendungen zu interagieren. +

    +
    + + +
    +

    Kommentieren Sie Apps oder Websites

    +

    + Sie können jede Software mit Anmerkungen versehen Bleistift + Bleistift + oder die Marker + Marker. +

    + +
    + Kommentieren einer Anwendung in Desktop-Modus +
    Beispiel einer Anmerkung auf einer Webseite.
    +
    + + +
    +
    + +
    + +
    + + diff --git a/resources/startupHints/locales/de/4.html b/resources/startupHints/locales/de/4.html index 08f334715..20175b7a7 100644 --- a/resources/startupHints/locales/de/4.html +++ b/resources/startupHints/locales/de/4.html @@ -1,32 +1,79 @@ - - - - - Dokumenten-Modus - - - - - -
    -

    Dokumenten-Modus

    -
    -
    -

    OpenBoard bietet einen Dokumentenmanager. Sie können darauf zugreifen, indem Sie einfach auf klicken.

    -
    - -

    Legen Sie Ordner an und organisieren Sie Ihre Arbeit

    -

    Sie können einen Ordner erstellen, indem Sie auf "Neuer Ordner" klicken. Benennen Sie ihn und ziehen Sie Ihre Dokumente per Drag & Drop hinein. Auf die gleiche Weise können Sie einen ganzen Ordner in einen anderen verschieben.

    -
    - -

    Seitenverwaltung

    -

    Ein OpenBoard-Dokument kann mehrere Seiten enthalten. Über den Dokumentenmodus können Sie sie duplizieren, in den Papierkorb legen oder in ein anderes Dokument kopieren, neue Seiten aus einem Bildordner erstellen, ...

    -
    - - Beachten Sie, dass die verfügbaren Aktionen je nachdem, was ausgewählt wird, aktualisiert werden. -
    -
    - -
    - - + + + + + Dokumentenmodus + + + + + + + + + +
    + +
    +

    Greifen Sie auf den Dokumentenmanager zu

    +

    + OpenBoard bietet a Dokumentenmanager. Klicken Sie auf + Symbol „Dokumente“. + um es zu öffnen. +

    +
    + Ansicht des Dokumentenmanagers +
    +
    + + +
    +

    Erstellen Sie Ordner und organisieren Sie Ihre Arbeit

    +

    + Erstellen Sie einen Ordner über + Neuer Ordner, + Nennen Sie es dann Drag & Drop Ihre Dokumente darin. + Ebenso können Sie einen ganzen Ordner in einen anderen verschieben. +

    +
    + Dateien erstellen und organisieren +
    Beispiel für das Erstellen und Organisieren von Ordnern.
    +
    +
    + + +
    +

    Seitenverwaltung

    +

    + Ein OpenBoard-Dokument kann enthalten mehrere Seiten. Von Dokumentenmodus aus können Sie: +

    +
      +
    • Doppelte Seiten
    • +
    • Schicken Sie sie in den Papierkorb
    • +
    • Kopieren Sie sie in ein anderes Dokument
    • +
    • Erstellen Sie neue Seiten aus einem Bilderordner
    • +
    +
    + Beispiele für Aktionen auf Seiten +
    Beispiel für den Umgang mit Dokumenten.
    +
    + + +
    +
    + +
    +
    + + diff --git a/resources/startupHints/locales/de/5.html b/resources/startupHints/locales/de/5.html index bf51f1176..5a38dff97 100644 --- a/resources/startupHints/locales/de/5.html +++ b/resources/startupHints/locales/de/5.html @@ -1,39 +1,105 @@ - - - - - Dokumenten-Modus - - - - - -
    -

    Dokumenten-Modus

    -
    -
    -

    In diesem Modus finden Sie auch zwei sehr wichtige Funktionen: den Import und den Export.

    -
    - -

    Import

    -

    Sie können PDF-Dateien (.pdf), Bilder (.png, .jpg), OpenBoard-Dokumente (.ubz) oder OpenBoard-Ordner (.ubx) importieren, indem Sie anklicken.

    -
    -

    Beachten Sie, dass Sie auch ein OpenBoard-Dokument (.ubz) importieren können, indem Sie darauf doppelklicken. Dies startet OpenBoard, wenn es nicht bereits gestartet ist, und importiert es (oder schlägt vor, die Datei zu ersetzen, wenn sie bereits vorhanden ist).

    - Sie können auch mehrere Elemente auf einmal importieren, ohne dass Sie immer und immer wieder auf klicken müssen. -

    Export

    -

    Sie können ein OpenBoard-Dokument im PDF- oder UBZ-Format und einen OpenBoard-Ordner im UBX-Format exportieren. So können Sie zum Beispiel : -

      -
    • Die während einer Unterrichtsstunde geleistete Arbeit speichern und sie mit einem abwesenden Schüler teilen.
    • -
    • Es mit anderen Lehrern teilen oder auf einem USB-Stick speichern, um es auf einen anderen Computer zu importieren.
    • -
    • Die Arbeit eines Schülers im PDF-Format importieren, mit Anmerkungen versehen und das mit Anmerkungen versehene Dokument im PDF-Format exportieren, um es an den Schüler zurückzuschicken
    • -
    -

    -
    - - Exportieren Sie alle Ihre Dokumente, indem Sie auf den Stammordner "Meine Dokumente" klicken, bevor Sie auf klicken, um sie im UBX-Format zu exportieren. Sie können sie dann auf einem anderen Computer importieren. Dies ist auch eine gute Möglichkeit, ein Archiv oder eine Sicherungskopie Ihrer gesamten Arbeit zu erstellen! -
    -
    - -
    - - + + + + + + Import und Export + + + + + + + + + + +
    + +
    +

    Importieren und Exportieren

    +

    + In diesem Modus stehen Ihnen zwei wesentliche Funktionen zur Verfügung: Import Und Export. +

    +
    + Import- und Exportbereich in Dokumentenmodus + +
    +
    + + +
    +

    Inhalte importieren

    +

    + Sie können Dateien importieren PDF (.pdf), + des Bilder (.png, .jpg), + des Dokumente OpenBoard (.ubz) oder + OpenBoard Ordner (.ubx), indem Sie auf klicken + Symbol „Importieren“.. + +

    + +
    + Beispiel für den Import von Dateien in OpenBoard +
    Beispiel für den Import von Dateien in OpenBoard.
    +
    + +

    + Sie können auch ein OpenBoard-Dokument importieren (.ubz) In + doppelklicken darauf von Ihrem System. OpenBoard wird bei Bedarf gestartet und + bietet den Import an (oder ersetzt ihn, falls er bereits vorhanden ist). +

    + + +
    + + +
    +

    Exportieren Sie Ihre Dokumente

    +

    + Sie können eine exportieren Dokument OpenBoard im Format PDF Oder UBZ, + und a Ordner OpenBoard im Format UBX. Zum Beispiel : +

    +
      +
    • Zeichnen Sie die während einer Unterrichtsstunde geleistete Arbeit auf und teilen Sie sie mit einem abwesenden Schüler.
    • +
    • Teilen Sie es mit anderen Lehrern oder speichern Sie es auf einem USB-Stick, um es woanders zu importieren.
    • +
    • Importieren Sie die Arbeit eines Schülers als PDF, versehen Sie sie mit Anmerkungen und exportieren Sie dann die kommentierte PDF-Datei zum erneuten Senden.
    • +
    + +
    + Beispiele für den Export von Dokumenten und Ordnern OpenBoard +
    Beispiel für den Export von Dateien aus OpenBoard.
    +
    + + +
    +
    + +
    +
    + + + diff --git a/resources/startupHints/locales/de/6.html b/resources/startupHints/locales/de/6.html index f66431dfc..cc12d021f 100644 --- a/resources/startupHints/locales/de/6.html +++ b/resources/startupHints/locales/de/6.html @@ -1,38 +1,96 @@ - - - - - Internet-Modus - - - - - -
    -

    Internet-Modus

    -
    -
    - Der Internetmodus ist ein interner Browser, der einen traditionelleren Browser um einige sehr interessante Funktionen erweitert. -

    Verwendung des internen Browsers

    -

    Mithilfe des internen Browsers haben Sie die Möglichkeit, das Internet zu durchsuchen, ohne OpenBoard zu minimieren oder zu verlassen, mit zusätzlichen Funktionen, die es Ihnen ermöglichen, einen Teil oder den gesamten Bildschirm zu erfassen oder Apps von Websites zu erstellen und sie dem Board hinzuzufügen!

    - -
    - -

    Eine App erstellen

    -

    Sie können eine App von jeder beliebigen Website aus erstellen, um sie dem Board hinzuzufügen und mit ihr zu interagieren, genau wie mit den Standardanwendungen.

    -

    Gehen Sie dazu auf die Website, die Sie erfassen möchten, klicken Sie auf Capture Web Content und bestätigen Sie ihre Erstellung, indem Sie auf "Anwendung erstellen" klicken.

    -
    - -

    Wenn eine Anwendung erstellt wird, wird sie automatisch in der OpenBoard-Bibliothek gespeichert, sodass Sie sie in jedem beliebigen Dokument wiederverwenden können. Sie finden sie unter dem Ordner "Anwendungen" in einem Unterordner namens "Internet".

    - -
    - -

    Verwendung des externen Browsers

    -

    Sie können auch einen externen Browser verwenden, indem Sie die entsprechende Option in den Einstellungen ankreuzen. Wenn Sie auf klicken, wird Ihr bevorzugter Browser dann im Desktopmodus gestartet.

    - Wenn Sie einen externen Browser verwenden, können Sie immer noch eine App von einer Website aus erstellen, indem Sie die URL in der Adresszeile Ihres Browsers kopieren und sie dann einfügen, wenn Sie wieder im Boardmodus sind! -
    -
    - -
    - - + + + + + Webmodus + + + + + + + + + +
    + +
    +

    Der integrierte Browser

    +

    + DER Webmodus ist ein interner Browser von OpenBoard, der zu einem Browser hinzugefügt wird + klassische, sehr praktische Features für den Unterricht. +

    +

    + Es ermöglicht Ihnen, im Internet zu surfen, ohne OpenBoard zu verlassen, um Inhalte (teilweise oder teilweise) zu erfassen + Vollbild) und sogar Apps erstellen von Websites für + Wiederverwendung in Ihren Dokumenten. +

    + +
    + Beispiel für die Verwendung des integrierten Browsers von OpenBoard +
    +
    + + +
    +

    Erstellen Sie eine Anwendung von einer Site

    +

    + Von jeder Site aus können Sie eine erstellen Anwendung um es hinzuzufügen + auf dem Board und interagieren Sie damit wie mit den Standardanwendungen. +

    +

    + Gehen Sie zur gewünschten Seite und klicken Sie auf + Symbol „Zur Bibliothek hinzufügen“. + Anschließend durch Auswählen bestätigen Erstellen Sie eine App. +

    + +
    + Erstellen einer Webanwendung über den integrierten Browser +
    Erstellung einer Anwendung aus dem Webmodus.
    +
    + +

    + Nach der Erstellung wird die Anwendung automatisch im gespeichert + Bibliothek OpenBoard. Sie finden es weiter unten Anwendungen > Web + und kann es in jedem Dokument wiederverwenden. +

    + +
    + Anwendung in Bibliothek, Webordner gespeichert +
    Suchen Sie eine in Ihrer Bibliothek erstellte Anwendung.
    +
    +
    + + +
    + +

    + Sie können die Links in einem öffnen externer Browser durch Überprüfung + die entsprechende Option in den Einstellungen. Klicken Sie auf + Webmodus-Symbol + startet dann Ihren Lieblingsbrowser als Desktop-Modus. +

    + + +
    + +
    + +
    + +
    + + + diff --git a/resources/startupHints/locales/de/7.html b/resources/startupHints/locales/de/7.html index ce1ab539d..36b706a16 100644 --- a/resources/startupHints/locales/de/7.html +++ b/resources/startupHints/locales/de/7.html @@ -1,44 +1,102 @@ - - - - - Bildschirmaufnahme - - - - - -
    -

    Bildschirmaufnahme

    -
    -
    - Eine der grundlegenden, aber wirklich nützlichen Funktionen von OpenBoard ist der Screenshot. Damit können Sie während Ihres Unterrichts einen Schnappschuss von einem beliebigen Zustand des Boards erstellen. -

    Im Boardmodus

    -

    Suchen Sie the screen capture icon in der Stiftpalette, klicken Sie darauf und wählen Sie den Bereich aus, den Sie erfassen möchten. Anschließend werden Ihnen drei Optionen angeboten :

    -
      -
    • Zur aktuellen Seite hinzufügen
    • -
    • Zur neuen Seite hinzufügen
    • -
    • Zur Bibliothek hinzufügen (fügt den Schnappschuss dem Bilder Ordner hinzu, sodass Sie ihn jederzeit wiederverwenden können)
    • -
    - -

    Hier ein Beispiel für den gesamten Prozess :

    -
    - - Sie müssen sich keine Gedanken um die Stiftpalette, die Seitenvorschau oder die Registerkarte für die OpenBoard-Bibliothek machen - sie werden nicht von Ihrem Lasso eingefangen! - -

    Im Desktop-Modus

    -

    Dasselbe können Sie im Desktop-Modus tun, um Ihre Arbeit in anderen Programmen zu erfassen. Wechseln Sie in den Desktop-Modus, indem Sie auf klicken.

    -

    Sie finden zwei Symbole: um den gesamten Bildschirm zu erfassen, und um nur einen Teil davon zu erfassen. Wie im Boardmodus müssen Sie auswählen, wo Sie die Aufnahme hinzufügen möchten

    - -

    Hier eine Illustration des gesamten Prozesses :

    -
    - - -

    Im Internetmodus

    -

    In ähnlicher Weise ist dies auch im Internetmodus möglich, wo Sie eine Teilaufnahme des Bildschirms machen oder den aktiven Tab des Browsers erfassen können, indem Sie anklicken

    -
    -
    - -
    - - + + + + + Screenshot + + + + + + + + + +
    +
    +

    Eine einfache und sehr nützliche Funktion

    +

    + Mit dem Screenshot können Sie einen erstellen sofort der Zustand des Boards (oder Ihres Bildschirms) + während Ihres Kurses, um Inhalte zu archivieren, zu teilen oder wiederzuverwenden. +

    +
    + + +
    +

    Im Tafelmodus

    +

    + Klicken Sie in der Stiftpalette auf + Symbol für den Erfassungsbereich (Tafelmodus), + Wählen Sie dann den zu erfassenden Bereich aus. Sie können dann wählen: +

    +
      +
    • Zur aktuellen Seite hinzufügen
    • +
    • Zu neuer Seite hinzufügen
    • +
    • + Zur Bibliothek hinzufügen — Die Aufnahme wird im Ordner gespeichert Bilder + zur späteren Wiederverwendung. +
    • +
    + +
    + Beispiel für die Erfassung eines Bereichs in Tafelmodus +
    Beispiel für die Einbettung eines Screenshots in OpenBoard.
    +
    + + +
    + + +
    +

    In Desktop-Modus

    +

    + Wechseln Sie zu Desktop-Modus über + Desktop-Modus-Symbol + um andere Software zu erfassen. +

    +

    Es stehen zwei Optionen zur Verfügung:

    +
      +
    • + Symbol für Bildschirmaufnahme + Vollbildaufnahme +
    • +
    • + Symbol für den Erfassungsbereich + Erfassen eines Bereichs +
    • +
    +

    Wahlen Sie wie im Tafelmodus aus, wo die Aufnahme eingefugt werden soll.

    + +
    + Beispiel für die Erfassung in Desktop-Modus +
    Vollständiger oder teilweiser Screenshot in Desktop-Modus.
    +
    +
    + + +
    +

    In Webmodus

    +

    + In Webmodus können Sie einen Bereich erobern des Bildschirms bzw + Aktive Registerkarte erfassen aus dem Browser mit + Symbol für die Registerkarte „Aufnahme“.. +

    +
    +
    + +
    +
    + + + diff --git a/resources/startupHints/locales/de/8.html b/resources/startupHints/locales/de/8.html index ab34f00a8..b661833c5 100644 --- a/resources/startupHints/locales/de/8.html +++ b/resources/startupHints/locales/de/8.html @@ -1,36 +1,102 @@ - - - - - Bilder - - - - - -
    -

    Bilder

    -
    -
    - In OpenBoard können Sie auf Bilder auf verschiedene Arten zugreifen. -

    Organisieren Sie Ihren Bilder-Ordner

    -

    Screenshots, die der Bibliothek hinzugefügt wurden, finden Sie im Ordner Bilder :
    Sie können Unterordner erstellen, indem Sie ganz unten in der Bibliothek verwenden. Ziehen Sie dann Ihre Bilder per Drag & Drop in die verschiedenen Ordner, die Sie erstellt haben.

    -
    - - Egal, wo Sie sich in der OpenBoard-Bibliothek befinden, die Bilder, die Sie hinzufügen, werden automatisch innerhalb des Bilder-Ordners platziert , so dass die Bibliothek organisiert bleibt. - -

    Fügen Sie Bilder von Ihrem Computer hinzu

    -

    Sie können auch Bilder von Ihrem Computer hinzufügen, wie hier dargestellt :

    -
    - -

    Fügen Sie Bilder aus Suchmaschinen hinzu

    -

    Schließlich können Sie auch nach lizenzfreien Bildern suchen und diese zum Board hinzufügen, indem Sie die Suchmaschinen im Ordner "Websuche" verwenden:

    -
    - - Anstatt Drag-and-Drop zu verwenden, klicken Sie einfach auf ein Bild, das als Suchergebnis erscheint, um Details zu sehen und Optionen wie Zur Bibliothek hinzufügen zu finden. Nützlich bei der Vorbereitung einer Unterrichtsstunde! -
    -
    - -
    - - + + + + + Bilder + + + + + + + + + +
    +
    +

    Greifen Sie auf Ihre Bilder zu und organisieren Sie sie

    +

    + In OpenBoard können Sie von Ihrem aus auf verschiedene Arten auf Bilder zugreifen und diese hinzufügen Bibliothek. Letzteres befindet sich auf der rechten Seite des Bildschirms. Hier können Sie auch Sounds und Videos speichern und verschiedene Anwendungen finden. +

    +
    + Import- und Exportbereich in Dokumentenmodus +
    Bild der Bibliothek rechts in der Software.
    +
    +
    + + +
    +

    Organisieren Sie Ihren Bilderordner

    +

    + Zur Bibliothek hinzugefügte Screenshots werden im Ordner gruppiert + Symbolbilder. + Sie können erstellen Unterordner über + Neues Ordnersymbol + (unten in der Bibliothek) und ziehen Sie Ihre Bilder dann per Drag & Drop in die verschiedenen erstellten Ordner. +

    + +
    + Organisieren des Bilderordners mit Unterordnern +
    Beispiel für die Organisation und Klassifizierung von Bildern in der Bibliothek.
    +
    + + +
    + + +
    +

    Fügen Sie Bilder von Ihrem Computer hinzu

    +

    + Sie können Ihre eigenen Bilddateien direkt in OpenBoard importieren, wie unten gezeigt: +

    + +
    + Bilder vom Computer hinzufügen +
    Importieren eines lokalen Bildes in OpenBoard.
    +
    +
    + + +
    +

    Fügen Sie Bilder aus dem Internet hinzu

    +

    + Sie können auch nach suchen lizenzfreie Bilder über integrierte Suchmaschinen, + befindet sich im Ordner Websuche + Symbolbilder. +

    + +
    + Hinzufügen von Bildern aus einer integrierten Suchmaschine +
    Beispiel für das Suchen und Hinzufügen von Bildern aus dem Internet.
    +
    + + +
    +
    + +
    + +
    + + + diff --git a/resources/startupHints/locales/de/9.html b/resources/startupHints/locales/de/9.html index 8ca9d819d..52138850f 100644 --- a/resources/startupHints/locales/de/9.html +++ b/resources/startupHints/locales/de/9.html @@ -1,43 +1,99 @@ - - - - - Interaktivitäten - - - - - -
    -

    Interaktivitäten

    -
    -
    - Für jüngere Schülerinnen und Schüler finden Sie eine Reihe von anpassbaren Anwendungen, die speziell auf diese Zielgruppe zugeschnitten sind. -

    Die interaktiven Übungen von OpenBoard

    -

    Mit diesen Interaktivitäten können Sie verschiedene Arten von Übungen erstellen. Dabei kann es sich um Kategorisierung, Kopfrechnen, Auswendiglernen usw. handeln.

    -

    Die Interaktivitäten befinden sich in der OpenBoard-Bibliothek: Klicken Sie auf , um sie zu finden.

    - -

    Beispiele

    -

    Wir werden eine Übung erstellen, die die Interaktivität "Categorize pictures" (übersetzt: Bilder kategorisieren) verwendet:

    - Probieren Sie es aus! Verschieben Sie dieses Fenster auf eine Seite und reproduzieren Sie das Beispiel auf der Pinnwand! -

    Zunächst ziehen Sie die Interaktivität per Drag & Drop auf das Board und klicken auf "Bearbeiten".

    -

    Ändern Sie dann die Namen der Kategorien nach Bedarf :

    -
    - - Sie können Kategorien mithilfe der Symbole + und - hinzufügen oder entfernen. - -

    Fügen Sie dann die entsprechenden Bilder zu jeder Kategorie hinzu.

    -
    - -

    Klicken Sie schließlich auf "Anzeigen", um das Ergebnis zu sehen und die Übung durchzuführen.

    -
    - - Sie können die Übung erneut starten, indem Sie auf das Symbol "Neu laden" klicken. - - -
    -
    - -
    - - + + + + + Interaktivitäten + + + + + + + + + + +
    + +
    +

    Interaktive Übungen, einfach zu personalisieren

    +

    + Für die jüngsten Schüler (und nicht nur) bietet OpenBoard ein Set + vonInteraktivitäten anpassbar: Kategorisierung, mentale Berechnung, Auswendiglernen usw. +

    +

    + Sie sind im zugänglich Bibliothek : klicken Sie auf + Interaktivitätssymbol. +

    +
    + + +
    +

    Beispiel: „Bilder kategorisieren“

    +

    + Lassen Sie uns eine Übung mit Interaktivität erstellen + Katzeegorisieren Bildures (Bilder kategorisieren): + Miniaturbilder kategorisieren. +

    + + + +

    1) Platzieren Sie die Interaktivität und öffnen Sie den Editor

    +

    + Ziehen Sie die Interaktivität per Drag-and-Drop auf das Board und klicken Sie dann Zu ändern. +

    + +

    2) Benennen Sie die Kategorien

    +

    + Benennen Sie die Kategorien nach Bedarf um. Das können Sie auch + hinzufügen Oder LÖSCHEN Kategorien mit den Symbolen „+“ und „−“. +

    + +
    + Bearbeiten von Interaktivitätskategorien +
    Kategorien bearbeiten: Hinzufügen, Löschen, Umbenennen.
    +
    + +

    3) Fügen Sie die Bilder hinzu

    +

    + Fügen Sie Bilder entsprechend jeder Kategorie hinzu (Drag & Drop, Auswahl aus der Bibliothek usw.). +

    + +
    + Bilder zu Kategorien hinzufügen +
    Verknüpfen Sie Bilder mit definierten Kategorien.
    +
    + +

    4) Zeigen Sie die Übung und machen Sie sie

    +

    + Klicken Sie auf Anzeige um die Aktivität im Studentenmodus zu starten. +

    + +
    + Durchführen der Übung „Bilder kategorisieren“. +
    Durchführung der Übung: Verschieben Sie die Bilder in die richtige Kategorie.
    +
    + + +
    +
    + +
    +
    + + diff --git a/resources/startupHints/locales/de/css/style.css b/resources/startupHints/locales/de/css/style.css deleted file mode 100644 index de0ecb4d5..000000000 --- a/resources/startupHints/locales/de/css/style.css +++ /dev/null @@ -1,36 +0,0 @@ -*{ - font-family: Arial, Helvetica, sans-serif; -} - -.title -{ - width:100%; - color: #6682b5; - text-align: center; -} - -.file-extension -{ - background: lightgrey; -} - -.image -{ - text-align:center; -} - -.image img -{ - max-width: 600px; -} - -.icon -{ - vertical-align: middle; -} - -.tip -{ - font-style: italic; - font-size: 0.8rem; -} diff --git a/resources/startupHints/locales/de/error.html b/resources/startupHints/locales/de/error.html index e396c93ee..9d4d207f5 100644 --- a/resources/startupHints/locales/de/error.html +++ b/resources/startupHints/locales/de/error.html @@ -1,15 +1,20 @@ - - - - - Fehler - - - - - -

    Etwas ist schief gelaufen ... -Überprüfen Sie die Existenz Ihrer Tipps im Ordner -/resources/startupHints/ …

    - - + + + + + Fehler + + + + + +
    +

    Fehler beim Laden

    +

    + Etwas ist schief gelaufen. + Überprüfen Sie, ob im Ordner Tipps-Seiten vorhanden sind + /Resources/startupHints/. +

    +
    + + diff --git a/resources/startupHints/locales/en/1.html b/resources/startupHints/locales/en/1.html index af197854a..a2841f8c3 100644 --- a/resources/startupHints/locales/en/1.html +++ b/resources/startupHints/locales/en/1.html @@ -1,26 +1,95 @@ - + - - Hints and tips - - + + Welcome + + + - -
    -

    Welcome to OpenBoard

    -
    -
    -
    -
    -

    Here's some hints and tips in order to help you familiarize with OpenBoard.

    -

    OpenBoard is an interactive whiteboard designed by teachers, for teachers. It comes with 4 modes : Board Mode, Desktop Mode, Documents Mode and Web Mode.

    -

    Let's jump in the first, and most important one : the Board Mode.

    -
    -
    - You can reopen the "Hints and tips" dialog at any time, using the dedicated button on the OpenBoard menu (the OpenBoard menu) -
    + + +
    + + +

    + Here are some tips and tricks to help you get familiar with OpenBoard. +

    + +

    + OpenBoard is an interactive whiteboard designed by teachers, for teachers. + It offers four complementary modes for classroom use. +

    + +
    +

    The 4 modes of OpenBoard

    + +
    + +
    +

    Let's start with the first, and most important: Board Mode.

    +
    + + +
    + +
    + +
    + + - diff --git a/resources/startupHints/locales/en/10.html b/resources/startupHints/locales/en/10.html index e6c8eb617..16159f7d6 100644 --- a/resources/startupHints/locales/en/10.html +++ b/resources/startupHints/locales/en/10.html @@ -1,44 +1,95 @@ - - - - - Applications - - - - - -
    -

    Applications

    -
    -
    -

    OpenBoard comes with several applications and tools. You can access them by clicking on on the OpenBoard Library

    -

    Default applications

    -

    You'll find geometric tools, a calculator, Google Map, a QR code generator and more ! Combine them to create stimulating lessons !

    -
    -

    Create applications

    -

    Like explained in the Web Mode section, you can create applications from sites, using the internal navigator, or by copy-pasting urls to the Board.

    -

    This way, you can support your lessons with powerful online tools directly on the Board ! Here's some examples of interesting apps to create (Drag and drop them to the Board to test them !) :

    - - -

    ... and much more !

    - - You need a more powerful calculator than the one provided by OpenBoard ? Remember ! You can create your own applications ! Simply drag and drop the following link on the Board : https://ti89-simulator.com/ -
    -
    - -
    - - - + + + + + Applications + + + + + + + + + + +
    + +
    +

    Access apps

    +

    + OpenBoard offers different apps and tools. + Find them in the library by clicking on + Applications icon. +

    +
    + + +
    +

    Default apps

    +

    + You will find geometric tools, a calculator, Google Maps, a QR code generator and much more. + Combine them to create engaging educational experiences! +

    + +
    + Example of using integrated applications +
    Example of using OpenBoard built-in applications.
    +
    +
    + + +
    +

    Create your own apps

    +

    + As explained in section Web Mode, you can create an application from a site, + using the internal browser or by copying and pasting a URL onto the board. +

    +

    + This allows you to enhance your courses with powerful online tools, which can be used directly in OpenBoard. + Here are some interesting examples (drag and drop the links onto the board to test them): +

    + + + +

    ...and much more!

    + + +
    +
    + +
    + +
    + + + + + diff --git a/resources/startupHints/locales/en/11.html b/resources/startupHints/locales/en/11.html index 49a409612..ad4627a64 100644 --- a/resources/startupHints/locales/en/11.html +++ b/resources/startupHints/locales/en/11.html @@ -1,39 +1,90 @@ - - - - - Video Capture - - - - - -
    -

    Video Capture

    -
    -
    -

    Record your class session

    -

    OpenBoard gives you the ability to make an audio and video capture of your class session.

    -

    This, for example, can be helpful to share additional exercises or information via a video clip, or to record a class session in order to share it with missing students.

    - - In Board mode, the toolbars and palettes won't appear in the video clip, so don't worry about them ! Same in Web Mode, where only the current tab will appear on the capture. - -

    You can open this tool like this :

    -
    - -

    You'll see the following interface appear at the bottom right of the whiteboard :

    -
    - -

    Click on the red button to start recording.

    - - You can adjust settings by clicking on . You'll find audio settings (select a microphone, or none), video settings (resolution of the video capture) and configurable publishing options - -

    Once the capture is done, it will be automatically saved to the computer's desktop

    -
    - -
    -
    - -
    - - + + + + + Video Capture + + + + + + + + + + +
    + +
    +

    Record your course

    +

    + OpenBoard allows you to save audio and the video of your course. + Ideal for sharing additional exercises, publishing a short explanatory clip or + make a course available to absent students. +

    + + +
    + + +
    +

    Access the registration tool

    +

    Open the Snipping Tool as shown below:

    + +
    + Opening the Video Capture tool in OpenBoard +
    Quick access to the registration tool from the OpenBoard interface.
    +
    +
    + + +
    +

    Control interface

    +

    The interface appears at the bottom right of the Table:

    + +
    + Recorder interface (bottom right controls) +
    + +

    Click on the red button to start recording.

    + + +
    + + +
    +

    Finish and recover the video

    +

    + Once recording is completed, the video is automatically saved to desktop of your computer. +

    + +
    + End recording and save the video to the Desktop +
    Stop recording and video file available on Desktop.
    +
    +
    +
    + +
    + +
    + + diff --git a/resources/startupHints/locales/en/12.html b/resources/startupHints/locales/en/12.html index 00d01f9e7..53b93fece 100644 --- a/resources/startupHints/locales/en/12.html +++ b/resources/startupHints/locales/en/12.html @@ -1,37 +1,94 @@ - - - - - Favorite Documents - - - - - -
    -

    Favorite Documents

    -
    -
    -

    Add OpenBoard documents to favorites

    -

    Coming with OpenBoard 1.7, you can add OpenBoard documents to your favorites !

    -

    To do so, go to Documents Mode, select a document and click on in the Documents toolbar.

    - -
    - -

    Quickly switch between documents

    -

    Your favorite documents will appear in the OpenBoard Library, under
    - Each OpenBoard document () can then be dragged and dropped to the Board !

    -
    - - You can use the search bar at the bottom of the OpenBoard Library to quickly find a document by its name - -

    Recently open documents

    -

    Every document you open is temporary added to the favorites so you can switch between each one of them during a session, without the need for them to be explicitely marked as favorites.

    -
    - If you want to permanently add a recently open document in your favorites, you can do so via the OpenBoard Library : select it and click on -
    -
    - -
    - - + + + + + Favorite documents + + + + + + + + + + +
    + +
    +

    Add OpenBoard documents to favorites

    +

    + Introduced into the version 1.7, this functionality allows you to add your OpenBoard documents + in favorites. +

    +

    + To do this, go to Documents Mode, select a document and click + Add to Favorites icon + in the toolbar. +

    + +
    + Adding a OpenBoard document to favorites + +
    +
    + + +
    +

    Quickly move from one document to another

    +

    + Your favorite documents appear in the library OpenBoard, below + Favorites Icon. +

    +

    + Each favorite document + Document Icon OpenBoard + can be dragged and dropped directly onto the Table. +

    + +
    + Viewing Favorites in Library OpenBoard +
    + + + +
    + + +
    +

    Recently opened documents

    +

    + Each open document is temporarily added to favorites, to quickly switch from one to another + during the same session, without having to explicitly mark them. +

    + +
    + Example of recently opened documents in OpenBoard + +
    + + +
    +
    + +
    +
    + + diff --git a/resources/startupHints/locales/en/2.html b/resources/startupHints/locales/en/2.html index 4ac62998b..b3deb104f 100644 --- a/resources/startupHints/locales/en/2.html +++ b/resources/startupHints/locales/en/2.html @@ -1,47 +1,122 @@ - - - - - Board Mode - - - - - -
    -

    Board Mode

    -
    -
    -

    The Stylus palette

    -

    The Stylus palette gives you access to essential tools when working on a whiteboard.

    -
    - -

    Here you'll find the pen (), the eraser () and the marker (). OpenBoard also provides other features a classical whiterboard can't provide :

    -
      -
    • is the selector. With it, you can select objects and interact with them.
    • -
    • is the magic finger. You can move objects or interact with them without the need for them to be selected.
    • -
    • is the hand. You can move through the page with it. Very useful when combined with the zoom features !
    • -
    • give you the possibility to zoom in and out. Just select the one you want and click on the page, where you want to apply the zoom !
    • -
    • is a laser pointer, useful to point out elements on board without interacting with them.
    • -
    • is used to draw straight lines easily.
    • -
    • is a textbox tool, to write text using the keyboard instead of the pen.
    • -
    - - You can have a finer control of the zoom with the mouse ! Use Ctrl/Cmd + the mouse wheel to zoom in/out more precisely ! - -

    The Board toolbar

    -

    The Board toolbar gives you the possibility to change pen's or marker's color and size, among other things.

    -
    - -

    You'll also find the ability to change the board's background, by clicking on

    -
    - -

    You'll also find buttons to undo/redo actions, create and navigate through pages, clear the entire whiteboard, ...

    - - You can clear more precise parts of the board, by long clicking on the erase icon. Some hidden features on the erase icon too ! -
    -
    - -
    - - + + + + + Board Mode + + + + + + + + +
    + +
    +

    The stylus palette

    +

    + The stylus palette gives you access to essential tools when working on a whiteboard. +

    + +
    + OpenBoard stylus palette +
    + +

    + There you will find the pencil + Pencil, + the ereaser + Eraser + and the highlighter + Highlighter. + OpenBoard also provides features that a traditional whiteboard cannot: +

    + +
      +
    • + + Selector : select and interact with objects. +
    • +
    • + + Magic Finger : move/interact without selecting first. +
    • +
    • + + Hand : move the page (very useful combined with zoom). +
    • +
    • + + + Zoom / Zoom out : Click where to apply the effect after selecting it. +
    • +
    • + + Laser pointer : point at elements without interacting with them. +
    • +
    • + + Line : draw straight lines easily. +
    • +
    • + + Text : insert a text box for typing on the keyboard. +
    • +
    + + +
    + + +
    +

    The table bar

    +

    + The table bar allows you to change the color and/or the size pencil and highlighter, among other things. +

    + +
    + OpenBoard table bar +
    + +

    + You can also change the background of the table by clicking on + Change the background. +

    + +
    + Example of changing table background +
    + +

    + Other functions are available: undo/redo, create pages, navigate between pages, clear the table, etc. +

    + + +
    +
    + +
    + +
    + + diff --git a/resources/startupHints/locales/en/3.html b/resources/startupHints/locales/en/3.html index 16e87b4fc..889ebe1af 100644 --- a/resources/startupHints/locales/en/3.html +++ b/resources/startupHints/locales/en/3.html @@ -1,30 +1,74 @@ - - - - - Desktop Mode - - - - - -
    -

    Desktop Mode

    -
    -
    -

    Interacting with other softwares

    -

    Using Desktop mode, you can interact with your entire computer and other softwares, while keeping OpenBoard as an overlay !

    -

    Just click on the following icon :

    -
    -
    Use the selector () to interact with the desktop and other softwares.
    -

    Make annotations on top of an application or a website

    -
    You can add annotations on top of any software, using the pen () or the marker ().
    -
    - - You can access to more options for the pen, the marker and the eraser by doing a long click on them, or by clicking on the small black arrow. -
    -
    - -
    - - + + + + + Desktop Mode + + + + + + + + + +
    + +
    +

    Interact with other apps

    +

    + The Office mode allows you to interact with your operating system and software, + while retaining the OpenBoard tools on top. +

    +

    Simply click on the following icon:

    + +
    + Icon to activate Desktop Mode +
    + +

    + Use the selector + Selector + to interact with the desktop and other applications. +

    +
    + + +
    +

    Annotate apps or websites

    +

    + You can annotate any software with the pencil + Pencil + or the marker + Marker. +

    + +
    + Annotating an application in Desktop Mode +
    Example of annotation on a web page.
    +
    + + +
    +
    + +
    + +
    + + diff --git a/resources/startupHints/locales/en/4.html b/resources/startupHints/locales/en/4.html index dbdf7b61d..36cb2bd5c 100644 --- a/resources/startupHints/locales/en/4.html +++ b/resources/startupHints/locales/en/4.html @@ -1,32 +1,79 @@ - - - - - Documents Mode - - - - - -
    -

    Documents Mode

    -
    -
    -

    OpenBoard comes with a documents handler. You can access it by simply clicking on

    -
    - -

    Create folders and organize your work

    -

    You can create a folder by clicking on the "New Folder" icon. Name it, and drag and drop your documents in. The same way, you can move an entire folder in another one.

    -
    - -

    Handle pages

    -

    An OpenBoard document can contain multiple pages. You can duplicate them, move them to trash or to another document, create new ones from folders of images, ...

    -
    - - Note that available actions are updated according to what is selected -
    -
    - -
    - - + + + + + Documents Mode + + + + + + + + + +
    + +
    +

    Access the document manager

    +

    + OpenBoard offers a document manager. Click on + Documents icon + to open it. +

    +
    + Document manager view +
    +
    + + +
    +

    Create folders and organize your work

    +

    + Create a folder via + New folder, + name it, then drag and drop your documents inside. + Likewise, you can move an entire folder to another. +

    +
    + Creating and organizing files +
    Example of creating and organizing folders.
    +
    +
    + + +
    +

    Page management

    +

    + A OpenBoard document can contain several pages. From Documents Mode, you can: +

    +
      +
    • Duplicate pages
    • +
    • Send them to the trash
    • +
    • Copy them to another document
    • +
    • Create new pages from an images folder
    • +
    +
    + Examples of actions on pages +
    Example of document handling.
    +
    + + +
    +
    + +
    +
    + + diff --git a/resources/startupHints/locales/en/5.html b/resources/startupHints/locales/en/5.html index a412ec34b..c4b08ad5a 100644 --- a/resources/startupHints/locales/en/5.html +++ b/resources/startupHints/locales/en/5.html @@ -1,39 +1,105 @@ - - - - - Documents Mode - - - - - -
    -

    Documents Mode

    -
    -
    -

    You'll also find in this mode two very important features : Import and Export.

    -
    - -

    Import

    -

    You can import PDF files (.pdf), images (.png, .jpg), OpenBoard documents (.ubz) or OpenBoard folders (.ubx), by clicking on

    -
    -

    Note that you can also import an OpenBoard document (.ubz) by double-clicking on it. It will launch OpenBoard if not already launched, and import it (or ask for replacement if it already exists).

    - You can also import multiple elements at a time, without having to click on again and again. -

    Export

    -

    You can export an OpenBoard document to PDF or UBZ, and an OpenBoard folder to UBX. Thus, you can, for example : -

      -
    • Save the work done during a class session and share it with a missing student
    • -
    • Share it with other teachers, or store it on an USB device and open it on another computer
    • -
    • Import a student's homework in the PDF format, annotate it, and export the annotated PDF to send it back to the student
    • -
    -

    -
    - - Export all your documents by selecting the "My documents" folder before clicking on the export button, to export it as a UBX file, and then import it on another computer. It's also a good way to make a backup of your work ! -
    -
    - -
    - - + + + + + + Import and export + + + + + + + + + + +
    + +
    +

    Import & Export

    +

    + In this mode, you have two essential features: import And export. +

    +
    + Import and export area in Documents Mode + +
    +
    + + +
    +

    Import content

    +

    + You can import files PDF (.pdf), + of the pictures (.png, .jpg), + of the documents OpenBoard (.ubz) or + OpenBoard folders (.ubx) by clicking on + Import icon. + +

    + +
    + Example of importing files into OpenBoard +
    Example of importing files into OpenBoard.
    +
    + +

    + You can also import a OpenBoard document (.ubz) in + double-click on it from your system. OpenBoard will launch if necessary and + will offer to import (or replace if it already exists). +

    + + +
    + + +
    +

    Export your documents

    +

    + You can export a document OpenBoard in format PDF Or UBZ, + and a folder OpenBoard in format UBX. For example : +

    +
      +
    • Record the work done during a lesson and share it with an absent student.
    • +
    • Share with other teachers, or save to a USB key to import elsewhere.
    • +
    • Import a student's work as a PDF, annotate it, then export the annotated PDF for resend.
    • +
    + +
    + Examples of exporting documents and folders OpenBoard +
    Example of exporting files from OpenBoard.
    +
    + + +
    +
    + +
    +
    + + + diff --git a/resources/startupHints/locales/en/6.html b/resources/startupHints/locales/en/6.html index 615001a33..40790c69c 100644 --- a/resources/startupHints/locales/en/6.html +++ b/resources/startupHints/locales/en/6.html @@ -1,38 +1,96 @@ - - - - - Web Mode - - - - - -
    -

    Web Mode

    -
    -
    - The Web Mode is an internal navigator that adds some cool features to a more standard one. -

    Use the internal navigator

    -

    Using the internal navigator, you'll be able to browse the Internet without the need for reducing or exiting OpenBoard, with some additional features giving the ability to capture part of the screen or create applications from sites, and add them to the whiteboard !

    - -
    - -

    Create an application

    -

    You can create an application from any site, in order to add it to the board and interact with it like with default applications.

    -

    To do so, go to the website you want to capture, click on Capture Web Content and confirm the creation by clicking on "Create an application".

    -
    - -

    When an application is created, it is automatically saved in the OpenBoard library, so you can use it in any document. You'll find them on the applications folder, under a dedicated one called "Web".

    - -
    - -

    Use an external navigator

    -

    You can also choose to use an external navigator, by checking the corresponding option in the preferences. Clicking on the Web icon will then launch your favorite navigator in Desktop Mode.

    - Using an external navigator, you will still be able to create applications from a site, by copying the URL in the address bar of your navigator, coming back to Board, and pasting it ! -
    -
    - -
    - - + + + + + Web Mode + + + + + + + + + +
    + +
    +

    The integrated browser

    +

    + THE Web Mode is a browser internal to OpenBoard which adds to a browser + classic, very practical features for the classroom. +

    +

    + It allows you to browse the Internet without leaving OpenBoard, to capture content (partial or + full screen) and even create apps from websites for + reuse in your documents. +

    + +
    + Example of using the built-in browser of OpenBoard +
    +
    + + +
    +

    Create an application from a site

    +

    + From any site, you can generate a application to add it + on the board and interact with it as with the default applications. +

    +

    + Go to the desired site, click on + Add to Library icon + then confirm by selecting Create an app. +

    + +
    + Creating a web application from the built-in browser +
    Creation of an application from web mode.
    +
    + +

    + Once created, the application is automatically saved in the + library OpenBoard. You will find it under Applications > Web + and can reuse it in any document. +

    + +
    + Application saved in library, web folder +
    Find an application created in your library.
    +
    +
    + + +
    + +

    + You can choose to open the links in a external browser by checking + the corresponding option in the preferences. Click on + Web Mode icon + will then launch your favorite browser as Desktop Mode. +

    + + +
    + +
    + +
    + +
    + + + diff --git a/resources/startupHints/locales/en/7.html b/resources/startupHints/locales/en/7.html index bdf8e0478..dca846742 100644 --- a/resources/startupHints/locales/en/7.html +++ b/resources/startupHints/locales/en/7.html @@ -1,44 +1,102 @@ - - - - - Screen Capture - - - - - -
    -

    Screen Capture

    -
    -
    - One of the simplest but really useful features of OpenBoard is screen capture. With it, you can create a snapshot of any particular state of the Board, during your class session. -

    In Board Mode

    -

    Search forthe screen capture icon in the Stylus palette, click on it and select the area to capture. Then, you'll have three options :

    -
      -
    • Add to current page
    • -
    • Add in a new page
    • -
    • Add to library(adds the capture to the Pictures folder, so you can reuse it at any time)
    • -
    - -

    Here's an example of the whole process :

    -
    - - You don't have to worry about the Stylus palette, the Board's thumbnails view or the OpenBoard Library, they won't be captured by your lasso ! - -

    In Desktop Mode

    -

    You can do the same thing in Desktop Mode, to capture your work on other softwares. Go to Desktop Mode by clicking on

    -

    You'll find two icons : to capture the whole screen, and to capture just a part of it. Like in Board Mode, you'll have to choose where to drop the capture you made

    - -

    Here's an illustration of the whole process

    -
    - - -

    In Web Mode

    -

    Again, the same process is available in Web Mode, where you will be able to capture just a part of the screen, or capture the current tab with

    -
    -
    - -
    - - + + + + + Screenshot + + + + + + + + + +
    +
    +

    A simple and very useful feature

    +

    + Screenshot allows you to create a instant the state of the board (or your screen) + during your course, to archive, share or reuse content. +

    +
    + + +
    +

    In Board Mode

    +

    + In the Pen palette, click + Capture Area Icon (Board Mode), + then select the area to capture. You can then choose: +

    +
      +
    • Add to current page
    • +
    • Add to new page
    • +
    • + Add to library — the capture is saved in the folder Pictures + for later reuse. +
    • +
    + +
    + Example of capturing an area in Board Mode +
    Example of embedding a screenshot in OpenBoard.
    +
    + + +
    + + +
    +

    In Desktop Mode

    +

    + Switch to Desktop Mode via + Desktop Mode icon + to capture other software. +

    +

    Two options are available:

    +
      +
    • + Screen Capture icon + Full screen capture +
    • +
    • + Capture area icon + Capturing an area +
    • +
    +

    As in Board Mode, choose where to add the capture.

    + +
    + Example of capture in Desktop Mode +
    Full or partial screenshot in Desktop Mode.
    +
    +
    + + +
    +

    In Web Mode

    +

    + In Web Mode you can capture an area of the screen or + capture active tab from the browser with + Capture tab icon. +

    +
    +
    + +
    +
    + + + diff --git a/resources/startupHints/locales/en/8.html b/resources/startupHints/locales/en/8.html index 358f17038..d4a3c2a78 100644 --- a/resources/startupHints/locales/en/8.html +++ b/resources/startupHints/locales/en/8.html @@ -1,36 +1,102 @@ - - - - - Images - - - - - -
    -

    Images

    -
    -
    - In OpenBoard, you can have access to images in several ways. -

    Organize your Pictures folder

    -

    Screen captures added to the Library are saved to the Pictures folder :
    You can create folders using at the bottom of the Library. Then, drag and drop your images in the different folders you created.

    -
    - - No matter where you are in the OpenBoard Library, images you add will be automatically placed inside the Pictures folder, to keep things organized. - -

    Add images from your computer

    -

    You can also add images directly from your computer, like illustrated below :

    -
    - -

    Add images from the search engines

    -

    Last but not least, you can search and add to your board royalty free images, using the search engines located in the Web Search folder :

    -
    - - Instead of using drag and drop, just click on one of the images returned by your search, to see details about the image, and find an option to add it to the Library, without adding it to your current document. Useful when preparing a lesson ! -
    -
    - -
    - - + + + + + Pictures + + + + + + + + + +
    +
    +

    Access and organize your images

    +

    + In OpenBoard you can access and add images in different ways from your library. The latter is located on the right of the screen. This is also where you can store sounds, videos and find different applications. +

    +
    + Import and export area in Documents Mode +
    Image of the library located on the right in the software.
    +
    +
    + + +
    +

    Organize your Pictures folder

    +

    + Screenshots added to the library are grouped in the folder + Icon Images. + You can create subfolders via + New folder icon + (at the bottom of the library), then drag and drop your images into the different folders created. +

    + +
    + Organizing the Pictures folder with subfolders +
    Example of organizing and classifying images in the library.
    +
    + + +
    + + +
    +

    Add images from your computer

    +

    + You can import your own image files directly into OpenBoard, as shown below: +

    + +
    + Adding images from the computer +
    Importing a local image into OpenBoard.
    +
    +
    + + +
    +

    Add images from the web

    +

    + You can also search for royalty free images via integrated search engines, + located in the folder Web Search + Icon Images. +

    + +
    + Adding images from an integrated search engine +
    Example of finding and adding images from the web.
    +
    + + +
    +
    + +
    + +
    + + + diff --git a/resources/startupHints/locales/en/9.html b/resources/startupHints/locales/en/9.html index f9fee4aee..c0b5935a4 100644 --- a/resources/startupHints/locales/en/9.html +++ b/resources/startupHints/locales/en/9.html @@ -1,43 +1,99 @@ - - - - - Interactivities - - - - - -
    -

    Interactivities

    -
    -
    - For younger students, you'll find a dedicated set of customizable applications. -

    Interactive exercices of OpenBoard

    -

    Using these interactivities, you can create different kinds of custom exercices. These can be about categorization, calculation, memorization, and so on.

    -

    Interactivites are located in the OpenBoard Library : click on to find them.

    - -

    Example

    -

    As an example, we'll create an exercise using the "Categorize pictures" interactivity :

    - Try it ! Move this dialog on a side and reproduce the example on the Board ! -

    First, drag and drop the interactivity on your board, and click on "Edit"

    -

    Then, change the name of the categories to your needs :

    -
    - - You can add or delete categories using the + and - icons - -

    Then, add the images you want in the corresponding category.

    -
    - -

    Finally, Click on "Display" to show the result and do the exercise.

    -
    - - You can restart the exercise using the "Reload" icon. - - -
    -
    - -
    - - + + + + + Interactivities + + + + + + + + + + +
    + +
    +

    Interactive exercises, easy to personalize

    +

    + For the youngest students (and not only), OpenBoard provides a set + ofinteractivities customizable: categorization, mental calculation, memorization, etc. +

    +

    + They are accessible in the library : click on + Interactivities Icon. +

    +
    + + +
    +

    Example: “Categorize Pictures”

    +

    + Let’s create an exercise with interactivity + categorize pictures (categorize images): + Thumbnail Categorize Pictures. +

    + + + +

    1) Place interactivity and open the editor

    +

    + Drag and drop the interactivity onto the Board, then click To modify. +

    + +

    2) Name the categories

    +

    + Rename the categories as needed. You can also + add Or DELETE categories with “+” and “−” icons. +

    + +
    + Editing interactivity categories +
    Editing categories: addition, deletion, renaming.
    +
    + +

    3) Add the images

    +

    + Add images corresponding to each category (drag and drop, selection from library, etc.). +

    + +
    + Adding images to categories +
    Associate images with defined categories.
    +
    + +

    4) Show and do the exercise

    +

    + Click on Display to start the activity in student mode. +

    + +
    + Performing the Categorize Pictures exercise +
    Execution of the exercise: move the images into the correct category.
    +
    + + +
    +
    + +
    +
    + + diff --git a/resources/startupHints/locales/en/css/style.css b/resources/startupHints/locales/en/css/style.css deleted file mode 100644 index de0ecb4d5..000000000 --- a/resources/startupHints/locales/en/css/style.css +++ /dev/null @@ -1,36 +0,0 @@ -*{ - font-family: Arial, Helvetica, sans-serif; -} - -.title -{ - width:100%; - color: #6682b5; - text-align: center; -} - -.file-extension -{ - background: lightgrey; -} - -.image -{ - text-align:center; -} - -.image img -{ - max-width: 600px; -} - -.icon -{ - vertical-align: middle; -} - -.tip -{ - font-style: italic; - font-size: 0.8rem; -} diff --git a/resources/startupHints/locales/en/error.html b/resources/startupHints/locales/en/error.html index f998df943..2e4c100ee 100644 --- a/resources/startupHints/locales/en/error.html +++ b/resources/startupHints/locales/en/error.html @@ -1,15 +1,20 @@ - - - - - erreur - - - - - -

    Quelque chose s'est mal passé ... -Vérifier l'existence de vos conseils dans le dossier -/Resources/startupHints/ …

    - - + + + + + Error + + + + + +
    +

    Loading error

    +

    + Something went wrong. + Check the presence of tips pages in the folder + /Resources/startupHints/. +

    +
    + + diff --git a/resources/startupHints/locales/fr/1.html b/resources/startupHints/locales/fr/1.html index a497619ff..6bf6bebde 100644 --- a/resources/startupHints/locales/fr/1.html +++ b/resources/startupHints/locales/fr/1.html @@ -1,26 +1,95 @@ - - - - - Trucs et astuces - - - - - -
    -

    Bienvenue sur OpenBoard

    -
    -
    -
    -
    -

    Voici quelques trucs et astuces pour vous aider à vous familiariser avec OpenBoard.

    -

    OpenBoard est un tableau blanc interactif pensé par des enseignants, pour des enseignants. Il est composé de 4 modes : le mode Tableau, le mode Bureau, le mode Documents et le mode Web.

    -

    Commençons par le premier, et le plus important : le mode Tableau.

    -
    -
    - Vous pouvez réouvrir à tout moment cette fenêtre via le bouton "Trucs et astuces" dans le menu OpenBoard (the OpenBoard menu) -
    - - - + + + + + Bienvenue + + + + + + + +
    + + +

    + Voici quelques trucs et astuces pour vous aider à vous familiariser avec OpenBoard. +

    + +

    + OpenBoard est un tableau blanc interactif pensé par des enseignants, pour des enseignants. + Il propose quatre modes complémentaires pour couvrir vos usages en classe. +

    + +
    +

    Les 4 modes d’OpenBoard

    + +
    + +
    +

    Commençons par le premier, et le plus important : le mode Tableau.

    +
    + + +
    + +
    + +
    + + + + diff --git a/resources/startupHints/locales/fr/10.html b/resources/startupHints/locales/fr/10.html index 926cd2566..203e6fc84 100644 --- a/resources/startupHints/locales/fr/10.html +++ b/resources/startupHints/locales/fr/10.html @@ -1,44 +1,95 @@ - + - - Applications - - + + Applications + + + + -
    -

    Applications

    -
    -
    -

    OpenBoard propose différentes applications et outils. Vous pouvez y accéder en cliquant sur dans la bibliothèque OpenBoard

    -

    Applications par défaut

    -

    Vous y trouverez des outils géométriques, une calculatrice, Google Maps, un générateur de code QR et bien plus ! Combinez-les pour créer des expériences pédagogiques stimulantes !

    -
    -

    Créez des applications

    -

    Comme expliqué dans la section Mode Web, vous pouvez créer une application depuis un site, en utilisant le navigateur interne, ou en copiant-collant une URL sur le tableau.

    -

    Grâce à cela, vous pouvez agrémenter vos cours avec des outils en ligne puissants, utilisables directement sur le tableau ! Voici quelques illustrations d'applications intéressantes (Glissez-déposez les sur le tableau pour les tester !) :

    - - -

    ... et bien plus !

    - - Vous avez besoin d'une calculatrice plus puissante que celle fournie par défaut dans OpenBoard ? Souvenez-vous ! Vous pouvez créer vos propres applications ! Glissez-déposez le lien suivant sur votre tableau : https://ti89-simulator.com/ -
    -
    - -
    - + + + +
    + +
    +

    Accéder aux applications

    +

    + OpenBoard propose différentes applications et outils. + Retrouvez-les dans la bibliothèque en cliquant sur + Icône Applications. +

    +
    + + +
    +

    Applications par défaut

    +

    + Vous y trouverez des outils géométriques, une calculatrice, Google Maps, un générateur de QR codes et bien plus. + Combinez-les pour créer des expériences pédagogiques stimulantes ! +

    + +
    + Exemple d’utilisation des applications intégrées +
    Exemple d’utilisation des applications intégrées d’OpenBoard.
    +
    +
    + + +
    +

    Créez vos propres applications

    +

    + Comme expliqué dans la section Mode Web, vous pouvez créer une application à partir d’un site, + en utilisant le navigateur interne ou en copiant-collant une URL sur le tableau. +

    +

    + Cela vous permet d’agrémenter vos cours avec des outils en ligne puissants, utilisables directement dans OpenBoard. + Voici quelques exemples intéressants (glissez-déposez les liens sur le tableau pour les tester) : +

    + + + +

    ... et bien plus encore !

    + + +
    +
    + +
    + +
    + + + diff --git a/resources/startupHints/locales/fr/11.html b/resources/startupHints/locales/fr/11.html index 6aa7a7f1d..b94572e5d 100644 --- a/resources/startupHints/locales/fr/11.html +++ b/resources/startupHints/locales/fr/11.html @@ -1,39 +1,90 @@ - + - - Capture Vidéo - - + + Capture Vidéo + + + + -
    -

    Capture Vidéo

    -
    -
    -

    Enregistrez votre cours

    -

    OpenBoard vous offre la possibilité de faire une capture audio et vidéo de votre cours.

    -

    Cela peut, par exemple, être utile pour partager des exercices additionnels ou une information via un clip vidéo, ou encore enregistrer un cours afin de le partager avec des élèves absents.

    - - En mode tableau, les barres d'outils et palettes n'apparaîtront pas dans le clip vidéo, donc vous n'avez pas à vous en soucier ! Idem avec le navigateur interne, où seul l'onglet actif sera capturé. - -

    Vous pouvez accéder à cet outil comme ceci :

    -
    - -

    Vous verrez apparaître l'interface suivante en bas à droite du tableau :

    -
    - -

    Cliquez sur le bouton rouge pour commencer l'enregistrement.

    - - Vous pouvez ajuster les paramètres en cliquant sur . Vous y trouverez des paramètres audio (sélectionner un micro, ou aucun), des paramètres vidéo (résolution de la capture vidéo) et des options de publication configurables - -

    Une fois l'enregistrement terminé, la capture sera sauvegardée et disponible sur le bureau de votre ordinateur

    -
    - -
    -
    - -
    + + + +
    + +
    +

    Enregistrez votre cours

    +

    + OpenBoard permet d’enregistrer l’audio et la vidéo de votre cours. + Idéal pour partager des exercices additionnels, publier un court clip explicatif ou + mettre un cours à disposition des élèves absents. +

    + + +
    + + +
    +

    Accéder à l’outil d’enregistrement

    +

    Ouvrez l’outil de capture comme illustré ci-dessous :

    + +
    + Ouverture de l’outil Capture Vidéo dans OpenBoard +
    Accès rapide à l’outil d’enregistrement depuis l’interface d’OpenBoard.
    +
    +
    + + +
    +

    Interface de contrôle

    +

    L’interface apparaît en bas à droite du Tableau :

    + +
    + Interface de l’enregistreur (contrôles en bas à droite) +
    + +

    Cliquez sur le bouton rouge pour lancer l’enregistrement.

    + + +
    + + +
    +

    Terminer et récupérer la vidéo

    +

    + Une fois l’enregistrement terminé, la vidéo est automatiquement enregistrée sur le Bureau de votre ordinateur. +

    + +
    + Fin d’enregistrement et sauvegarde de la vidéo sur le Bureau +
    Arrêt de l’enregistrement et fichier vidéo disponible sur le Bureau.
    +
    +
    +
    + +
    + +
    diff --git a/resources/startupHints/locales/fr/12.html b/resources/startupHints/locales/fr/12.html index ed111ba77..8a536de55 100644 --- a/resources/startupHints/locales/fr/12.html +++ b/resources/startupHints/locales/fr/12.html @@ -1,37 +1,94 @@ - + - - Documents favoris - - + + Documents favoris + + + + -
    -

    Documents favoris

    -
    -
    -

    Ajouter des documents OpenBoard aux favoris

    -

    Nouvelle fonctionnalité de la version 1.7, vous pouvez ajouter des documents OpenBoard à vos favoris !

    -

    Pour ce faire, allez en mode Documents, sélectionnez un document et cliquez sur dans la la barre d'outils du mode Documents.

    - -
    - -

    Passez rapidement d'un document à l'autre

    -

    Vos documents favoris apparaîtront dans la bibliothèque OpenBoard, sous
    - Chacun de ces documents OpenBoard () peut ensuite être glissé-déposé sur le tableau !

    -
    - - Vous pouvez utiliser la barre de recherche située en bas de la bibliothèque OpenBoard pour retrouver un document à partir de son nom - -

    Documents récemment ouverts

    -

    Chaque document ouvert sera ajouté temporairement aux favoris afin que vous puissiez passer rapidement de l'un à l'autre durant une même session, sans qu'il soit nécessaire de les marquer explicitement comme favoris.

    -
    - Si vous voulez ajouter de façon permanente un document récemment ouvert aux favoris, vous pouvez le faire via le dossier Favoris de la bibliothèque OpenBoard : sélectionnez-le et cliquez sur -
    -
    - -
    + + + +
    + +
    +

    Ajouter des documents OpenBoard aux favoris

    +

    + Introduite dans la version 1.7, cette fonctionnalité permet d’ajouter vos documents OpenBoard + en favoris. +

    +

    + Pour cela, passez en Mode Documents, sélectionnez un document et cliquez sur + Icône Ajouter aux favoris + dans la barre d’outils. +

    + +
    + Ajout d’un document OpenBoard aux favoris + +
    +
    + + +
    +

    Passer rapidement d’un document à l’autre

    +

    + Vos documents favoris apparaissent dans la bibliothèque OpenBoard, sous + Icône Favoris. +

    +

    + Chaque document favori + Icône Document OpenBoard + peut être glissé-déposé directement sur le Tableau. +

    + +
    + Affichage des favoris dans la bibliothèque OpenBoard +
    + + + +
    + + +
    +

    Documents récemment ouverts

    +

    + Chaque document ouvert est ajouté temporairement aux favoris, pour passer rapidement de l’un à l’autre + au cours d’une même session, sans devoir les marquer explicitement. +

    + +
    + Exemple de documents récemment ouverts dans OpenBoard + +
    + + +
    +
    + +
    +
    diff --git a/resources/startupHints/locales/fr/2.html b/resources/startupHints/locales/fr/2.html index 81f24e5c0..2e937325a 100644 --- a/resources/startupHints/locales/fr/2.html +++ b/resources/startupHints/locales/fr/2.html @@ -1,46 +1,122 @@ - + - - Mode Tableau - - + + Mode Tableau + + + -
    -

    Mode Tableau

    -
    -
    -

    La palette des stylets

    -

    La palette des stylets vous donne accès à des outils essentiels quand on travaille sur un tableau blanc.

    -
    - -

    Vous y trouverez le crayon (), la gomme () et le surligneur (). OpenBoard fournit également des fonctionnalités qu'un tableau blanc classique ne peut pas fournir :

    -
      -
    • est le sélecteur. Avec, vous pouvez sélectionner et interagir avec des objets.
    • -
    • est le doigt magique. Vous pouvez déplacer des objets ou interagir avec eux sans qu'il soit nécessaire de les sélectionner.
    • -
    • est la main. Vous pouvez déplacer la page avec. Très utile quand combinée avec les fonctionnalités de zoom !
    • -
    • vous donnent la possibilité de zoomer et dézoomer. Sélectionnez simplement l'effet désiré, et cliquer à l'endroit où vous souhaitez l'appliquer !
    • -
    • est un pointeur laser, utile pour pointer des éléments sur le tableau sans interagir avec ces derniers.
    • -
    • est utilisé pour tracer des droites facilement.
    • -
    • est une boîte de texte, permettant d'écrire au clavier plutôt qu'au crayon.
    • -
    - - Vous pouvez avoir un contrôle plus fin du zoom avec la souris ! Utilisez Ctrl/Cmd + la molette de la souris afin de zoomer/dézoomer avec plus de précision ! -

    La barre du tableau

    -

    La barre du tableau vous permet de changer la couleur et/ou la taille du crayon et marqueur, entre autres choses.

    -
    - -

    Vous y trouverez également la possibilité de changer le fond du tableau en cliquant sur

    -
    - -

    Vous y trouverez aussi des fonctionnalités comme annuler/rétablir, créer et naviguer de pages en pages, effacer le tableau, ...

    - - Vous pouvez effacer entièrement des éléments plus spécifiques du tableau, en faisant un clic long sur the erase icon. Quelques fonctionnalités supplémentaires accessibles également pour the erase icon ! -
    -
    - -
    + + +
    + +
    +

    La palette des stylets

    +

    + La palette des stylets vous donne accès à des outils essentiels quand on travaille sur un tableau blanc. +

    + +
    + Palette des stylets d’OpenBoard +
    + +

    + Vous y trouverez le crayon + Crayon, + la gomme + Gomme + et le surligneur + Surligneur. + OpenBoard fournit également des fonctionnalités qu’un tableau blanc classique ne peut pas proposer : +

    + +
      +
    • + + Sélecteur : sélectionnez et interagissez avec les objets. +
    • +
    • + + Doigt magique : déplacez/interagissez sans sélectionner au préalable. +
    • +
    • + + Main : déplacez la page (très utile combiné au zoom). +
    • +
    • + + + Zoom / Dézoom : cliquez où appliquer l’effet après l’avoir sélectionné. +
    • +
    • + + Pointeur laser : pointez des éléments sans interagir avec eux. +
    • +
    • + + Ligne : tracez des droites facilement. +
    • +
    • + + Texte : insérez une zone de texte pour taper au clavier. +
    • +
    + + +
    + + +
    +

    La barre du tableau

    +

    + La barre du tableau vous permet de changer la couleur et/ou la taille du crayon et du surligneur, entre autres choses. +

    + +
    + Barre du tableau d’OpenBoard +
    + +

    + Vous pouvez aussi changer le fond du tableau en cliquant sur + Changer le fond. +

    + +
    + Exemple de changement de fond du tableau +
    + +

    + D’autres fonctions sont disponibles : annuler/rétablir, créer des pages, naviguer entre les pages, effacer le tableau, etc. +

    + + +
    +
    + +
    + +
    diff --git a/resources/startupHints/locales/fr/3.html b/resources/startupHints/locales/fr/3.html index 0921ed8e8..256a75d69 100644 --- a/resources/startupHints/locales/fr/3.html +++ b/resources/startupHints/locales/fr/3.html @@ -1,30 +1,74 @@ - + - - Mode Bureau - - + + Mode Bureau + + + + -
    -

    Mode Bureau

    -
    -
    -

    Interagir avec d'autres applications

    -

    En utilisant le mode Bureau, vous pouvez interagir avec votre système d'exploitation et d'autres logiciels, tout en conservant OpenBoard par dessus !

    -

    Cliquez simplement sur l'icône suivante :

    -
    -
    Utilisez le sélecteur () pour interagir avec le bureau et les autres applications.
    -

    Annoter des applications ou des sites web

    -
    Vous pouvez annoter n'importe quel logiciel, en utilisant le crayon () ou le marqueur ().
    -
    - - Vous pouvez accéder à plus d'options pour the pen, the marker et the eraser en faisant en clic long sur eux, ou en cliquant sur la petite flèche noire. -
    -
    - -
    + + +
    + +
    +

    Interagir avec d’autres applications

    +

    + Le mode Bureau vous permet d’interagir avec votre système d’exploitation et vos logiciels, + tout en conservant les outils d’OpenBoard par-dessus. +

    +

    Cliquez simplement sur l’icône suivante :

    + +
    + Icône pour activer le Mode Bureau +
    + +

    + Utilisez le sélecteur + Sélecteur + pour interagir avec le bureau et les autres applications. +

    +
    + + +
    +

    Annoter des applications ou des sites web

    +

    + Vous pouvez annoter n’importe quel logiciel avec le crayon + Crayon + ou le marqueur + Marqueur. +

    + +
    + Annotation d’une application en Mode Bureau +
    Exemple d'annotation sur une page internet.
    +
    + + +
    +
    + +
    + +
    diff --git a/resources/startupHints/locales/fr/4.html b/resources/startupHints/locales/fr/4.html index cc87807d7..2dc8ec7fe 100644 --- a/resources/startupHints/locales/fr/4.html +++ b/resources/startupHints/locales/fr/4.html @@ -1,32 +1,79 @@ - + - - Mode Documents - - + + Mode Documents + + + + -
    -

    Mode Documents

    -
    -
    -

    OpenBoard propose un gestionnaire de documents. Vous pouvez y accéder en cliquant simplement sur

    -
    - -

    Créez des dossiers et organisez votre travail

    -

    Vous pouvez créer un dossier en cliquant sur . Nommez-le, et glissez-déposez y vos documents. De la même façon, vous pouvez déplacer un dossier entier à l'intérieur d'un autre.

    -
    + + +
    + +
    +

    Accéder au gestionnaire de documents

    +

    + OpenBoard propose un gestionnaire de documents. Cliquez sur + Icône Documents + pour l’ouvrir. +

    +
    + Vue du gestionnaire de documents +
    +
    + + +
    +

    Créez des dossiers et organisez votre travail

    +

    + Créez un dossier via + Nouveau dossier, + nommez-le, puis glissez-déposez vos documents à l’intérieur. + De la même façon, vous pouvez déplacer un dossier entier dans un autre. +

    +
    + Création et organisation de dossiers +
    Exemple de création et d'organisation de dossiers.
    +
    +
    + + +
    +

    Gestion des pages

    +

    + Un document OpenBoard peut contenir plusieurs pages. Depuis le Mode Documents, vous pouvez : +

    +
      +
    • Dupliquer des pages
    • +
    • Les envoyer à la corbeille
    • +
    • Les copier vers un autre document
    • +
    • Créer de nouvelles pages à partir d’un dossier d’images
    • +
    +
    + Exemples d’actions sur les pages +
    Exemple de manipulation de documents.
    +
    -

    Gestion des pages

    -

    Un document OpenBoard peut contenir plusieurs pages. Via le mode Documents, vous pouvez les dupliquer, les mettre à la corbeille ou les copier vers un autre document, en créer de nouvelles à partir d'un dossier d'images, ...

    -
    - - À noter que les actions disponibles sont mises à jour en fonction de ce qui est sélectionné. -
    -
    + + +
    - +
    +
    diff --git a/resources/startupHints/locales/fr/5.html b/resources/startupHints/locales/fr/5.html index d379cad55..93eb11579 100644 --- a/resources/startupHints/locales/fr/5.html +++ b/resources/startupHints/locales/fr/5.html @@ -1,39 +1,105 @@ - + + - - Mode Documents - - + + Importer et exporter + + + + -
    -

    Mode Documents

    -
    -
    -

    Vous trouverez également dans ce mode deux fonctionnalités très importantes : l'import et l'export.

    -
    - -

    Import

    -

    Vous pouvez importer des fichiers PDF (.pdf), des images (.png, .jpg), des documents OpenBoard (.ubz) ou des dossiers OpenBoard (.ubx), en cliquant sur

    -
    -

    À noter que vous pouvez aussi importer un document OpenBoard (.ubz) en double-cliquant dessus. Cela lancera OpenBoard s'il n'est pas déjà lancé, et l'importera (ou proposera un remplacement du fichier si déjà existant).

    - Vous pouvez également importer plusieurs éléments à la fois, sans qu'il soit nécessaire de cliquer sur encore et encore. -

    Export

    -

    Vous pouvez exporter un document OpenBoard au format PDF ou UBZ, et un dossier OpenBoard au format UBX. Ainsi, vous pouvez, par exemple : -

      -
    • Enregistrer le travail fait durant un cours, et le partager avec un élève absent.
    • -
    • Le partager avec d'autres enseignants, ou le sauvegarder sur une clé USB pour l'importer sur un autre ordinateur.
    • -
    • Importer le travail d'un élève au format PDF, l'annoter, et exporter le document annoté au format PDF pour le renvoyer à l'élève
    • -
    -

    -
    - - Exporter tous vos documents en cliquant sur le dossier racine "Mes Documents", avant de cliquer sur , pour les exporter au format UBX. Vous pourrez alors les importer sur un autre ordinateur. C'est aussi un bon moyen d'effectuer une archive ou une sauvegarde de tout votre travail ! -
    -
    - -
    + + + +
    + +
    +

    Import & Export

    +

    + Dans ce mode, vous disposez de deux fonctionnalités essentielles : l’import et l’export. +

    +
    + Zone d’import et d’export dans le Mode Documents + +
    +
    + + +
    +

    Importer des contenus

    +

    + Vous pouvez importer des fichiers PDF (.pdf), + des images (.png, .jpg), + des documents OpenBoard (.ubz) ou des + dossiers OpenBoard (.ubx) en cliquant sur + Icône Importer. + +

    + +
    + Exemple d’import de fichiers dans OpenBoard +
    Exemple d'importation de fichiers dans OpenBoard.
    +
    + +

    + Vous pouvez aussi importer un document OpenBoard (.ubz) en + double-cliquant dessus depuis votre système. OpenBoard se lancera si nécessaire et + vous proposera d’importer (ou de remplacer s’il existe déjà). +

    + + +
    + + +
    +

    Exporter vos documents

    +

    + Vous pouvez exporter un document OpenBoard au format PDF ou UBZ, + et un dossier OpenBoard au format UBX. Par exemple : +

    +
      +
    • Enregistrer le travail fait durant un cours et le partager à un élève absent.
    • +
    • Partager à d’autres enseignants, ou sauvegarder sur une clé USB pour l’importer ailleurs.
    • +
    • Importer le travail d’un élève en PDF, l’annoter, puis exporter le PDF annoté pour le renvoyer.
    • +
    + +
    + Exemples d’export de documents et dossiers OpenBoard +
    Exemple d'exportation de fichiers depuis OpenBoard.
    +
    + + +
    +
    + +
    +
    + diff --git a/resources/startupHints/locales/fr/6.html b/resources/startupHints/locales/fr/6.html index 4a444dec9..21a9cd726 100644 --- a/resources/startupHints/locales/fr/6.html +++ b/resources/startupHints/locales/fr/6.html @@ -1,38 +1,96 @@ - + - - Mode Web - - + + Mode Web + + + + -
    -

    Mode Web

    -
    -
    - Le mode Web est un navigateur interne qui ajoute à un navigateur plus traditionnel des fonctionnalités très intéressantes. -

    Utilisation du navigateur interne

    -

    En utilisant le navigateur interne, vous aurez la possibilité de parcourir Internet sans le besoin de réduire ou quitter OpenBoard, avec des fonctionnalités supplémentaires vous permettant d'effectuer une capture partielle ou entière de l'écran, ou encore créer des applications à partir de sites, et les ajouter au tableau !

    - -
    - -

    Créer une application

    -

    Vous pouvez créer une application à partir de n'importe quel site, afin de l'ajouter au tableau et interagir avec, comme avec les applications par défaut.

    -

    Pour ce faire, allez sur le site web que vous souhaitez capturer, cliquez sur Capture Web Content et confirmez sa création en cliquant sur "Créer une application".

    -
    - -

    Quand une application est créée, elle est automatiquement sauvegardée dans la bibliothèque OpenBoard, afin que vous puissiez la réutiliser dans n'importe quel document. Vous les retrouverez sous le dossier Applications, dans un sous-dossier appelé "Web"

    - -
    - -

    Utilisation du navigateur externe

    -

    Vous pouvez également choisir d'utiliser un navigateur externe, en cochant l'option correspondante dans les préférences. Cliquer sur lancera alors votre navigateur préféré en mode Bureau.

    - En utilisant un navigateur externe, vous pourrez toujours créer une application depuis un site, en copiant l'URL située dans la barre d'adresse de votre navigateur, puis en la collant une fois de retour dans le mode Tableau ! -
    -
    - -
    + + +
    + +
    +

    Le navigateur intégré

    +

    + Le Mode Web est un navigateur interne à OpenBoard qui ajoute à un navigateur + classique des fonctionnalités très pratiques pour la classe. +

    +

    + Il permet de parcourir Internet sans quitter OpenBoard, de capturer des contenus (partiels ou + plein écran) et même de créer des applications à partir de sites web pour les + réutiliser dans vos documents. +

    + +
    + Exemple d’utilisation du navigateur intégré d’OpenBoard +
    +
    + + +
    +

    Créer une application à partir d’un site

    +

    + Depuis n’importe quel site, vous pouvez générer une application pour l’ajouter + au tableau et interagir avec elle comme avec les applications par défaut. +

    +

    + Rendez-vous sur le site souhaité, cliquez sur + Icône Ajouter à la bibliothèque + puis confirmez en sélectionnant Créer une application. +

    + +
    + Création d’une application Web depuis le navigateur intégré +
    Création d'une application depuis le mode web.
    +
    + +

    + Une fois créée, l’application est automatiquement sauvegardée dans la + bibliothèque OpenBoard. Vous la retrouverez sous Applications > Web + et pourrez la réutiliser dans n’importe quel document. +

    + +
    + Application enregistrée dans la bibliothèque, dossier Web +
    Retrouver une application créée dans sa bibliothèque.
    +
    +
    + + +
    + +

    + Vous pouvez choisir d’ouvrir les liens dans un navigateur externe en cochant + l’option correspondante dans les préférences. Cliquer sur + Icône Mode Web + lancera alors votre navigateur favori en Mode Bureau. +

    + + +
    + +
    + +
    + +
    + diff --git a/resources/startupHints/locales/fr/7.html b/resources/startupHints/locales/fr/7.html index 3e896b291..453d15b6b 100644 --- a/resources/startupHints/locales/fr/7.html +++ b/resources/startupHints/locales/fr/7.html @@ -1,44 +1,102 @@ - + - - Capture d'écran - - + + Capture d’écran + + + + -
    -

    Capture d'écran

    -
    -
    - Une des fonctionnalités basiques mais vraiment utiles d'OpenBoard est la capture d'écran. Avec, vous pouvez créer un instantané d'un état quelconque du tableau, pendant votre cours. -

    En mode Tableau

    -

    Cherchez the screen capture icon dans la palette des stylets, cliquez dessus et sélectionnez la zone à capturer. Ensuite, trois options vous seront proposées :

    -
      -
    • Ajouter à la page courante
    • -
    • Ajouter à la nouvelle page
    • -
    • Ajouter à la bibliothèque(ajoute la capture au dossier Images, afin que vous puissiez la réutiliser à tout moment)
    • -
    - -

    Voici un exemple du processus complet :

    -
    - - Vous n'avez pas à vous inquiéter de la palette des stylets, de l'onglet d'aperçu des pages ou de celui de la bibliothèque OpenBoard, ils ne seront pas capturés par votre lasso ! - -

    En mode Bureau

    -

    Vous pouvez faire la même chose en mode Bureau, pour capturer votre travail sur d'autres logiciels. Passez en mode Bureau en cliquant sur

    -

    Vous trouverez deux icônes : pour capturer l'ensemble de l'écran, et pour n'en capturer qu'une partie. Comme en mode Tableau, vous devrez choisir où ajouter la capture

    - -

    Voici une illustration du processus complet :

    -
    - - -

    En mode Web

    -

    De la même façon, il est possible de faire ceci en mode Web, où vous pourrez effectuer une capture partielle de l'écran, ou capturer l'onglet actif du navigateur en cliquant sur

    -
    -
    - -
    + + +
    +
    +

    Une fonctionnalité simple et très utile

    +

    + La capture d’écran vous permet de créer un instantané de l’état du tableau (ou de votre écran) + pendant votre cours, pour archiver, partager ou réutiliser le contenu. +

    +
    + + +
    +

    En Mode Tableau

    +

    + Dans la palette des stylets, cliquez sur + Icône Capture zone (Mode Tableau), + puis sélectionnez la zone à capturer. Vous pourrez ensuite choisir : +

    +
      +
    • Ajouter à la page courante
    • +
    • Ajouter à une nouvelle page
    • +
    • + Ajouter à la bibliothèque — la capture est enregistrée dans le dossier Images + pour réutilisation ultérieure. +
    • +
    + +
    + Exemple de capture d’une zone en Mode Tableau +
    Exemple d'intégration d'une capture d'écran à OpenBoard.
    +
    + + +
    + + +
    +

    En Mode Bureau

    +

    + Passez en Mode Bureau via + Icône Mode Bureau + pour capturer d’autres logiciels. +

    +

    Deux options sont disponibles :

    +
      +
    • + Icône Capture écran + Capture de l’écran entier +
    • +
    • + Icône Capture zone + Capture d’une zone +
    • +
    +

    Comme en Mode Tableau, choisissez où ajouter la capture.

    + +
    + Exemple de capture en Mode Bureau +
    Capture d’écran complet ou partiel en Mode Bureau.
    +
    +
    + + +
    +

    En Mode Web

    +

    + En Mode Web, vous pouvez capturer une zone de l’écran ou + capturer l’onglet actif du navigateur avec + Icône Capture onglet. +

    +
    +
    + +
    +
    + diff --git a/resources/startupHints/locales/fr/8.html b/resources/startupHints/locales/fr/8.html index f37dc684b..d48032119 100644 --- a/resources/startupHints/locales/fr/8.html +++ b/resources/startupHints/locales/fr/8.html @@ -1,36 +1,102 @@ - + - - Images - - + + Images + + + + -
    -

    Images

    -
    -
    - Dans OpenBoard, vous pouvez accéder à des images de plusieurs façons. -

    Organisez votre dossier Images

    -

    Les captures d'écran ajoutées à la bibliothèque sont trouvables dans le dossier Images :
    Vous pouvez créer des sous-dossiers en utilisant tout en bas de la Bibliothèque. Ensuite, glissez-déposez vos images dans les différents dossiers que vous avez créés.

    -
    - - Peu importe où vous vous situez dans la bibliothèque OpenBoard, les images que vous ajouterez seront automatiquement placées à l'intérieur du dossier , permettant ainsi de maintenir la bibliothèque organisée. - -

    Ajoutez des images depuis votre ordinateur

    -

    Vous pouvez également ajouter des images depuis votre ordinateur, comme illustré ici :

    -
    - -

    Ajoutez des images à partir des moteurs de recherche

    -

    Enfin, vous pouvez chercher et ajouter au tableau des images libres de droit, en utilisant les moteurs de recherche situés dans le dossier "Recherche Web" :

    -
    - - Au lieu d'utiliser le glisser-déposer, cliquez simplement sur une image apparaissant en résultat de recherche, pour en voir les détails et y trouver des options telles que Ajouter à la bibliothèque. Utile lors de la préparation d'un cours ! -
    -
    - -
    + + +
    +
    +

    Accéder et organiser vos images

    +

    + Dans OpenBoard, vous pouvez accéder et ajouter des images de différentes façons depuis votre bibliothèque. Cette dernière se situe à droite de l'écran. C'est également ici que vous pouvez stocker des sons, vidéos et trouver différentes applications. +

    +
    + Zone d’import et d’export dans le Mode Documents +
    Image de la bibliothèque située à droite dans le logiciel.
    +
    +
    + + +
    +

    Organisez votre dossier Images

    +

    + Les captures d’écran ajoutées à la bibliothèque sont regroupées dans le dossier + Icône Images. + Vous pouvez créer des sous-dossiers via + Icône Nouveau dossier + (en bas de la bibliothèque), puis glisser-déposer vos images dans les différents dossiers créés. +

    + +
    + Organisation du dossier Images avec sous-dossiers +
    Exemple d’organisation et de classement d’images dans la bibliothèque.
    +
    + + +
    + + +
    +

    Ajoutez des images depuis votre ordinateur

    +

    + Vous pouvez importer vos propres fichiers image directement dans OpenBoard, comme illustré ci-dessous : +

    + +
    + Ajout d’images depuis l’ordinateur +
    Import d’une image locale dans OpenBoard.
    +
    +
    + + +
    +

    Ajoutez des images depuis le Web

    +

    + Vous pouvez également rechercher des images libres de droit via les moteurs de recherche intégrés, + situés dans le dossier Recherche Web + Icône Images. +

    + +
    + Ajout d’images depuis un moteur de recherche intégré +
    Exemple de recherche et d’ajout d’images depuis le Web.
    +
    + + +
    +
    + +
    + +
    + diff --git a/resources/startupHints/locales/fr/9.html b/resources/startupHints/locales/fr/9.html index 6ff5a7c90..d40c27113 100644 --- a/resources/startupHints/locales/fr/9.html +++ b/resources/startupHints/locales/fr/9.html @@ -1,41 +1,99 @@ - + - - Interactivités - - + + Interactivités + + + + -
    -

    Interactivités

    -
    -
    - Pour les plus jeunes élèves, vous trouverez un ensemble d'applications personnalisables qui leur sont dédiées. -

    Les exercices interactifs d'OpenBoard

    -

    Avec ces interactivités, vous pouvez créer différents types d'exercices. Il peut s'agir de catégorisation, calcul mental, mémorisation, etc.

    -

    Les interactivités sont situées dans la bibliothèque OpenBoard : cliquez sur pour les trouver.

    - -

    Exemple

    -

    Nous allons créer un exercice utilisant l'interactivité "Categorize pictures" (traduction : Catégorisez des images) :

    - Essayez ! Déplacez cette fenêtre sur un côté et reproduisez l'exemple sur le Tableau ! -

    Tout d'abord, glissez-déposez l'interactivité sur le tableau, et cliquez sur "Modifier"

    -

    Ensuite, changez les noms des catégories selon vos besoins :

    -
    - - Vous pouvez ajouter ou supprimer des catégories en utilisant les icônes + et - - -

    Ensuite, ajoutez les images correspondant à chaque catégorie.

    -
    - -

    Enfin, cliquez sur "Afficher" pour voir le résultat et faire l'exercice.

    -
    - - Vous pouvez relancer l'exercice en cliquant sur l'icône "Recharger". -
    -
    - -
    + + + +
    + +
    +

    Des exercices interactifs, simples à personnaliser

    +

    + Pour les plus jeunes élèves (et pas seulement), OpenBoard fournit un ensemble + d’interactivités personnalisables : catégorisation, calcul mental, mémorisation, etc. +

    +

    + Elles sont accessibles dans la bibliothèque : cliquez sur + Icône Interactivités. +

    +
    + + +
    +

    Exemple : « Categorize Pictures »

    +

    + Créons un exercice avec l’interactivité + Categorize pictures (catégoriser des images) : + Vignette Categorize Pictures. +

    + + + +

    1) Placer l’interactivité et ouvrir l’éditeur

    +

    + Glissez-déposez l’interactivité sur le Tableau, puis cliquez sur Modifier. +

    + +

    2) Nommer les catégories

    +

    + Renommez les catégories selon vos besoins. Vous pouvez aussi + ajouter ou supprimer des catégories avec les icônes « + » et « − ». +

    + +
    + Édition des catégories de l’interactivité +
    Édition des catégories : ajout, suppression, renommage.
    +
    + +

    3) Ajouter les images

    +

    + Ajoutez les images correspondant à chaque catégorie (glisser-déposer, sélection depuis la bibliothèque, etc.). +

    + +
    + Ajout d’images aux catégories +
    Associer des images aux catégories définies.
    +
    + +

    4) Afficher et faire l’exercice

    +

    + Cliquez sur Afficher pour lancer l’activité en mode élève. +

    + +
    + Exécution de l’exercice Categorize Pictures +
    Exécution de l’exercice : déplacer les images dans la bonne catégorie.
    +
    + + +
    +
    + +
    +
    diff --git a/resources/startupHints/locales/fr/css/style.css b/resources/startupHints/locales/fr/css/style.css deleted file mode 100644 index de0ecb4d5..000000000 --- a/resources/startupHints/locales/fr/css/style.css +++ /dev/null @@ -1,36 +0,0 @@ -*{ - font-family: Arial, Helvetica, sans-serif; -} - -.title -{ - width:100%; - color: #6682b5; - text-align: center; -} - -.file-extension -{ - background: lightgrey; -} - -.image -{ - text-align:center; -} - -.image img -{ - max-width: 600px; -} - -.icon -{ - vertical-align: middle; -} - -.tip -{ - font-style: italic; - font-size: 0.8rem; -} diff --git a/resources/startupHints/locales/fr/error.html b/resources/startupHints/locales/fr/error.html index f998df943..a24888b69 100644 --- a/resources/startupHints/locales/fr/error.html +++ b/resources/startupHints/locales/fr/error.html @@ -1,15 +1,20 @@ - - - - - erreur - - - - - -

    Quelque chose s'est mal passé ... -Vérifier l'existence de vos conseils dans le dossier -/Resources/startupHints/ …

    - - + + + + + Erreur + + + + + +
    +

    Erreur de chargement

    +

    + Quelque chose s'est mal passé. + Vérifiez la présence des pages d'astuces dans le dossier + /Resources/startupHints/. +

    +
    + + diff --git a/resources/startupHints/locales/hr/1.html b/resources/startupHints/locales/hr/1.html index 71537c1f4..ff6e6f57c 100644 --- a/resources/startupHints/locales/hr/1.html +++ b/resources/startupHints/locales/hr/1.html @@ -1,26 +1,95 @@ - + - - Savjeti - - + + Dobrodošli + + + - -
    -

    Dobro došao, dobro došla u OpenBoard

    -
    -
    -
    -
    -

    Ovdje se nalaze savjeti koji pomažu naučiti koristiti OpenBoard.

    -

    OpenBoard je interaktivna ploča koju su dizajnirali učitelji za učitelje. Postoje četiri modusa:ploča, radna površina, dokumenti i web.

    -

    Krenimo s prvim i najvažnijim: modus ploče.

    -
    -
    - Dijalog „Savjeti” možeš otvoriti u bilo kojem trenutku pomoću gumba (the OpenBoard menu) u OpenBoard izborniku -
    + + +
    + + +

    + Evo nekoliko savjeta i trikova koji će vam pomoći da se upoznate s OpenBoard. +

    + +

    + OpenBoard je interaktivna ploča koju su dizajnirali učitelji za učitelje. + Nudi cetiri nacina koji pokrivaju najcesce potrebe u nastavi. +

    + +
    +

    4 načina OpenBoard

    + +
    + +
    +

    Počnimo s prvim i najvažnijim: Nacin Ploca.

    +
    + + +
    + +
    + +
    + + - diff --git a/resources/startupHints/locales/hr/10.html b/resources/startupHints/locales/hr/10.html index 9c63026a8..64f75e070 100644 --- a/resources/startupHints/locales/hr/10.html +++ b/resources/startupHints/locales/hr/10.html @@ -1,44 +1,95 @@ - - - - - Programi - - - - - -
    -

    Programi

    -
    -
    -

    OpenBoard pruža nekoliko programa i alata. Možeš im pristupiti pritiskom na u OpenBoard biblioteci

    -

    Standardni programi

    -

    Pronaći ćeš geometrijske alate, kalkulator, Google kartu, generator QR koda i još više! Kombiniraj ih za stvaranje poticajnih nastava!

    -
    -

    Stvaranje programa

    -

    Kao što je objašnjeno u odjeljku Web modusa, programi s web stranica se mogu izraditi koristeći interni navigator ili kopiranjem i umetanjem URL adresa na ploču.

    -

    Na ovaj način možeš podržati svoju nastavu moćnim online alatima izravno na ploči! Primjeri zanimljivih programa koje se mogu stvoriti (povuci i ispusti ih na ploču za testiranje!):

    - - -

    … i još puno više!

    - - Trebaš moćniji kalkulator od onog koji nudi OpenBoard? Misli na to da možeš stvoriti vlastite programe! Jednostavno povuci i ispusti sljedeću poveznicu na ploču: https://ti89-simulator.com/ -
    -
    - -
    - - - + + + + + Prijave + + + + + + + + + + +
    + +
    +

    Pristup aplikacijama

    +

    + OpenBoard nudi drugačije aplikacije i alate. + Pronađite ih u knjižnici klikom na + Ikona aplikacija. +

    +
    + + +
    +

    Zadane aplikacije

    +

    + Pronaći ćete geometrijske alate, kalkulator, Google karte, generator QR koda i još mnogo toga. + Kombinirajte ih kako biste stvorili privlačna obrazovna iskustva! +

    + +
    + Primjer korištenja integriranih aplikacija +
    Primjer korištenja ugrađenih aplikacija OpenBoard.
    +
    +
    + + +
    +

    Izradite vlastite aplikacije

    +

    + Kao što je objašnjeno u odjeljku Nacin Web, možete izraditi aplikaciju sa stranice, + pomoću internog preglednika ili kopiranjem i lijepljenjem URL-a na ploču. +

    +

    + To vam omogućuje da poboljšate svoje tečajeve moćnim online alatima, koji se mogu koristiti izravno u OpenBoard. + Evo nekoliko zanimljivih primjera (povucite i ispustite veze na ploču da ih testirate): +

    + + + +

    ...i mnogo više!

    + + +
    +
    + +
    + +
    + + + + + diff --git a/resources/startupHints/locales/hr/11.html b/resources/startupHints/locales/hr/11.html index 60b0268fb..04aa87f4d 100644 --- a/resources/startupHints/locales/hr/11.html +++ b/resources/startupHints/locales/hr/11.html @@ -1,39 +1,90 @@ - - - - - Snimanje videa - - - - - -
    -

    Snimanje videa

    -
    -
    -

    Snimanje nastave

    -

    OpenBoard omogućuje snimanja videa nastave.

    -

    Vrlo korisno za dijeljenje dodatnih vježbi ili informacija putem videa ili za snimanje nastave za učenike koji nisu prisutni.

    - - U modusu ploče se alatne trake i palete neće vidjeti u videu! Isto vrijedi i za web modus, gdje će se na snimci vidjeti samo trenutačna kartica. - -

    Otvori ovaj alat ovako:

    -
    - -

    U donjem desnom kutu ploče će se pojaviti sljedeće sučelje:

    -
    - -

    Pritisni crveni gumb za pokretanje snimanja.

    - - Postavke se mogu prilagoditi pritiskom na . Tamo se nalaze postavke za zvuk (odaberi mikrofon ili ništa), postavke za video (razlučivost videa) i opcije za objavljivanje - -

    Nakon što je snimanje gotovo, snimka će se automatski spremiti na radnu površinu računala

    -
    - -
    -
    - -
    - - + + + + + Video snimanje + + + + + + + + + + +
    + +
    +

    Snimite svoj tečaj

    +

    + OpenBoard vam omogućuje spremanje audio i video vašeg tečaja. + Idealno za dijeljenje dodatnih vježbi, objavljivanje kratkog isječka s objašnjenjem ili + učiniti tečaj dostupnim odsutnim studentima. +

    + + +
    + + +
    +

    Pristupite alatu za registraciju

    +

    Otvorite alat za izrezivanje kao što je prikazano u nastavku:

    + +
    + Otvaranje alata za video snimanje u OpenBoard +
    Brzi pristup alatu za registraciju sa sučelja OpenBoard.
    +
    +
    + + +
    +

    Upravljačko sučelje

    +

    Sučelje se pojavljuje u donjem desnom kutu tablice:

    + +
    + Sučelje snimača (kontrole dolje desno) +
    + +

    Kliknite na crveni gumb za početak snimanja.

    + + +
    + + +
    +

    Završite i oporavite videozapis

    +

    + Nakon što je snimanje završeno, video se automatski pokreće spremljeno na radnu površinu vašeg računala. +

    + +
    + Završite snimanje i spremite video na radnu površinu +
    Zaustavite snimanje i video datoteka dostupna na radnoj površini.
    +
    +
    +
    + +
    + +
    + + diff --git a/resources/startupHints/locales/hr/12.html b/resources/startupHints/locales/hr/12.html index 73a9de08c..28c0523ae 100644 --- a/resources/startupHints/locales/hr/12.html +++ b/resources/startupHints/locales/hr/12.html @@ -1,37 +1,94 @@ - - - - - Favoriti - - - - - -
    -

    Favoriti

    -
    -
    -

    Dodavanje OpenBoard dokumenata u favorite

    -

    OpenBoard verzija 1.7 omogućuje dodavanje OpenBoard dokumenata u favorite!

    -

    Idi u modus dokumenata, odaberi jedan dokument i pritisni na u alatnoj traci dokumenata.

    - -
    - -

    Brzo prebacivanje s jednog dokumenta na drugi

    -

    Omiljeni dokumenti će se pojaviti u OpenBoard biblioteci pod
    - Svaki OpenBoard dokument () se može povući i ispustiti na ploču!

    -
    - - Koristi traku za pretraživanje na dnu OpenBoard biblioteke za brzo pronalaženje dokumenta na osnovi imena - -

    Nedavno otvoreni dokumenti

    -

    Svaki dokument koji se otvori privremeno se dodaje u favorite za brzo prebacivanje između dokumenata, bez da moraju biti izričito označeni kao favoriti.

    -
    - Ako nedavno otvoreni dokument želiš trajno dodati u favorite, to možeš obaviti putem OpenBoard biblioteke: odaberi ga i pritisni na -
    -
    - -
    - - + + + + + Omiljeni dokumenti + + + + + + + + + + +
    + +
    +

    Dodajte OpenBoard dokumenata u favorite

    +

    + Uveden u verzija 1.7, ova vam funkcija omogućuje dodavanje vaših OpenBoard dokumenata + u favoriti. +

    +

    + Da biste to učinili, idite na Nacin Dokumenti, odaberite dokument i kliknite + Ikona Dodaj u favorite + na alatnoj traci. +

    + +
    + Dodavanje OpenBoard dokumenta u favorite + +
    +
    + + +
    +

    Brzo prijeđite s jednog dokumenta na drugi

    +

    + Vaši omiljeni dokumenti pojavljuju se u knjižnica OpenBoard, u nastavku + Ikona favorita. +

    +

    + Svaki omiljeni dokument + Ikona dokumenta OpenBoard + može se povući i ispustiti izravno na stol. +

    + +
    + Pregledavanje favorita u biblioteci OpenBoard +
    + + + +
    + + +
    +

    Nedavno otvoreni dokumenti

    +

    + Svaki otvoreni dokument privremeno se dodaje u favorite, radi brzog prebacivanja s jednog na drugi + tijekom iste sesije, bez potrebe da ih izričito označite. +

    + +
    + Primjer nedavno otvorenih dokumenata u OpenBoard + +
    + + +
    +
    + +
    +
    + + diff --git a/resources/startupHints/locales/hr/2.html b/resources/startupHints/locales/hr/2.html index 1ab997c6e..831628e93 100644 --- a/resources/startupHints/locales/hr/2.html +++ b/resources/startupHints/locales/hr/2.html @@ -1,48 +1,122 @@ - - - - - Modus ploče - - - - - -
    -

    Modus ploče

    -
    -
    -

    Paleta alata

    -

    Paleta alata sadrži osnovne alate za rad na ploči.

    -
    - -

    Sadrži pisaljku (), gumicu () i marker (). OpenBoard također sadrži daljnje funkcije koje klasična ploča ne pruža:

    -
      -
    • je selektor. Omogućuje biranje objekata kao i interakciju s njima.
    • -
    • je čarobni prst. Omogućuje pomicanje objekata ili interakciju s njima bez da se moraju odabrati.
    • -
    • je ruka. Omogućuje kretanje po stranici. Vrlo korisno u kombinaciji sa zumiranjem!
    • -
    • omogućuje zumiranje. Odaberi vrstu zumiranja i pritisni stranicu na koju želiš primijeniti zumiranje!
    • -
    • je laserski pokazivač. Koristno za označavanje elemenata na ploči bez interakcije s njima.
    • -
    • se koristi za jednostavno crtanje ravnih crta.
    • -
    • je alat za pisanje teksta pomoću tipkovnice umjesto pisaljke.
    • -
    - - Zumiranje možeš preciznije odrediti pomoću miša! Koristi Ctrl/Cmd + kotačić miša za preciznije zumiranje! - - -

    Alatna traka ploče

    -

    Alatna traka ploče omogućuje pored ostalog mijenjanje boje i debljine pisaljke ili markera.

    -
    - -

    Omogućuje mijenjanje pozadine ploče pritiskom na

    -
    - -

    Sadrži gumbe za poništavanje/ponavljanje radnji, stvaranje i kretanje po stranicama, brisanje cijele ploče …

    - - Dijelove ploče možeš preciznije obrisati dugim pritiskom na the erase icon. Neke skrivene funkcije i na the erase icon! -
    -
    - -
    - - + + + + + Nacin Ploca + + + + + + + + +
    + +
    +

    Paleta olovki

    +

    + Paleta olovki vam daje pristup osnovnim alatima kada radite na bijeloj ploči. +

    + +
    + OpenBoard paleta stilusa +
    + +

    + Tamo ćete pronaći olovku + Olovka, + guma + Gumica za brisanje + i highlighter + Highlighter. + OpenBoard također pruža značajke koje tradicionalna pametna ploča ne može: +

    + +
      +
    • + + Selektor : odabir i interakcija s objektima. +
    • +
    • + + Čarobni prst : pomicanje/interakcija bez prethodnog odabira. +
    • +
    • + + Ruka : pomicanje stranice (vrlo korisno u kombinaciji sa zumiranjem). +
    • +
    • + + + Zumiraj / Smanji : Kliknite gdje želite primijeniti efekt nakon što ga odaberete. +
    • +
    • + + Laserski pokazivač : pokažite na elemente bez interakcije s njima. +
    • +
    • + + Linija : lako crtajte ravne linije. +
    • +
    • + + Tekst : umetnite tekstualni okvir za tipkanje na tipkovnici. +
    • +
    + + +
    + + +
    +

    Stolna traka

    +

    + Traka tablice omogućuje vam promjenu boja i/ili veličina između ostalog olovka i highlighter. +

    + +
    + OpenBoard traka tablice +
    + +

    + Također možete promijeniti pozadina stola klikom na + Promjena pozadine. +

    + +
    + Primjer mijenjanja pozadine stola +
    + +

    + Dostupne su i druge funkcije: poništiti/ponoviti, stvarati stranice, kretati se između stranica, čistiti tablicu itd. +

    + + +
    +
    + +
    + +
    + + diff --git a/resources/startupHints/locales/hr/3.html b/resources/startupHints/locales/hr/3.html index 16da5b6ee..e23f1e462 100644 --- a/resources/startupHints/locales/hr/3.html +++ b/resources/startupHints/locales/hr/3.html @@ -1,30 +1,74 @@ - - - - - Modus radne površine - - - - - -
    -

    Modus radne površine

    -
    -
    -

    Interakcija s drugim programima

    -

    Modus radne površine omogućuje interakciju s računalom i drugim programima, zadržavajući OpenBoard u prednjem planu!

    -

    Jednostavno pritisni sljedeću ikonu:

    -
    -
    Koristi selektor () za interakciju s radnom površinom i drugim programima.
    -

    Dodavanje pribilješki programu ili web stranici

    -
    Pribilješke se mogu dodati bilo kojem programu koristeći pisaljku () ili marker ().
    -
    - - Pristupi daljnjim opcijama za the pen, the marker i the eraser dugim pritiskom na ikonu ili pritiskom na malu crnu strelicu. -
    -
    - -
    - - + + + + + Nacin Radna Povrsina + + + + + + + + + +
    + +
    +

    Interakcija s drugim aplikacijama

    +

    + Opcija Uredski način rada omogućuje vam interakciju s vašim operativnim sustavom i softverom, + zadržavajući OpenBoard alate na vrhu. +

    +

    Jednostavno kliknite na sljedeću ikonu:

    + +
    + Ikona za aktiviranje Nacin Radna Povrsina +
    + +

    + Koristite selektor + Selektor + za interakciju s radnom površinom i drugim aplikacijama. +

    +
    + + +
    +

    Označite aplikacije ili web stranice

    +

    + Možete označiti bilo koji softver s olovka + Olovka + ili marker + Marker. +

    + +
    + Označavanje aplikacije u Nacin Radna Povrsina +
    Primjer anotacije na web stranici.
    +
    + + +
    +
    + +
    + +
    + + diff --git a/resources/startupHints/locales/hr/4.html b/resources/startupHints/locales/hr/4.html index dbafab821..36fb32411 100644 --- a/resources/startupHints/locales/hr/4.html +++ b/resources/startupHints/locales/hr/4.html @@ -1,32 +1,79 @@ - - - - - Modus dokumenata - - - - - -
    -

    Modus dokumenata

    -
    -
    -

    OpenBoard sadrži sustav za upravljanje dokumentima. Jednostavno pritisni

    -
    - -

    Stvaranje mape i organiziranje rada

    -

    Mapa se može stvoriti pritiskom na ikonu „Nova mapa”. Upiši ime za mapu te povuci i ispusti dokumente u mapu. Tehnika povuci i ispusti se može koristiti i za premještanje cijele mape u jednu drugu mapu.

    -
    - -

    Bartanje stranicama

    -

    OpenBoard dokument može sadržati više stranica. Mogu se duplicirati, premjestiti u smeće ili u jedan drugi dokument. Nove stranice se mogu stvoriti i iz mapa sa slikama, …

    -
    - - Imaj na umu da se dostupne radnje aktualiziraju prema onome što je odabrano -
    -
    - -
    - - + + + + + Nacin Dokumenti + + + + + + + + + +
    + +
    +

    Pristupite upravitelju dokumenata

    +

    + OpenBoard nudi a upravitelj dokumenata. Kliknite na + Ikona dokumenata + da ga otvorim. +

    +
    + Pogled upravitelja dokumenata +
    +
    + + +
    +

    Stvorite mape i organizirajte svoj rad

    +

    + Stvorite mapu putem + Nova mapa, + ime, onda povuci i ispusti svoje dokumente unutra. + Isto tako, možete premjestiti cijelu mapu u drugu. +

    +
    + Stvaranje i organiziranje datoteka +
    Primjer izrade i organiziranja mapa.
    +
    +
    + + +
    +

    Upravljanje stranicama

    +

    + Dokument OpenBoard može sadržavati nekoliko stranica. Iz Nacin Dokumenti možete: +

    +
      +
    • Duplicirane stranice
    • +
    • Pošaljite ih u smeće
    • +
    • Kopirajte ih u drugi dokument
    • +
    • Stvorite nove stranice iz mape slika
    • +
    +
    + Primjeri akcija na stranicama +
    Primjer obrade dokumenata.
    +
    + + +
    +
    + +
    +
    + + diff --git a/resources/startupHints/locales/hr/5.html b/resources/startupHints/locales/hr/5.html index 8b6b7161b..70188ab92 100644 --- a/resources/startupHints/locales/hr/5.html +++ b/resources/startupHints/locales/hr/5.html @@ -1,39 +1,105 @@ - - - - - Modus dokumenata - - - - - -
    -

    Modus dokumenata

    -
    -
    -

    U ovom se modusu nalaze dvije važne funkcije: uvoz i izvoz.

    -
    - -

    Uvoz

    -

    Omogućuje uvoz PDF datoteka (.pdf), slika (.png, .jpg), OpenBoard dokumenata (.ubz) ili OpenBoard mapa (.ubx) pritiskom na

    -
    -

    OpenBoard dokument (.ubz) se može otvoriti i dvostrukim pritiskom na dokument. Ako OpenBoard još nije pokrenut, program će se pokrenuti i dokument će se uvesti (ili će se zatražiti zamjena ako već postoji).

    - Također je moguće uvesti više elemenata odjednom, bez potrebe za višestrukim pritiskom na . -

    Izvoz

    -

    Omogućuje izvoz OpenBoard dokumenta u PDF ili UBZ format kao i izvoz OpenBoard mape u UBX format. Na taj način je moguće: -

      -
    • Spremiti rad obavljen tijekom predavanja i dijeliti ga s učenicima koji nisu prisutni
    • -
    • Dijeliti ga s drugim učiteljima ili ga spremiti na USB uređaj i otvoriti ga na jednom drugom računalu
    • -
    • Uvesti domaću zadaću učenika u PDF formatu, dodati pribilješku te izvesti i učeniku poslati komentirani PDF
    • -
    -

    -
    - - Izvezi sve tvoje dokumente biranjem mape „Moji dokumenti” prije pritiskanja gumba za izvoz kako bi se izvezli kao UBX datoteke, a zatim ih uvezi na jednom drugom računalu. To je također dobar način za spremanje sigurnosne kopije tvog rada! -
    -
    - -
    - - + + + + + + Uvoz i izvoz + + + + + + + + + + +
    + +
    +

    Uvoz i izvoz

    +

    + U ovom načinu rada imate dvije bitne značajke: uvoz I izvoz. +

    +
    + Područje uvoza i izvoza u Nacin Dokumenti + +
    +
    + + +
    +

    Uvoz sadržaja

    +

    + Možete uvesti datoteke PDF (.pdf), + od slike (.png, .jpg), + od dokumenti OpenBoard (.ubz) ili + OpenBoard mapa (.ubx) klikom na + Ikona uvoza. + +

    + +
    + Primjer uvoza datoteka u OpenBoard +
    Primjer uvoza datoteka u OpenBoard.
    +
    + +

    + Također možete uvesti OpenBoard dokument (.ubz) u + dupli klik na njemu iz vašeg sustava. OpenBoard pokrenut će se ako je potrebno i + će ponuditi uvoz (ili zamjenu ako već postoji). +

    + + +
    + + +
    +

    Izvezite svoje dokumente

    +

    + Možete izvesti a dokument OpenBoard u formatu PDF Ili UBZ, + i a mapa OpenBoard u formatu UBX. Na primjer: +

    +
      +
    • Zabilježite rad obavljen tijekom lekcije i podijelite ga s odsutnim učenikom.
    • +
    • Podijelite s drugim učiteljima ili spremite na USB ključ za uvoz na drugo mjesto.
    • +
    • Uvezite studentov rad kao PDF, označite ga, zatim izvezite komentirani PDF za ponovno slanje.
    • +
    + +
    + Primjeri izvoza dokumenata i mapa OpenBoard +
    Primjer izvoza datoteka iz OpenBoard.
    +
    + + +
    +
    + +
    +
    + + + diff --git a/resources/startupHints/locales/hr/6.html b/resources/startupHints/locales/hr/6.html index 49e5fc24a..cac5678bd 100644 --- a/resources/startupHints/locales/hr/6.html +++ b/resources/startupHints/locales/hr/6.html @@ -1,38 +1,96 @@ - - - - - Web modus - - - - - -
    -

    Web modus

    -
    -
    - Web modus je interni navigator s dodatnim funkcijama. -

    Korištenje internog navigatora

    -

    Interni navigator omogućuje pregledavanje interneta bez potrebe za smanjivanjem prozora ili zatvaranjem OpenBoarda. Pored toga omogućuje snimanja dijela ekrana ili stvaranje programa iz web stranica koje se mogu dodati na ploču!

    - -
    - -

    Stvaranje programa

    -

    Program se može stvoriti s bilo kojeg mjesta i dodati na ploču te s njom interagirati kao sa standardnim programima.

    -

    Otvori web stranicu koju želiš snimiti, pritisni na Capture Web Content te potvrdi izradu pritiskom na „Stvori program”.

    -
    - -

    Kad je program stvoren, on se automatski sprema u OpenBoard biblioteku, tako da se može koristiti u bilo kojem dokumentu. Programi se nalaze u mapi s programima koja se zove „Web”.

    - -
    - -

    Korištenje eksternog navigatora

    -

    Označavanjem odgovarajuće opcije u postavkama može se koristiti i eksterni navigator. Tada će se pritiskom na web ikonu pokrenuti favorizirani navigator u modusu radne površine.

    - Koristeći eksterni navigator, i dalje ćeš moći stvarati programe s web stranice. Kopiraj URL-adresu u adresnu traku navigatora, vrati se na ploču i umetni je! -
    -
    - -
    - - + + + + + Nacin Web + + + + + + + + + +
    + +
    +

    Integrirani preglednik

    +

    + THE Nacin Web je interni preglednik za OpenBoard koji dodaje pregledniku + klasične, vrlo praktične značajke za učionicu. +

    +

    + Omogućuje vam pregledavanje interneta bez napuštanja OpenBoard, za snimanje sadržaja (djelomičnog ili + preko cijelog zaslona) i čak stvarati aplikacije s web stranica za + ponovno koristiti u svojim dokumentima. +

    + +
    + Primjer korištenja ugrađenog preglednika OpenBoard +
    +
    + + +
    +

    Napravite aplikaciju sa stranice

    +

    + S bilo kojeg mjesta možete generirati a primjena dodati ga + na ploči i komunicirati s njim kao sa zadanim aplikacijama. +

    +

    + Idite na željenu stranicu, kliknite na + Ikona Dodaj u knjižnicu + zatim potvrdite odabirom Napravite aplikaciju. +

    + +
    + Izrada web aplikacije iz ugrađenog preglednika +
    Izrada aplikacije iz web moda.
    +
    + +

    + Jednom kreirana aplikacija se automatski sprema u + knjižnica OpenBoard. Naći ćete ga ispod Aplikacije > Web + i može ga ponovno koristiti u bilo kojem dokumentu. +

    + +
    + Aplikacija spremljena u biblioteku, web mapu +
    Pronađite aplikaciju stvorenu u svojoj knjižnici.
    +
    +
    + + +
    + +

    + Možete izabrati da otvorite veze u a vanjski preglednik provjerom + odgovarajuću opciju u postavkama. Kliknite na + Nacin Web ikona + će zatim pokrenuti vaš omiljeni preglednik kao Nacin Radna Povrsina. +

    + + +
    + +
    + +
    + +
    + + + diff --git a/resources/startupHints/locales/hr/7.html b/resources/startupHints/locales/hr/7.html index 702dc2e45..cfcbe9ec0 100644 --- a/resources/startupHints/locales/hr/7.html +++ b/resources/startupHints/locales/hr/7.html @@ -1,44 +1,102 @@ - - - - - Snimanje ekrana - - - - - -
    -

    Snimanje ekrana

    -
    -
    - Jedna od najjednostavnijih ali vrlo korisnih OpenBoard funkcija je snimanje ekrana, što omogućuje snimanje bilo kojeg određenog stanja ploče tijekom nastave. -

    U modusu ploče

    -

    Tražithe screen capture icon u paleti alata. Pritisni ikonu i odaberi područje za snimanje. Nakon toga postoje tri mogućnosti:

    -
      -
    • Dodaj u trenutačnu stranicu
    • -
    • Dodaj u novu stranicu
    • -
    • Dodaj u biblioteku(dodaje snimku u mapu „Slike” kako bi se mogla ponovo koristiti u bilo kojem trenutku)
    • -
    - -

    Primjer cijelog postupka:

    -
    - - Paleta alata, prikaz minijatura te OpenBoard biblioteka se neće snimiti pri odabiru sadržaja! - -

    U modusu radne površine

    -

    Za snimanje vlastitog rada u drugim programima, isto se može obaviti u modusu radne površine. Prijeđi u modus radne površine pritiskom na

    -

    Prikazat će se dvije ikone: za snimanje cijelog ekrana i za snimanje dijelova ekrana. Kao u modusu ploče, morat ćeš odabrati mjesto za ispuštanje snimke

    - -

    Prikaz cijelog postupka

    -
    - - -

    U web modusu

    -

    Isti postupak je dostupan u web modusu, gdje ja moguće snimiti dio ekrana ili trenutačnu karticu pomoću

    -
    -
    - -
    - - + + + + + Snimka zaslona + + + + + + + + + +
    +
    +

    Jednostavna i vrlo korisna značajka

    +

    + Snimka zaslona omogućuje vam stvaranje a trenutak stanje ploče (ili vašeg zaslona) + tijekom tečaja, za arhiviranje, dijeljenje ili ponovno korištenje sadržaja. +

    +
    + + +
    +

    U Nacin Ploca

    +

    + U paleti olovke kliknite + Ikona područja snimanja (Nacin Ploca), + zatim odaberite područje za snimanje. Zatim možete odabrati: +

    +
      +
    • Dodaj trenutnoj stranici
    • +
    • Dodaj na novu stranicu
    • +
    • + Dodaj u knjižnicu — snimak se sprema u mapu Slike + za kasniju ponovnu upotrebu. +
    • +
    + +
    + Primjer snimanja područja u Nacin Ploca +
    Primjer ugradnje snimke zaslona u OpenBoard.
    +
    + + +
    + + +
    +

    U Nacin Radna Povrsina

    +

    + Prebacite se na Nacin Radna Povrsina putem + Nacin Radna Povrsina ikona + za snimanje drugog softvera. +

    +

    Dostupne su dvije opcije:

    +
      +
    • + Ikona snimanja zaslona + Snimanje cijelog zaslona +
    • +
    • + Ikona područja snimanja + Snimanje područja +
    • +
    +

    Kao u Nacin Ploca, odaberite gdje ćete dodati snimanje.

    + +
    + Primjer snimanja u Nacin Radna Povrsina +
    Cijeli ili djelomični snimak zaslona u Nacin Radna Povrsina.
    +
    +
    + + +
    +

    U Nacin Web

    +

    + U Nacin Web možete uhvatiti područje ekrana ili + snimi aktivnu karticu iz preglednika sa + Ikona kartice za snimanje. +

    +
    +
    + +
    +
    + + + diff --git a/resources/startupHints/locales/hr/8.html b/resources/startupHints/locales/hr/8.html index d1c36c50f..4caa1a76e 100644 --- a/resources/startupHints/locales/hr/8.html +++ b/resources/startupHints/locales/hr/8.html @@ -1,36 +1,102 @@ - - - - - Slike - - - - - -
    -

    Slike

    -
    -
    - OpenBoard omogućuje pristup slikama na nekoliko načina. -

    Organiziranje mape „Slike”

    -

    Snimke ekrana koje se dodaju u biblioteku spremaju se u mapu „Slike”:
    Mape se mogu stvoriti koristeći na dnu biblioteke. Zatim povuci i ispusti slike u stvorene mape.

    -
    - - Bez obzira gdje se nalaziš u OpenBoard biblioteci, slike koje dodaš će se automatski smjestiti u mapu „Slike”. - -

    Dodavanje slika iz računala

    -

    Prikaz dodavanja slika koja su spremljena na računalu:

    -
    - -

    Dodavanje slika iz tražilica

    -

    Besplatne slike možeš tražiti i dodati na ploču koristeći tražilice koje se nalaze u mapi „Web pretraga”:

    -
    - - Umjesto korištenja tehnike povuci i ispusti, jednostavno pritisni sliku koju je pronašla tražilica za prikaz detalja slike i pronađi opciju za dodavanje slike u biblioteku, bez dodavanja slike u trenutačni dokument. Korisno za pripremanje nastave! -
    -
    - -
    - - + + + + + Slike + + + + + + + + + +
    +
    +

    Pristupite svojim slikama i organizirajte ih

    +

    + U OpenBoard možete pristupiti i dodavati slike na različite načine od svojih knjižnica. Potonji se nalazi na desnoj strani ekrana. Ovdje također možete pohraniti zvukove, video zapise i pronaći različite aplikacije. +

    +
    + Područje uvoza i izvoza u Nacin Dokumenti +
    Slika knjižnice koja se nalazi desno u softveru.
    +
    +
    + + +
    +

    Organizirajte svoju mapu Slike

    +

    + Snimke zaslona dodane u biblioteku grupirane su u mapi + Slike ikona. + Možete stvarati podmape preko + Nova ikona mape + (na dnu biblioteke), zatim povucite i ispustite svoje slike u različite stvorene mape. +

    + +
    + Organiziranje mape Slike s podmapama +
    Primjer organizacije i klasifikacije slika u knjižnici.
    +
    + + +
    + + +
    +

    Dodajte slike sa svog računala

    +

    + Možete uvesti vlastite slikovne datoteke izravno u OpenBoard, kao što je prikazano u nastavku: +

    + +
    + Dodavanje slika s računala +
    Uvoz lokalne slike u OpenBoard.
    +
    +
    + + +
    +

    Dodajte slike s weba

    +

    + Također možete tražiti slike bez naknade putem integriranih tražilica, + koji se nalazi u mapi Web pretraživanje + Slike ikona. +

    + +
    + Dodavanje slika iz integrirane tražilice +
    Primjer pronalaženja i dodavanja slika s weba.
    +
    + + +
    +
    + +
    + +
    + + + diff --git a/resources/startupHints/locales/hr/9.html b/resources/startupHints/locales/hr/9.html index 03fc2b937..6fa8d5ba4 100644 --- a/resources/startupHints/locales/hr/9.html +++ b/resources/startupHints/locales/hr/9.html @@ -1,43 +1,99 @@ - - - - - Interaktivnosti - - - - - -
    -

    Interaktivnosti

    -
    -
    - Za mlađe učenike postoji poseban skup prilagođenih programa. -

    Interaktivna vježba za OpenBoard

    -

    Pomoću ovih interaktivnosti mogu se izraditi različite vrste prilagođenih vježbi. To se može odnositi na kategorizaciju, računanje, pamćenje i tako dalje.

    -

    Interaktivnosti se nalaze u OpenBoard biblioteci: pronađi ih pritiskom na .

    - -

    Primjer

    -

    Kao primjer ćemo izraditi vježbu koristeći interaktivnost „Kategoriziraj sliku”:

    - Probaj! Pomakni ovaj dijalog na stranu i reproduciraj primjer na ploči! -

    Najprije povuci i ispusti interaktivnost na tvoju ploču i pritisni „Uredi”

    -

    Zatim promijeni imena kategorija prema svojim potrebama:

    -
    - - Kategorije možeš dodati ili izbrisati pomoću ikona + i - - -

    Zatim dodaj željene slike u odgovarajuću kategoriju.

    -
    - -

    Na kraju pritisni „Prikaz” za prikaz rezultata i izvođenje vježbe.

    -
    - - Vježbu možeš ponovo pokrenuti pomoću ikone „Učitaj ponovo”. - - -
    -
    - -
    - - + + + + + Interaktivnosti + + + + + + + + + + +
    + +
    +

    Interaktivne vježbe, jednostavne za personalizaciju

    +

    + Za najmlađe učenike (i ne samo), OpenBoard nudi set + odinteraktivnosti prilagodljiv: kategorizacija, mentalno računanje, pamćenje itd. +

    +

    + Dostupni su u knjižnica : kliknite na + Ikona interaktivnosti. +

    +
    + + +
    +

    Primjer: “Kategoriziraj slike”

    +

    + Kreirajmo vježbu s interaktivnošću + mačkaegorizirati slikaures (kategoriziraj slike): + Thumbnail Kategorizirajte slike. +

    + + + +

    1) Postavite interaktivnost i otvorite uređivač

    +

    + Povucite i ispustite interaktivnost na ploču, zatim kliknite Za izmjenu. +

    + +

    2) Imenujte kategorije

    +

    + Preimenujte kategorije prema potrebi. Možete također + dodati Ili IZBRISATI kategorije s ikonama "+" i "−". +

    + +
    + Uređivanje kategorija interaktivnosti +
    Uređivanje kategorija: dodavanje, brisanje, preimenovanje.
    +
    + +

    3) Dodajte slike

    +

    + Dodajte slike koje odgovaraju svakoj kategoriji (povuci i ispusti, odabir iz biblioteke itd.). +

    + +
    + Dodavanje slika u kategorije +
    Povežite slike s definiranim kategorijama.
    +
    + +

    4) Pokažite i napravite vježbu

    +

    + Kliknite na Prikaz za pokretanje aktivnosti u studentskom načinu rada. +

    + +
    + Izvođenje vježbe Kategoriziraj slike +
    Izvođenje vježbe: premjestite slike u odgovarajuću kategoriju.
    +
    + + +
    +
    + +
    +
    + + diff --git a/resources/startupHints/locales/hr/css/style.css b/resources/startupHints/locales/hr/css/style.css deleted file mode 100644 index de0ecb4d5..000000000 --- a/resources/startupHints/locales/hr/css/style.css +++ /dev/null @@ -1,36 +0,0 @@ -*{ - font-family: Arial, Helvetica, sans-serif; -} - -.title -{ - width:100%; - color: #6682b5; - text-align: center; -} - -.file-extension -{ - background: lightgrey; -} - -.image -{ - text-align:center; -} - -.image img -{ - max-width: 600px; -} - -.icon -{ - vertical-align: middle; -} - -.tip -{ - font-style: italic; - font-size: 0.8rem; -} diff --git a/resources/startupHints/locales/hr/error.html b/resources/startupHints/locales/hr/error.html index 525043180..1c01b9e20 100644 --- a/resources/startupHints/locales/hr/error.html +++ b/resources/startupHints/locales/hr/error.html @@ -1,15 +1,20 @@ - - - - - erreur - - - - - -

    Dogodila se greška … -Provjeri postojanje savjeta u datoteci -/Resources/startupHints/ …

    - - + + + + + Greška + + + + + +
    +

    Greška pri učitavanju

    +

    + Nešto nije u redu. + Provjerite prisutnost stranica sa savjetima u mapi + /Resursi/startupHints/. +

    +
    + + diff --git a/resources/startupHints/locales/it/1.html b/resources/startupHints/locales/it/1.html index 03a739b0d..1cb2f4404 100644 --- a/resources/startupHints/locales/it/1.html +++ b/resources/startupHints/locales/it/1.html @@ -1,26 +1,95 @@ - + - - Suggerimenti e consigli - - + + Benvenuto + + + - -
    -

    Benvenuti in OpenBoard

    -
    -
    -
    -
    -

    Ecco alcuni suggerimenti per aiutarti a familiarizzare con OpenBoard.

    -

    OpenBoard è una lavagna interattiva progettata da insegnanti, per insegnanti. Dispone di 4 modalità: Modalità Lavagna, Modalità Desktop, Modalità Documenti e Modalità Web.

    -

    Iniziamo con la prima e più importante: la Modalità Lavagna.

    -
    -
    - Puoi riaprire il dialogo "Suggerimenti e consigli" in qualsiasi momento, utilizzando il pulsante dedicato nel menu di OpenBoard (il menu di OpenBoard) -
    + + +
    + + +

    + Ecco alcuni suggerimenti per aiutarti a familiarizzare con OpenBoard. +

    + +

    + OpenBoard e una lavagna interattiva progettata da insegnanti, per insegnanti. + Offre quattro modalità complementari per coprire i tuoi usi in classe. +

    + +
    +

    Le 4 modalita di OpenBoard

    + +
    + +
    +

    Prima di tutto, e cosa più importante: Modalita Lavagna.

    +
    + + +
    + +
    + +
    + + - diff --git a/resources/startupHints/locales/it/10.html b/resources/startupHints/locales/it/10.html index 3ceabe0b8..cf2854aa8 100644 --- a/resources/startupHints/locales/it/10.html +++ b/resources/startupHints/locales/it/10.html @@ -1,45 +1,95 @@ - - - - - Applicazioni - - - - - -
    -

    Applicazioni

    -
    -
    -

    OpenBoard dispone di diverse applicazioni e strumenti. Puoi accedervi cliccando su nella Libreria di OpenBoard

    -

    Applicazioni predefinite

    -

    Troverai strumenti geometrici, una calcolatrice, Google Map, un generatore di codici QR e altro ancora! Combinali per creare lezioni stimolanti!

    -
    -

    Crea applicazioni

    -

    Come spiegato nella sezione Modalità Web, puoi creare applicazioni da siti web, utilizzando il navigatore interno, o copiando e incollando gli URL sulla lavagna.

    -

    In questo modo, puoi supportare le tue lezioni con potenti strumenti online direttamente sulla lavagna! Ecco alcuni esempi di app interessanti da creare (Trascinale sulla lavagna per testarle!):

    - - -

    ... e molto altro ancora!

    - - Hai bisogno di una calcolatrice più potente di quella fornita da OpenBoard? Ricorda! Puoi creare le tue applicazioni! Trascina semplicemente il seguente link sulla lavagna: https://ti89-simulator.com/ -
    -
    - -
    - - - - + + + + + Applicazioni + + + + + + + + + + +
    + +
    +

    Accedere alle applicazioni

    +

    + OpenBoard offre diverse applicazioni e strumenti. + Le trovi nella libreria facendo clic su + Icona Applicazioni. +

    +
    + + +
    +

    Applicazioni predefinite

    +

    + Troverai strumenti geometrici, una calcolatrice, Google Maps, un generatore di QR code e molto altro. + Combinali per creare esperienze didattiche coinvolgenti. +

    + +
    + Esempio di utilizzo delle applicazioni integrate +
    Esempio di utilizzo delle applicazioni integrate di OpenBoard.
    +
    +
    + + +
    +

    Crea le tue applicazioni

    +

    + Come spiegato nella sezione Modalita Web, puoi creare un'applicazione da un sito, + usando il browser interno oppure copiando e incollando un URL sulla lavagna. +

    +

    + Questo ti permette di arricchire le lezioni con strumenti online efficaci, utilizzabili direttamente in OpenBoard. + Ecco alcuni esempi interessanti (trascina i link sulla lavagna per provarli): +

    + + + +

    ... e molto altro!

    + + +
    +
    + +
    + +
    + + + + + diff --git a/resources/startupHints/locales/it/11.html b/resources/startupHints/locales/it/11.html index 86ef8f83b..88ccac629 100644 --- a/resources/startupHints/locales/it/11.html +++ b/resources/startupHints/locales/it/11.html @@ -1,40 +1,90 @@ - - - - - Registrazione Video - - - - - -
    -

    Registrazione Video

    -
    -
    -

    Registra la tua lezione

    -

    OpenBoard ti offre la possibilità di registrare un audio e video della tua lezione.

    -

    Questo può essere utile, ad esempio, per condividere esercizi o informazioni aggiuntive tramite un video clip o per registrare una lezione al fine di condividerla con gli studenti assenti.

    - - In modalità Lavagna, le barre degli strumenti e le palette non appariranno nel video clip, quindi non preoccuparti di esse! Lo stesso vale in modalità Web, dove apparirà solo la scheda corrente nella registrazione. - -

    Puoi aprire questo strumento in questo modo:

    -
    - -

    Vedrai apparire l'interfaccia seguente in basso a destra sulla lavagna bianca:

    -
    - -

    Fai clic sul pulsante rosso per avviare la registrazione.

    - - Puoi regolare le impostazioni facendo clic su . Troverai impostazioni audio (seleziona un microfono o nessuno), impostazioni video (risoluzione della registrazione video) e opzioni di pubblicazione configurabili. - -

    Una volta completata la registrazione, verrà automaticamente salvata sul desktop del computer.

    -
    - -
    -
    - -
    - - - + + + + + Cattura video + + + + + + + + + + +
    + +
    +

    Registra la tua lezione

    +

    + OpenBoard permette di registrare audio e video della tua lezione. + Ideale per condividere esercizi aggiuntivi, pubblicare un breve video esplicativo + o mettere una lezione a disposizione degli studenti assenti. +

    + + +
    + + +
    +

    Accedere allo strumento di registrazione

    +

    Apri lo strumento di cattura come mostrato qui sotto:

    + +
    + Apertura dello strumento Cattura video in OpenBoard +
    Accesso rapido allo strumento di registrazione dall'interfaccia di OpenBoard.
    +
    +
    + + +
    +

    Interfaccia di controllo

    +

    L'interfaccia appare in basso a destra della lavagna:

    + +
    + Interfaccia del registratore (controlli in basso a destra) +
    + +

    Fai clic sul pulsante rosso per avviare la registrazione.

    + + +
    + + +
    +

    Terminare e recuperare il video

    +

    + Una volta terminata la registrazione, il video viene automaticamente salvato sul Desktop del tuo computer. +

    + +
    + Fine registrazione e salvataggio del video sul Desktop +
    Arresto della registrazione e file video disponibile sul Desktop.
    +
    +
    +
    + +
    + +
    + + diff --git a/resources/startupHints/locales/it/12.html b/resources/startupHints/locales/it/12.html index 5d621a416..26e7a7a3d 100644 --- a/resources/startupHints/locales/it/12.html +++ b/resources/startupHints/locales/it/12.html @@ -1,38 +1,94 @@ - - - - - Documenti Preferiti - - - - - -
    -

    Documenti Preferiti

    -
    -
    -

    Aggiungi documenti OpenBoard ai preferiti

    -

    Con OpenBoard 1.7, puoi aggiungere documenti OpenBoard ai tuoi preferiti!

    -

    Per farlo, vai alla Modalità Documenti, seleziona un documento e clicca su nella barra degli strumenti dei Documenti.

    - -
    - -

    Cambia rapidamente tra i documenti

    -

    I tuoi documenti preferiti appariranno nella Libreria OpenBoard, sotto
    - Ogni documento OpenBoard () può quindi essere trascinato sulla Lavagna!

    -
    - - Puoi utilizzare la barra di ricerca in fondo alla Libreria OpenBoard per trovare rapidamente un documento per il suo nome - -

    Documenti aperti di recente

    -

    Ogni documento che apri viene temporaneamente aggiunto ai preferiti, in modo da poter passare tra ognuno di essi durante una sessione, senza la necessità di contrassegnarli esplicitamente come preferiti.

    -
    - Se desideri aggiungere permanentemente un documento aperto di recente tra i tuoi preferiti, puoi farlo tramite la Libreria OpenBoard: selezionalo e clicca su -
    -
    - -
    - - - + + + + + Documenti preferiti + + + + + + + + + + +
    + +
    +

    Aggiungere documenti OpenBoard ai preferiti

    +

    + Introdotta nella versione 1.7, questa funzionalita permette di aggiungere i tuoi documenti OpenBoard + ai preferiti. +

    +

    + Per farlo, passa alla modalita Documenti, seleziona un documento e fai clic su + Icona Aggiungi ai preferiti + nella barra degli strumenti. +

    + +
    + Aggiunta di un documento OpenBoard ai preferiti + +
    +
    + + +
    +

    Passare rapidamente da un documento all'altro

    +

    + I documenti preferiti appaiono nella libreria OpenBoard, sotto + Icona Preferiti. +

    +

    + Ogni documento preferito + Icona Documento OpenBoard + puo essere trascinato direttamente sulla lavagna. +

    + +
    + Visualizzazione dei preferiti nella libreria OpenBoard +
    + + + +
    + + +
    +

    Documenti aperti di recente

    +

    + Ogni documento aperto viene aggiunto temporaneamente ai preferiti, per passare rapidamente da uno all'altro + durante la stessa sessione, senza doverli contrassegnare esplicitamente. +

    + +
    + Esempio di documenti aperti di recente in OpenBoard + +
    + + +
    +
    + +
    +
    + + diff --git a/resources/startupHints/locales/it/2.html b/resources/startupHints/locales/it/2.html index cc5e8b1c6..be85591de 100644 --- a/resources/startupHints/locales/it/2.html +++ b/resources/startupHints/locales/it/2.html @@ -1,48 +1,122 @@ - - - - - Modalità Lavagna - - - - - -
    -

    Modalità Lavagna

    -
    -
    -

    La palette della penna

    -

    La palette della penna ti offre accesso agli strumenti essenziali quando lavori su una lavagna.

    -
    - -

    Qui troverai la penna (), la gomma () e il marcatore (). OpenBoard offre anche altre funzionalità che una lavagna classica non può fornire:

    -
      -
    • è il selettore. Con esso, puoi selezionare oggetti e interagire con essi.
    • -
    • è il dito magico. Puoi spostare gli oggetti o interagire con essi senza la necessità di selezionarli.
    • -
    • è la mano. Puoi muoverti sulla pagina con essa. Molto utile quando combinata con le funzioni di zoom!
    • -
    • ti danno la possibilità di ingrandire e rimpicciolire. Scegli semplicemente quello che desideri e fai clic sulla pagina dove vuoi applicare lo zoom!
    • -
    • è un puntatore laser, utile per evidenziare elementi sulla lavagna senza interagire con essi.
    • -
    • è utilizzato per disegnare linee rette facilmente.
    • -
    • è uno strumento di testo, per scrivere un testo utilizzando la tastiera invece della penna.
    • -
    - - Puoi avere un controllo più preciso dello zoom con il mouse! Usa Ctrl/Cmd + la rotellina del mouse per ingrandire/ridurre con maggiore precisione! - -

    La barra degli strumenti della lavagna

    -

    La barra degli strumenti della lavagna ti offre la possibilità di cambiare colore e dimensione della penna o del marcatore, tra altre cose.

    -
    - -

    Troverai anche la possibilità di cambiare lo sfondo della lavagna, cliccando su

    -
    - -

    Troverai anche pulsanti per annullare/ripristinare azioni, creare e navigare tra le pagine, cancellare l'intera lavagna, ...

    - - Puoi cancellare parti più precise della lavagna facendo clic prolungato su l'icona per cancellare. Alcune funzioni nascoste anche su l'icona per cancellare! -
    -
    - -
    - - - + + + + + Modalita Lavagna + + + + + + + + +
    + +
    +

    La tavolozza dello stilo

    +

    + La tavolozza dello stilo consente di accedere agli strumenti essenziali quando si lavora su una lavagna. +

    + +
    + OpenBoard tavolozza dello stilo +
    + +

    + Lì troverai la matita + Matita, + la gomma + Gomma per cancellare + e l'evidenziatore + Evidenziatore. + OpenBoard fornisce inoltre funzionalità che una lavagna tradizionale non può: +

    + +
      +
    • + + Selettore : seleziona e interagisci con gli oggetti. +
    • +
    • + + Dito magico : muoversi/interagire senza prima selezionare. +
    • +
    • + + Mano : sposta la pagina (molto utile abbinato allo zoom). +
    • +
    • + + + Zoom/Rimpicciolisci : fare clic su dove applicare l'effetto dopo averlo selezionato. +
    • +
    • + + Puntatore laser : punta agli elementi senza interagire con essi. +
    • +
    • + + Linea : traccia facilmente linee rette. +
    • +
    • + + Testo : inserisce una casella di testo per la digitazione da tastiera. +
    • +
    + + +
    + + +
    +

    Il tavolo-bar

    +

    + La barra della tabella consente di modificare il colore e/o il misurare matita ed evidenziatore, tra le altre cose. +

    + +
    + OpenBoard barra della tabella +
    + +

    + Puoi anche modificare il file sfondo del tavolo cliccando su + Cambia lo sfondo. +

    + +
    + Esempio di modifica dello sfondo del tavolo +
    + +

    + Sono disponibili altre funzioni: annullare/rifare, creare pagine, navigare tra le pagine, cancellare la tabella, ecc. +

    + + +
    +
    + +
    + +
    + + diff --git a/resources/startupHints/locales/it/3.html b/resources/startupHints/locales/it/3.html index fef4192ba..4874db0b0 100644 --- a/resources/startupHints/locales/it/3.html +++ b/resources/startupHints/locales/it/3.html @@ -1,31 +1,74 @@ - - - - - Modalità Desktop - - - - - -
    -

    Modalità Desktop

    -
    -
    -

    Interagire con altri software

    -

    Utilizzando la Modalità Desktop, puoi interagire con l'intero computer e altri software, mantenendo OpenBoard come sovrapposizione!

    -

    Basta cliccare sull'icona seguente:

    -
    -
    Usa il selettore () per interagire con il desktop e altri software.
    -

    Fare annotazioni sopra un'applicazione o un sito web

    -
    Puoi aggiungere annotazioni sopra qualsiasi software, utilizzando la penna () o il marcatore ().
    -
    - - Puoi accedere a ulteriori opzioni per la penna, il marcatore e la gomma facendo un clic prolungato su di essi o facendo clic sulla piccola freccia nera. -
    -
    - -
    - - - + + + + + Modalita Desktop + + + + + + + + + +
    + +
    +

    Interagire con altre applicazioni

    +

    + La modalita Desktop ti permette di interagire con il sistema operativo e le tue applicazioni, + mantenendo gli strumenti di OpenBoard sempre in primo piano. +

    +

    Fai semplicemente clic sull'icona seguente:

    + +
    + Icona per attivare la modalita Desktop +
    + +

    + Usa il selettore + Selettore + per interagire con il desktop e con le altre applicazioni. +

    +
    + + +
    +

    Annotare applicazioni o siti web

    +

    + Puoi annotare qualsiasi software con la penna + Penna + o con l'evidenziatore + Evidenziatore. +

    + +
    + Annotazione di un'applicazione in modalita Desktop +
    Esempio di annotazione su una pagina web.
    +
    + + +
    +
    + +
    + +
    + + diff --git a/resources/startupHints/locales/it/4.html b/resources/startupHints/locales/it/4.html index c12820ed1..2c498f229 100644 --- a/resources/startupHints/locales/it/4.html +++ b/resources/startupHints/locales/it/4.html @@ -1,33 +1,79 @@ - - - - - Modalità Documenti - - - - - -
    -

    Modalità Documenti

    -
    -
    -

    OpenBoard dispone di un gestore documenti. Puoi accedervi semplicemente facendo clic su

    -
    - -

    Crea cartelle e organizza il tuo lavoro

    -

    Puoi creare una cartella cliccando sull'icona "Nuova Cartella". Assegnale un nome e trascina i tuoi documenti all'interno. Allo stesso modo, puoi spostare un'intera cartella in un'altra.

    -
    - -

    Gestisci le pagine

    -

    Un documento OpenBoard può contenere molte pagine. Puoi duplicarle, spostarle nel cestino o in un altro documento, crearne di nuove da cartelle di immagini, ...

    -
    - - Tieni presente che le azioni disponibili vengono aggiornate in base a ciò che è selezionato -
    -
    - -
    - - - + + + + + Modalita Documenti + + + + + + + + + +
    + +
    +

    Accedere al gestore documenti

    +

    + OpenBoard offre un gestore documenti. Fai clic su + Icona Documenti + per aprirlo. +

    +
    + Vista del gestore documenti +
    +
    + + +
    +

    Crea cartelle e organizza il tuo lavoro

    +

    + Crea una cartella tramite + Nuova cartella, + assegnale un nome, poi trascina e rilascia i documenti al suo interno. + Allo stesso modo puoi spostare un'intera cartella dentro un'altra. +

    +
    + Creazione e organizzazione di cartelle +
    Esempio di creazione e organizzazione delle cartelle.
    +
    +
    + + +
    +

    Gestione delle pagine

    +

    + Un documento OpenBoard puo contenere piu pagine. Dalla modalita Documenti puoi: +

    +
      +
    • Duplicare pagine
    • +
    • Inviarle nel cestino
    • +
    • Copiarle in un altro documento
    • +
    • Creare nuove pagine a partire da una cartella di immagini
    • +
    +
    + Esempi di azioni sulle pagine +
    Esempio di gestione dei documenti.
    +
    + + +
    +
    + +
    +
    + + diff --git a/resources/startupHints/locales/it/5.html b/resources/startupHints/locales/it/5.html index 145fa7b6d..61a3184c4 100644 --- a/resources/startupHints/locales/it/5.html +++ b/resources/startupHints/locales/it/5.html @@ -1,40 +1,104 @@ - - - - - Modalità Documenti - - - - - -
    -

    Modalità Documenti

    -
    -
    -

    In questa modalità troverai anche due funzionalità molto importanti: Importa ed Esporta.

    -
    - -

    Importa

    -

    Puoi importare file PDF (.pdf), immagini (.png, .jpg), documenti OpenBoard (.ubz) o cartelle OpenBoard (.ubx) cliccando su

    -
    -

    Nota che puoi anche importare un documento OpenBoard (.ubz) facendo doppio clic su di esso. Si aprirà OpenBoard se non è già aperto e lo importerà (o chiederà di sostituirlo se esiste già).

    - Puoi anche importare più elementi contemporaneamente, senza dover fare clic su nuovamente e nuovamente. -

    Esporta

    -

    Puoi esportare un documento OpenBoard in formato PDF o UBZ e una cartella OpenBoard in formato UBX. Pertanto, puoi, ad esempio: -

      -
    • Salvare il lavoro svolto durante una sessione di classe e condividerlo con uno studente assente
    • -
    • Condividerlo con altri insegnanti o archiviarlo su un dispositivo USB e aprirlo su un altro computer
    • -
    • Importare i compiti di uno studente in formato PDF, annotarli e esportare il PDF annotato per restituirlo allo studente
    • -
    -

    -
    - - Esporta tutti i tuoi documenti selezionando la cartella "I miei documenti" prima di fare clic sul pulsante di esportazione, per esportarla come file UBX, e quindi importala su un altro computer. È anche un buon modo per fare un backup del tuo lavoro! -
    -
    - -
    - - - + + + + + Importare ed esportare + + + + + + + + + + +
    + +
    +

    Import & Export

    +

    + In questa modalita hai due funzionalita essenziali: importazione ed esportazione. +

    +
    + Area di importazione ed esportazione nella modalita Documenti + +
    +
    + + +
    +

    Importare contenuti

    +

    + Puoi importare file PDF (.pdf), + immagini (.png, .jpg), + documenti OpenBoard (.ubz) o + cartelle OpenBoard (.ubx) facendo clic su + Icona Importa. + +

    + +
    + Esempio di importazione file in OpenBoard +
    Esempio di importazione di file in OpenBoard.
    +
    + +

    + Puoi anche importare un documento OpenBoard (.ubz) + facendo doppio clic dal tuo sistema. OpenBoard si avviera, se necessario, + e ti proporra di importarlo (o sostituirlo se esiste gia). +

    + + +
    + + +
    +

    Esportare i tuoi documenti

    +

    + Puoi esportare un documento OpenBoard in formato PDF o UBZ, + e una cartella OpenBoard in formato UBX. Ad esempio: +

    +
      +
    • Salvare il lavoro svolto durante una lezione e condividerlo con uno studente assente.
    • +
    • Condividerlo con altri insegnanti o salvarlo su una chiavetta USB per importarlo altrove.
    • +
    • Importare il lavoro di uno studente in PDF, annotarlo ed esportare il PDF annotato per rimandarlo.
    • +
    + +
    + Esempi di esportazione di documenti e cartelle OpenBoard +
    Esempio di esportazione di file da OpenBoard.
    +
    + + +
    +
    + +
    +
    + + + diff --git a/resources/startupHints/locales/it/6.html b/resources/startupHints/locales/it/6.html index 29d400c2b..cc6d85b91 100644 --- a/resources/startupHints/locales/it/6.html +++ b/resources/startupHints/locales/it/6.html @@ -1,39 +1,95 @@ - - - - - Modalità Web - - - - - -
    -

    Modalità Web

    -
    -
    - La Modalità Web è un browser interno che aggiunge alcune funzionalità interessanti a uno più standard. -

    Usa il browser interno

    -

    Utilizzando il browser interno, sarai in grado di navigare su Internet senza la necessità di ridurre o uscire da OpenBoard, con alcune funzionalità aggiuntive che ti consentiranno di catturare parte dello schermo o creare applicazioni dai siti web e aggiungerle alla lavagna!

    - -
    - -

    Crea un'applicazione

    -

    Puoi creare un'applicazione da qualsiasi sito web, in modo da aggiungerla alla lavagna e interagire con essa come con le applicazioni predefinite.

    -

    Per farlo, vai al sito web che desideri catturare, fai clic su Cattura contenuto web e conferma la creazione cliccando su "Crea un'applicazione".

    -
    - -

    Quando un'applicazione viene creata, viene automaticamente salvata nella libreria di OpenBoard, in modo da poterla utilizzare in qualsiasi documento. Le troverai nella cartella delle applicazioni, sotto una dedicata chiamata "Web".

    - -
    - -

    Usa un browser esterno

    -

    Puoi anche scegliere di utilizzare un browser esterno, selezionando l'opzione corrispondente nelle preferenze. Cliccando sull'icona Web lancerà il tuo browser preferito in Modalità Desktop.

    - Usando un browser esterno, sarai comunque in grado di creare applicazioni da un sito web, copiando l'URL nella barra degli indirizzi del tuo browser, tornando su Board e incollandolo! -
    -
    - -
    - - - + + + + + Modalita Web + + + + + + + + + +
    + +
    +

    Il browser integrato

    +

    + La modalita Web e un browser interno di OpenBoard che aggiunge, rispetto a un browser + tradizionale, funzioni molto utili per la didattica. +

    +

    + Permette di navigare su Internet senza uscire da OpenBoard, catturare contenuti (parziali o a schermo intero) + e perfino creare applicazioni a partire da siti web per riutilizzarle nei tuoi documenti. +

    + +
    + Esempio di uso del browser integrato di OpenBoard +
    +
    + + +
    +

    Creare un'applicazione a partire da un sito

    +

    + Da qualsiasi sito puoi creare una applicazione da aggiungere alla lavagna + e utilizzare come le applicazioni predefinite. +

    +

    + Apri il sito desiderato, fai clic su + Icona Aggiungi alla libreria + e conferma selezionando Crea un'applicazione. +

    + +
    + Creazione di un'applicazione Web dal browser integrato +
    Creazione di un'applicazione dalla modalita Web.
    +
    + +

    + Una volta creata, l'applicazione viene salvata automaticamente nella + libreria OpenBoard. La troverai in Applicazioni > Web + e potrai riutilizzarla in qualsiasi documento. +

    + +
    + Applicazione salvata nella libreria, cartella Web +
    Ritrovare un'applicazione creata nella libreria.
    +
    +
    + + +
    + +

    + Puoi scegliere di aprire i link in un browser esterno attivando + l'opzione nelle preferenze. Facendo clic su + Icona modalita Web + verra avviato il tuo browser preferito in modalita Desktop. +

    + + +
    + +
    + +
    + +
    + + + diff --git a/resources/startupHints/locales/it/7.html b/resources/startupHints/locales/it/7.html index f5f9484b7..a2c8b7fee 100644 --- a/resources/startupHints/locales/it/7.html +++ b/resources/startupHints/locales/it/7.html @@ -1,45 +1,102 @@ - - - - - Cattura Schermo - - - - - -
    -

    Cattura Schermo

    -
    -
    - Una delle funzionalità più semplici ma davvero utili di OpenBoard è la cattura schermo. Con essa, puoi creare uno snapshot di uno stato particolare della lavagna durante la tua sessione di classe. -

    In Modalità Lavagna

    -

    Cercal'icona di cattura schermo nella palette dello stilo, cliccaci sopra e seleziona l'area da catturare. Successivamente, avrai tre opzioni :

    -
      -
    • Aggiungi alla pagina corrente
    • -
    • Aggiungi in una nuova pagina
    • -
    • Aggiungi alla libreria (aggiunge la cattura alla cartella Immagini, in modo da poterla riutilizzare in qualsiasi momento)
    • -
    - -

    Ecco un esempio di tutto il processo :

    -
    - - Non devi preoccuparti della palette dello stilo, della vista delle miniature della lavagna o della libreria di OpenBoard, non verranno catturati dal tuo lasso! - -

    In Modalità Desktop

    -

    Puoi fare la stessa cosa anche in Modalità Desktop per catturare il tuo lavoro su altri software. Vai in Modalità Desktop cliccando su

    -

    Troverai due icone : per catturare l'intero schermo e per catturare solo una parte. Come in Modalità Lavagna, dovrai scegliere dove posizionare la cattura effettuata

    - -

    Ecco un'illustrazione di tutto il processo

    -
    - - -

    In Modalità Web

    -

    Di nuovo, lo stesso processo è disponibile in Modalità Web, dove sarai in grado di catturare solo una parte dello schermo o catturare la scheda corrente con

    -
    -
    - -
    - - - + + + + + Cattura schermo + + + + + + + + + +
    +
    +

    Une fonctionnalité simple et très utile

    +

    + La cattura schermo ti permette di creare un istantanea dello stato della lavagna (o dello schermo) + durante la lezione, per archiviare, condividere o riutilizzare i contenuti. +

    +
    + + +
    +

    In modalita Lavagna

    +

    + Nella palette degli strumenti, fai clic su + Icona Cattura area (modalita Lavagna), + poi seleziona la zona da catturare. In seguito potrai scegliere: +

    +
      +
    • Aggiungi alla pagina corrente
    • +
    • Aggiungi a una nuova pagina
    • +
    • + Aggiungi alla libreria — la cattura viene salvata nella cartella Immagini + per un uso successivo. +
    • +
    + +
    + Esempio di cattura di un'area in modalita Lavagna +
    Esempio di integrazione di una cattura schermo in OpenBoard.
    +
    + + +
    + + +
    +

    In modalita Desktop

    +

    + Passa alla modalita Desktop tramite + Icona modalita Desktop + per catturare altre applicazioni. +

    +

    Sono disponibili due opzioni:

    +
      +
    • + Icona Cattura schermo + Cattura schermo intero +
    • +
    • + Icona Cattura area + Cattura di un'area +
    • +
    +

    Come in modalita Lavagna, scegli dove aggiungere la cattura.

    + +
    + Esempio di cattura in modalita Desktop +
    Cattura schermo completa o parziale in modalita Desktop.
    +
    +
    + + +
    +

    In modalita Web

    +

    + In modalita Web puoi catturare un'area dello schermo oppure + catturare la scheda attiva del browser con + Icona Cattura scheda. +

    +
    +
    + +
    +
    + + + diff --git a/resources/startupHints/locales/it/8.html b/resources/startupHints/locales/it/8.html index 5f40e7f21..b452fbe01 100644 --- a/resources/startupHints/locales/it/8.html +++ b/resources/startupHints/locales/it/8.html @@ -1,37 +1,102 @@ - - - - - Immagini - - - - - -
    -

    Immagini

    -
    -
    - In OpenBoard, puoi accedere alle immagini in diversi modi. -

    Organizza la tua cartella Immagini

    -

    Le catture dello schermo aggiunte alla Libreria vengono salvate nella cartella Immagini :
    Puoi creare cartelle utilizzando nella parte inferiore della Libreria. Quindi, trascina e rilascia le tue immagini nelle diverse cartelle che hai creato.

    -
    - - Non importa dove ti trovi nella Libreria di OpenBoard, le immagini che aggiungi saranno automaticamente collocate all'interno della cartella Immagini, per mantenere l'organizzazione. - -

    Aggiungi immagini dal tuo computer

    -

    Puoi anche aggiungere immagini direttamente dal tuo computer, come illustrato di seguito :

    -
    - -

    Aggiungi immagini dai motori di ricerca

    -

    Ultimo ma non meno importante, puoi cercare e aggiungere alla tua lavagna immagini royalty free utilizzando i motori di ricerca situati nella cartella Ricerca Web :

    -
    - - Al posto di utilizzare il trascinamento, basta fare clic su una delle immagini restituite dalla tua ricerca per visualizzare i dettagli sull'immagine e trovare un'opzione per aggiungerla alla Libreria senza aggiungerla al tuo documento attuale. Utile quando si prepara una lezione! -
    -
    - -
    - - - + + + + + Images + + + + + + + + + +
    +
    +

    Accedere e organizzare le immagini

    +

    + In OpenBoard puoi accedere e aggiungere immagini in diversi modi dalla tua libreria. La libreria si trova a destra dello schermo. Qui puoi anche archiviare suoni, video e trovare varie applicazioni. +

    +
    + Area libreria a destra nell'interfaccia +
    Vista della libreria posizionata a destra nel software.
    +
    +
    + + +
    +

    Organizza la cartella Immagini

    +

    + Le catture schermo aggiunte alla libreria sono raggruppate nella cartella + Icona Immagini. + Puoi creare sottocartelle tramite + Icona Nuova cartella + (in basso nella libreria), poi trascinare le immagini nelle cartelle create. +

    + +
    + Organizzazione della cartella Immagini con sottocartelle +
    Esempio di organizzazione e classificazione delle immagini nella libreria.
    +
    + + +
    + + +
    +

    Aggiungere immagini dal computer

    +

    + Puoi importare i tuoi file immagine direttamente in OpenBoard, come mostrato qui sotto: +

    + +
    + Aggiunta di immagini dal computer +
    Importazione di un'immagine locale in OpenBoard.
    +
    +
    + + +
    +

    Aggiungere immagini dal Web

    +

    + Puoi anche cercare immagini libere da diritti tramite i motori di ricerca integrati, + disponibili nella cartella Ricerca Web + Icona Immagini. +

    + +
    + Aggiunta di immagini da un motore di ricerca integrato +
    Esempio di ricerca e aggiunta di immagini dal Web.
    +
    + + +
    +
    + +
    + +
    + + + diff --git a/resources/startupHints/locales/it/9.html b/resources/startupHints/locales/it/9.html index 62545121c..f03391097 100644 --- a/resources/startupHints/locales/it/9.html +++ b/resources/startupHints/locales/it/9.html @@ -1,44 +1,99 @@ - - - - - Interattività - - - - - -
    -

    Interattività

    -
    -
    - Per gli studenti più giovani, troverai un set dedicato di applicazioni personalizzabili. -

    Esercizi interattivi di OpenBoard

    -

    Utilizzando queste interattività, puoi creare diversi tipi di esercizi personalizzati. Questi possono riguardare la categorizzazione, i calcoli, la memorizzazione e così via.

    -

    Le interattività si trovano nella Libreria di OpenBoard : clicca su per trovarle.

    - -

    Esempio

    -

    Come esempio, creeremo un esercizio utilizzando l'interattività "Categorizza immagini" :

    - Provalo! Sposta questa finestra di dialogo da un lato e riproduci l'esempio sulla lavagna! -

    Prima, trascina e rilascia l'interattività sulla tua lavagna e clicca su "Modifica"

    -

    Quindi, cambia il nome delle categorie secondo le tue esigenze :

    -
    - - Puoi aggiungere o eliminare categorie usando le icone + e - - -

    Successivamente, aggiungi le immagini che desideri nella categoria corrispondente.

    -
    - -

    Infine, clicca su "Visualizza" per mostrare il risultato e svolgere l'esercizio.

    -
    - - Puoi ripetere l'esercizio utilizzando l'icona "Ricarica". - - -
    -
    - -
    - - - + + + + + Interattivita + + + + + + + + + + +
    + +
    +

    Esercizi interattivi, semplici da personalizzare

    +

    + Per gli alunni piu giovani (e non solo), OpenBoard offre un insieme di + interattivita personalizzabili: categorizzazione, calcolo mentale, memorizzazione, ecc. +

    +

    + Sono disponibili nella libreria: fai clic su + Icona Interattivita. +

    +
    + + +
    +

    Esempio: "Categorize Pictures"

    +

    + Creiamo un esercizio con l'interattivita + Categorize pictures (categorizzare immagini): + Miniatura Categorize Pictures. +

    + + + +

    1) Posizionare l'interattivita e aprire l'editor

    +

    + Trascina l'interattivita sulla lavagna, poi fai clic su Modifica. +

    + +

    2) Dare un nome alle categorie

    +

    + Rinomina le categorie secondo le tue esigenze. Puoi anche + aggiungere o eliminare categorie con le icone "+" e "-". +

    + +
    + Modifica delle categorie dell'interattivita +
    Modifica categorie: aggiunta, eliminazione, rinomina.
    +
    + +

    3) Aggiungere le immagini

    +

    + Aggiungi le immagini corrispondenti a ogni categoria (trascinamento, selezione dalla libreria, ecc.). +

    + +
    + Aggiunta immagini alle categorie +
    Associare immagini alle categorie definite.
    +
    + +

    4) Avviare e svolgere l'esercizio

    +

    + Fai clic su Mostra per avviare l'attivita in modalita studente. +

    + +
    + Esecuzione dell'esercizio Categorize Pictures +
    Esecuzione dell'esercizio: spostare le immagini nella categoria corretta.
    +
    + + +
    +
    + +
    +
    + + diff --git a/resources/startupHints/locales/it/css/style.css b/resources/startupHints/locales/it/css/style.css deleted file mode 100644 index de0ecb4d5..000000000 --- a/resources/startupHints/locales/it/css/style.css +++ /dev/null @@ -1,36 +0,0 @@ -*{ - font-family: Arial, Helvetica, sans-serif; -} - -.title -{ - width:100%; - color: #6682b5; - text-align: center; -} - -.file-extension -{ - background: lightgrey; -} - -.image -{ - text-align:center; -} - -.image img -{ - max-width: 600px; -} - -.icon -{ - vertical-align: middle; -} - -.tip -{ - font-style: italic; - font-size: 0.8rem; -} diff --git a/resources/startupHints/locales/it/error.html b/resources/startupHints/locales/it/error.html index 05ddb6b3a..cedc7403b 100644 --- a/resources/startupHints/locales/it/error.html +++ b/resources/startupHints/locales/it/error.html @@ -1,15 +1,20 @@ - - - - - errore - - - - - -

    Qualcosa è andato storto... -Verifica l'esistenza dei tuoi suggerimenti nella cartella -/Resources/startupHints/ …

    - - + + + + + Errore + + + + + +
    +

    Errore di caricamento

    +

    + Si e verificato un problema. + Verifica la presenza delle pagine dei suggerimenti nella cartella + /Resources/startupHints/. +

    +
    + + diff --git a/resources/style.qss b/resources/style.qss index 92bd8d6b5..32840a69d 100644 --- a/resources/style.qss +++ b/resources/style.qss @@ -9,24 +9,29 @@ QWidget#UBFeaturesNavigatorWidget, QWidget#PathList, QWidget#UBFeaturesCentralWidget { - background: #EEEEEE; + background: palette(window); border-radius: 10px; - border: 2px solid #999999; + border: 2px solid palette(mid); + color: palette(window-text); } QTextEdit, QLineEdit, QComboBox#DockPaletteWidgetComboBox QAbstractItemView { - selection-background-color: lightgreen; - selection-color: black; + selection-background-color: palette(highlight); + selection-color: palette(highlighted-text); + background-color: palette(base); + color: palette(text); } QWidget#mAdditionalDataContainer { border-radius: 10px; - border: 2px solid #999999; + border: 2px solid palette(mid); + background: palette(window); + color: palette(window-text); } QWidget#UBMediaVideoContainer @@ -38,9 +43,10 @@ QWidget#UBMediaVideoContainer QWidget#UBLibWebView { - background: #EEEEEE; + background: palette(window); border-radius : 10px; - border: 2px solid #999999; + border: 2px solid palette(mid); + color: palette(window-text); } QListView @@ -55,40 +61,113 @@ QWidget#UBFeatureProperties QWebView#SearchEngineView { - background:white; + background: palette(base); + color: palette(text); } QColorDialog { - background: #EEEEEE; + background: palette(window); + color: palette(window-text); +} + +QToolBar QToolButton +{ + margin: 4px; + margin-left: 0px; + margin-right: 0px; + padding: 0px; + border: none; + height: 58px; + background: transparent; +} + +/* Preserve grouped toolbar button geometry across platforms. */ +QToolButton#ubButtonGroupLeft, +QToolButton#desktop-ubButtonGroupLeft +{ + margin-top: 1px; + margin-right: 0px; + padding: 5px; + height: 14px; +} + +QToolButton#ubButtonGroupCenter, +QToolButton#desktop-ubButtonGroupCenter +{ + margin-top: 1px; + margin-right: 0px; + margin-left: 0px; + padding: 5px; + height: 14px; +} + +QToolButton#ubButtonGroupRight, +QToolButton#desktop-ubButtonGroupRight +{ + margin-top: 1px; + margin-left: 0px; + padding: 5px; + height: 14px; +} + +QToolButton#ubButtonGroupLeft:checked, +QToolButton#desktop-ubButtonGroupLeft:checked +{ + padding-right: 4px; +} + +QToolButton#ubButtonGroupCenter:checked, +QToolButton#desktop-ubButtonGroupCenter:checked +{ + padding-right: 4px; + padding-left: 4px; +} + +QToolButton#ubButtonGroupRight:checked, +QToolButton#desktop-ubButtonGroupRight:checked +{ + padding-left: 4px; } QLabel#DockPaletteWidgetTitle { - color: #FFFFFF; + color: palette(bright-text); font-size : 18px; font-weight:bold; } +QWidget#UBPageNavigationWidget QLabel +{ + color: palette(window-text); + background-color: transparent; + border: none; + font-family: Arial; + font-weight: bold; + font-size: 20px; +} + QLineEdit#UBTGLineEdit, QLabel#UBTGMediaDropMeLabel { - background: white; - border: 1 solid #999999; + background: palette(base); + color: palette(text); + border: 1 solid palette(mid); border-radius : 10px; padding: 2px; } QComboBox#DockPaletteWidgetComboBox { - background: white; + background: palette(base); + color: palette(text); border-radius : 10px; padding: 2px; } QComboBox#DockPaletteWidgetComboBox:drop-down { - background: white; + background: palette(base); width:1px; height:1px; margin: 9px 5px 0px 0px; @@ -96,9 +175,9 @@ QComboBox#DockPaletteWidgetComboBox:drop-down QComboBox#DockPaletteWidgetComboBox::down-arrow { - image:url(:/images/down_arrow.png); - background:#BBBBBB; - border: 2px solid #999999; + image:url(:/images/down_arrow.svg); + background: palette(button); + border: 2px solid palette(mid); height:16px; width:16px; padding: 0px 0px 0px 0px; @@ -108,8 +187,8 @@ QComboBox#DockPaletteWidgetComboBox::down-arrow QPushButton#DockPaletteWidgetButton { - background-color : #DDDDDD; - color : #555555; + background-color : palette(button); + color : palette(button-text); border-radius : 6px; padding : 5px; font-weight : bold; @@ -118,138 +197,87 @@ QPushButton#DockPaletteWidgetButton QTextEdit#TeacherStudentBox { - background: white; - border: 2px solid #999999; + background: palette(base); + color: palette(text); + border: 2px solid palette(mid); border-radius: 10px; } QPushButton#DockPaletteWidgetButton::checked { - background-color: #BBBBBB; -} - -QScrollBar::vertical -{ - background:transparent; - margin: 23px 1px 25px 1px; -} - -QScrollBar::handle:vertical -{ - border: 2px solid #999999; - border-radius:6px; - background:#BBBBBB; - min-height:40px; -} - -QScrollBar::add-line:vertical -{ - image:url(:/images/down_arrow.png); - height:16px; - background:#BBBBBB; - border:2px solid #999999; - border-radius:6px; - margin: 1px 1px 4px 1px; - subcontrol-position:bottom; - subcontrol-origin:margin; -} - -QScrollBar::sub-line:vertical -{ - image:url(:/images/up_arrow.png); - height:16px; - background:#BBBBBB; - border:2px solid #999999; - border-radius:6px; - margin: 2px 1px 1px 1px; - subcontrol-position:top; - subcontrol-origin:margin; -} - -QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical -{ - background:transparent; -} - -QScrollBar::horizontal -{ - background:transparent; - margin: 1px 23px 1px 25px; -} - -QScrollBar::handle:horizontal -{ - border: 2px solid #999999; - border-radius:6px; - background:#BBBBBB; - min-width:40px; -} - -QScrollBar::add-line:horizontal -{ - image:url(:/images/right_arrow.png); - width:16px; - background:#BBBBBB; - border:2px solid #999999; - border-radius:6px; - margin: 1px 1px 1px 4px; - subcontrol-position:right; - subcontrol-origin:margin; -} - -QScrollBar::sub-line:horizontal -{ - image:url(:/images/left_arrow.png); - width:16px; - background:#BBBBBB; - border:2px solid #999999; - border-radius:6px; - margin: 1px 2px 1px 1px; - subcontrol-position:left; - subcontrol-origin:margin; -} - -QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal -{ - background:transparent; + background-color: palette(highlight); + color: palette(highlighted-text); + border: 1px solid palette(highlight); } QSlider::handle::horizontal { - background-color:#EEEEEE; + background-color: palette(button); margin-top:-5px; margin-bottom:-5px; height:20px; width:18px; border-radius:10px; - border:1px solid #555555; + border: 1px solid palette(dark); } QSlider::groove::horizontal { - background-color:#999999; + background-color: palette(mid); height:10px; border-radius:5px; - border:1px solid #555555; + border: 1px solid palette(dark); +} + +QTreeView::branch:has-siblings:!adjoins-item, +QTreeView::branch:!has-children:!has-siblings:adjoins-item, +QTreeView::branch:has-siblings:adjoins-item +{ + border-image: none; + image: none; + width: 12px; + height: 12px; +} + +QTreeView::branch:has-children:!has-siblings:closed, +QTreeView::branch:closed:has-children:has-siblings, +QTreeView::branch:selected:has-children:!has-siblings:closed, +QTreeView::branch:selected:closed:has-children:has-siblings +{ + border-image: none; + image: url(:/images/right_arrow.svg); + width: 12px; + height: 12px; +} + +QTreeView::branch:open:has-children:!has-siblings, +QTreeView::branch:open:has-children:has-siblings, +QTreeView::branch:selected:open:has-children:!has-siblings, +QTreeView::branch:selected:open:has-children:has-siblings +{ + border-image: none; + image: url(:/images/down_arrow.svg); + width: 12px; + height: 12px; } QLabel#UBTGEditionDocumentTitle { - color: black; + color: palette(text); font-size : 14px; font-weight:bold; } QLabel#UBTGPresentationDocumentTitle { - color: black; + color: palette(text); font-size : 12px; font-weight:bold; } QLabel#UBTGPageNumberLabel { - color: black; + color: palette(text); font-size : 12px; font-weight:bold; } @@ -257,13 +285,13 @@ QLabel#UBTGPageNumberLabel UBTGAdaptableText#UBTGEditionPageTitle, UBTGAdaptableText#UBTGEditionComment { - color: black; + color: palette(text); font-size : 12px; } UBTGAdaptableText#UBTGPresentationPageTitle { - color: black; + color: palette(text); font-size:16px; font-weight:bold; border : none; @@ -271,19 +299,67 @@ UBTGAdaptableText#UBTGPresentationPageTitle UBTGAdaptableText#UBTGPresentationComment { - color: black; + color: palette(text); font-size:12px; border : none; } QFrame#UBTGSeparator { - background-color: #cccccc; + background-color: palette(mid); } UBTGAdaptableText { - background-color: white; - border:1 solid #999999; + background-color: palette(base); + color: palette(text); + border: 1 solid palette(mid); border-radius : 10px; padding: 2px; } + +/* Features Action Bar and Widgets */ +QWidget#UBFeaturesActionBar { + background: palette(window); + border-radius: 10px; + border: 2px solid palette(mid); +} + +QLineEdit#FeaturesSearchBar { + background-color: palette(base); + color: palette(text); + border-radius: 10px; + padding: 2px; + border: 1px solid palette(mid); +} + +QPushButton#UBFeatureItemButton { + background-color: palette(button); + color: palette(button-text); + border-radius: 6px; + padding: 5px; + font-weight: bold; + font-size: 12px; + border: 1px solid palette(mid); +} + +QPushButton#UBFeatureItemButton:pressed, +QPushButton#UBFeatureItemButton:checked, +QPushButton#UBFeatureItemButton:default { + background-color: palette(highlight); + color: palette(highlighted-text); + border: 1px solid palette(highlight); +} + +QLabel#UBFeatureInfoLabel { + color: palette(mid); + font-size: 18px; + font-weight: bold; +} + +QLineEdit[invalid="true"] { + background: #FFB3C8; +} + +QLineEdit[invalid="false"] { + background: palette(base); +} diff --git a/resources/style/treeview-branch-closed.png b/resources/style/treeview-branch-closed.png deleted file mode 100644 index 2c566533e..000000000 Binary files a/resources/style/treeview-branch-closed.png and /dev/null differ diff --git a/resources/style/treeview-branch-open.png b/resources/style/treeview-branch-open.png deleted file mode 100644 index 58fa30625..000000000 Binary files a/resources/style/treeview-branch-open.png and /dev/null differ diff --git a/src/adaptors/CMakeLists.txt b/src/adaptors/CMakeLists.txt index ef91e1096..107edbafc 100644 --- a/src/adaptors/CMakeLists.txt +++ b/src/adaptors/CMakeLists.txt @@ -1,10 +1,6 @@ target_sources(${PROJECT_NAME} PRIVATE - UBCFFSubsetAdaptor.cpp - UBCFFSubsetAdaptor.h UBExportAdaptor.cpp UBExportAdaptor.h - UBExportCFF.cpp - UBExportCFF.h UBExportDocument.cpp UBExportDocument.h UBExportDocumentSetAdaptor.cpp @@ -17,8 +13,6 @@ target_sources(${PROJECT_NAME} PRIVATE UBExportWeb.h UBImportAdaptor.cpp UBImportAdaptor.h - UBImportCFF.cpp - UBImportCFF.h UBImportDocument.cpp UBImportDocument.h UBImportDocumentSetAdaptor.cpp @@ -29,6 +23,8 @@ target_sources(${PROJECT_NAME} PRIVATE UBImportPDF.h UBMetadataDcSubsetAdaptor.cpp UBMetadataDcSubsetAdaptor.h + UBPageMapper.cpp + UBPageMapper.h UBSvgSubsetAdaptor.cpp UBSvgSubsetAdaptor.h UBThumbnailAdaptor.cpp diff --git a/src/adaptors/UBCFFSubsetAdaptor.cpp b/src/adaptors/UBCFFSubsetAdaptor.cpp deleted file mode 100644 index bec770dcf..000000000 --- a/src/adaptors/UBCFFSubsetAdaptor.cpp +++ /dev/null @@ -1,1570 +0,0 @@ -/* - * Copyright (C) 2015-2022 Département de l'Instruction Publique (DIP-SEM) - * - * Copyright (C) 2013 Open Education Foundation - * - * 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 - * 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, - * 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 OpenBoard. If not, see . - */ - - - -#include -#include -#include -#include -#include -#include - -#include "core/UBApplication.h" -#include "core/UBDisplayManager.h" -#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/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, std::shared_ptr 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(std::shared_ptrproxy, 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(' '), UB::SplitBehavior::SkipEmptyParts); - - foreach(const QString sPoint, ts) { - QStringList sCoord = sPoint.split(QLatin1Char(','), UB::SplitBehavior::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(); - 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(' '), - UB::SplitBehavior::SkipEmptyParts); - - foreach(const QString sPoint, ts) { - QStringList sCoord = sPoint.split(QLatin1Char(','), UB::SplitBehavior::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 lines - //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. / UBApplication::displayManager->logicalDpi(ScreenRole::Control); - - 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()) { -#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)) - format.setFontFamilies(QStringList(fontFamilyText)); -#else - format.setFontFamily(fontFamilyText); -#endif - } - 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().windowText().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).boundingRect(element.text()).width() + 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().windowText().color()); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)) - textFormat.setFontFamilies(QStringList("Arial")); -#else - textFormat.setFontFamily("Arial"); -#endif - 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(); - } - - - - const auto keys = strokesGroupsContainer.keys(); - for (const QString &key : keys) - { - 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; - pStrokesGroup = nullptr; - } - - 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); - std::shared_ptr 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%)' - static const QRegularExpression regexp(QRegularExpression::anchoredPattern("rgb\\(([0-9]+%{0,1}),([0-9]+%{0,1}),([0-9]+%{0,1})\\)")); - QRegularExpressionMatch match = regexp.match(clrString); - if (match.hasMatch()) - { - if (match.lastCapturedIndex() == 3 && match.capturedTexts().at(0).length() == clrString.length()) - { - int r = match.capturedTexts().at(1).toInt(); - if (match.capturedTexts().at(1).indexOf("%") != -1) - r = r * 255 / 100; - int g = match.capturedTexts().at(2).toInt(); - if (match.capturedTexts().at(2).indexOf("%") != -1) - g = g * 255 / 100; - int b = match.capturedTexts().at(3).toInt(); - if (match.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(" ", UB::SplitBehavior::SkipEmptyParts)) - { - //check pattern for strings like 'rotate(10)' - static const QRegularExpression rotate1(QRegularExpression::anchoredPattern("rotate\\( *([-+]{0,1}[0-9]*\\.{0,1}[0-9]*) *\\)")); - QRegularExpressionMatch match = rotate1.match(trStr); - if (match.hasMatch()) { - angle = match.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)' - static const QRegularExpression rotate3(QRegularExpression::anchoredPattern("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]*) *\\)")); - match = rotate3.match(trStr); - if (match.hasMatch()) { - angle = match.capturedTexts().at(1).toDouble(); - dxr = match.capturedTexts().at(2).toDouble(); - dyr = match.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)' - static const QRegularExpression translate2(QRegularExpression::anchoredPattern("translate\\( *([-+]{0,1}[0-9]*\\.{0,1}[0-9]*) *,*([-+]{0,1}[0-9]*\\.{0,1}[0-9]*)*\\)")); - match = translate2.match(trStr); - if (match.hasMatch()) { - dx = match.capturedTexts().at(1).toDouble(); - dy = match.capturedTexts().at(2).toDouble(); - tr.translate(dx,dy); - continue; - } - } - return tr; -} - -bool UBCFFSubsetAdaptor::UBCFFSubsetReader::getViewBoxDimenstions(const QString& viewBox) -{ - QStringList capturedTexts = viewBox.split(" ", UB::SplitBehavior::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(UBApplication::displayManager->logicalDpi(ScreenRole::Control)); - 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 deleted file mode 100644 index dc268e5cd..000000000 --- a/src/adaptors/UBCFFSubsetAdaptor.h +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (C) 2015-2022 Département de l'Instruction Publique (DIP-SEM) - * - * Copyright (C) 2013 Open Education Foundation - * - * 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 - * 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, - * 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 OpenBoard. 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, std::shared_ptr pDocument); - -private: - class UBCFFSubsetReader - { - public: - UBCFFSubsetReader(std::shared_ptrproxy, QFile *content); - ~UBCFFSubsetReader(); - - std::shared_ptrmProxy; - QString pwdContent; - - bool parse(); - - private: - QString mTempFilePath; - std::shared_ptr 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/UBExportCFF.cpp b/src/adaptors/UBExportCFF.cpp deleted file mode 100644 index 65016fff2..000000000 --- a/src/adaptors/UBExportCFF.cpp +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2015-2022 Département de l'Instruction Publique (DIP-SEM) - * - * Copyright (C) 2013 Open Education Foundation - * - * 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 - * 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, - * 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 OpenBoard. 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(std::shared_ptr 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/UBExportDocument.cpp b/src/adaptors/UBExportDocument.cpp index 12fb98cfb..2c35b7566 100644 --- a/src/adaptors/UBExportDocument.cpp +++ b/src/adaptors/UBExportDocument.cpp @@ -31,13 +31,15 @@ #include "frameworks/UBPlatformUtils.h" +#include "adaptors/UBPageMapper.h" + #include "core/UBDocumentManager.h" #include "core/UBApplication.h" +#include "core/UBSettings.h" #include "document/UBDocumentProxy.h" #include "document/UBDocumentController.h" - -#include "globals/UBGlobals.h" +#include "document/UBDocumentToc.h" #ifdef Q_OS_OSX #include @@ -78,8 +80,18 @@ bool UBExportDocument::persistsDocument(std::shared_ptr pDocume QDir documentDir = QDir(pDocumentProxy->persistencePath()); + // try to load a TOC for mapping and check version number + UBDocumentToc toc{pDocumentProxy->persistencePath()}; + std::unique_ptr mapper{nullptr}; + const auto version = QVersionNumber::fromString(pDocumentProxy->metaData(UBSettings::documentVersion).toString()); + + if (toc.load() && version >= QVersionNumber::fromString(UBSettings::currentFileVersion)) + { + mapper = std::unique_ptr{new UBPageMapper{pDocumentProxy->persistencePath(), &toc}}; + } + QuaZipFile outFile(&zip); - UBFileSystemUtils::compressDirInZip(documentDir, "", &outFile, true, this); + UBFileSystemUtils::compressDirInZip(documentDir, "", &outFile, true, mapper.get(), this); zip.close(); diff --git a/src/adaptors/UBExportDocumentSetAdaptor.cpp b/src/adaptors/UBExportDocumentSetAdaptor.cpp index 752b5ea38..70c49d38f 100644 --- a/src/adaptors/UBExportDocumentSetAdaptor.cpp +++ b/src/adaptors/UBExportDocumentSetAdaptor.cpp @@ -28,6 +28,8 @@ #include "UBExportDocumentSetAdaptor.h" #include "UBExportDocument.h" +#include "adaptors/UBPageMapper.h" + #include "frameworks/UBPlatformUtils.h" #include "core/UBDocumentManager.h" @@ -35,10 +37,9 @@ #include "document/UBDocumentProxy.h" #include "document/UBDocumentController.h" +#include "document/UBDocumentToc.h" -#include "globals/UBGlobals.h" #include "core/UBPersistenceManager.h" -#include "core/UBForeignObjectsHandler.h" #ifdef Q_OS_OSX #include @@ -165,18 +166,22 @@ bool UBExportDocumentSetAdaptor::addDocumentToZip(const QModelIndex &pIndex, UBD std::shared_ptrpDocumentProxy = 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()); + + // try to load a TOC for mapping and check version number + UBDocumentToc toc{pDocumentProxy->persistencePath()}; + std::unique_ptr mapper{nullptr}; + const auto version = QVersionNumber::fromString(pDocumentProxy->metaData(UBSettings::documentVersion).toString()); + + if (toc.load() && version >= QVersionNumber::fromString(UBSettings::currentFileVersion)) + { + mapper = std::unique_ptr{new UBPageMapper{pDocumentProxy->persistencePath(), &toc}}; + } + QuaZipFile zipFile(&zip); - UBFileSystemUtils::compressDirInZip(documentDir, QFileInfo(documentPath).fileName() + "/", &zipFile, false); + UBFileSystemUtils::compressDirInZip(documentDir, QFileInfo(documentPath).fileName() + "/", &zipFile, false, mapper.get()); if(zip.getZipError() != 0) { diff --git a/src/adaptors/UBExportFullPDF.cpp b/src/adaptors/UBExportFullPDF.cpp index 71d22b52c..8c8ef936c 100644 --- a/src/adaptors/UBExportFullPDF.cpp +++ b/src/adaptors/UBExportFullPDF.cpp @@ -40,14 +40,12 @@ #include "core/UBPersistenceManager.h" #include "domain/UBGraphicsScene.h" -#include "domain/UBGraphicsSvgItem.h" #include "domain/UBGraphicsPDFItem.h" +#include "document/UBDocument.h" #include "document/UBDocumentProxy.h" #include "document/UBDocumentController.h" -#include "pdf/GraphicsPDFItem.h" - #include "UBExportPDF.h" #include @@ -79,7 +77,9 @@ UBExportFullPDF::~UBExportFullPDF() void UBExportFullPDF::saveOverlayPdf(std::shared_ptr pDocumentProxy, const QString& filename) { - if (!pDocumentProxy || filename.length() == 0 || pDocumentProxy->pageCount() == 0) + auto document = UBDocument::getDocument(pDocumentProxy); + + if (!document || filename.length() == 0 || document->pageCount() == 0) return; /* @@ -105,12 +105,18 @@ void UBExportFullPDF::saveOverlayPdf(std::shared_ptr pDocumentP QPainter* pdfPainter = 0; - for(int pageIndex = 0 ; pageIndex < pDocumentProxy->pageCount(); pageIndex++) + for(int pageIndex = 0 ; pageIndex < document->pageCount(); pageIndex++) { - std::shared_ptr scene = UBPersistenceManager::persistenceManager()->loadDocumentScene(pDocumentProxy, pageIndex); + std::shared_ptr scene = document->loadScene(pageIndex); + + if (!scene) + { + continue; + } + // set background according to PDF export settings bool isDark = scene->isDarkBackground(); - UBPageBackground pageBackground = scene->pageBackground(); + const auto sceneBackground = scene->background(); bool exportDark = isDark && UBSettings::settings()->exportBackgroundColor->get().toBool(); @@ -148,15 +154,15 @@ void UBExportFullPDF::saveOverlayPdf(std::shared_ptr pDocumentP if (sceneHasPDFBackground) { scene->setDrawingMode(true); - scene->setBackground(false, UBPageBackground::plain); + scene->setSceneBackground(false, nullptr); } else if (UBSettings::settings()->exportBackgroundGrid->get().toBool()) { - scene->setBackground(exportDark, pageBackground); + scene->setSceneBackground(exportDark, sceneBackground); } else { - scene->setBackground(exportDark, UBPageBackground::plain); + scene->setSceneBackground(exportDark, nullptr); } //render to PDF @@ -168,7 +174,7 @@ void UBExportFullPDF::saveOverlayPdf(std::shared_ptr pDocumentP //restore background state scene->setDrawingMode(false); - scene->setBackground(isDark, pageBackground); + scene->setSceneBackground(isDark, sceneBackground); } if (pdfPainter) delete pdfPainter; @@ -219,11 +225,18 @@ bool UBExportFullPDF::persistsDocument(std::shared_ptr pDocumen // factor between scene coordinates and PDF coordinates double dpiScale = 72. / pDocumentProxy->pageDpi(); - int existingPageCount = pDocumentProxy->pageCount(); + auto document = UBDocument::getDocument(pDocumentProxy); + int existingPageCount = document->pageCount(); for(int pageIndex = 0 ; pageIndex < existingPageCount; pageIndex++) { - std::shared_ptr scene = UBPersistenceManager::persistenceManager()->loadDocumentScene(pDocumentProxy, pageIndex); + std::shared_ptr scene = document->loadScene(pageIndex); + + if (!scene) + { + continue; + } + UBGraphicsPDFItem *pdfItem = qgraphicsitem_cast(scene->backgroundObject()); if (pdfItem) diff --git a/src/adaptors/UBExportPDF.cpp b/src/adaptors/UBExportPDF.cpp index 3c5155429..c9c6fee05 100644 --- a/src/adaptors/UBExportPDF.cpp +++ b/src/adaptors/UBExportPDF.cpp @@ -41,14 +41,11 @@ #include "core/UBPersistenceManager.h" #include "domain/UBGraphicsScene.h" -#include "domain/UBGraphicsSvgItem.h" -#include "domain/UBGraphicsPDFItem.h" +#include "document/UBDocument.h" #include "document/UBDocumentProxy.h" #include "document/UBDocumentController.h" -#include "pdf/GraphicsPDFItem.h" - #include "core/memcheck.h" UBExportPDF::UBExportPDF(QObject *parent) @@ -97,26 +94,33 @@ bool UBExportPDF::persistsDocument(std::shared_ptr pDocumentPro QPainter pdfPainter; bool painterNeedsBegin = true; - int existingPageCount = pDocumentProxy->pageCount(); + auto document = UBDocument::getDocument(pDocumentProxy); + int existingPageCount = document->pageCount(); for(int pageIndex = 0 ; pageIndex < existingPageCount; pageIndex++) { - std::shared_ptr scene = UBPersistenceManager::persistenceManager()->loadDocumentScene(pDocumentProxy, pageIndex); + std::shared_ptr scene = document->loadScene(pageIndex); + + if (!scene) + { + continue; + } + UBApplication::showMessage(tr("Exporting page %1 of %2").arg(pageIndex + 1).arg(existingPageCount)); // set background to white, no crossing for PDF output bool isDark = scene->isDarkBackground(); - UBPageBackground pageBackground = scene->pageBackground(); + const auto pageBackground = scene->background(); bool exportDark = isDark && UBSettings::settings()->exportBackgroundColor->get().toBool(); if (UBSettings::settings()->exportBackgroundGrid->get().toBool()) { - scene->setBackground(exportDark, pageBackground); + scene->setSceneBackground(exportDark, pageBackground); } else { - scene->setBackground(exportDark, UBPageBackground::plain); + scene->setSceneBackground(exportDark, nullptr); } // pageSize is the output PDF page size; it is set to equal the scene's boundary size; if the contents @@ -146,7 +150,7 @@ bool UBExportPDF::persistsDocument(std::shared_ptr pDocumentPro scene->setRenderingQuality(UBItem::RenderingQualityNormal, UBItem::CacheAllowed); // Restore background state - scene->setBackground(isDark, pageBackground); + scene->setSceneBackground(isDark, pageBackground); } if(!painterNeedsBegin) diff --git a/src/adaptors/UBImportCFF.cpp b/src/adaptors/UBImportCFF.cpp deleted file mode 100644 index 1af076f39..000000000 --- a/src/adaptors/UBImportCFF.cpp +++ /dev/null @@ -1,302 +0,0 @@ -/* - * Copyright (C) 2015-2022 Département de l'Instruction Publique (DIP-SEM) - * - * Copyright (C) 2013 Open Education Foundation - * - * 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 - * 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, - * 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 OpenBoard. 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 -#ifdef Q_OS_OSX - #include - #include - #include -#else - #include "quazip.h" - #include "quazipfile.h" - #include "quazipfileinfo.h" -#endif -//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(std::shared_ptr 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 - std::shared_ptr destDocument = std::make_shared(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()); - UBApplication::showMessage(tr("Import successful.")); - return true; - } - else - { - UBFileSystemUtils::deleteDir(destDocument->persistencePath()); - 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; -} - - -std::shared_ptr 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 - std::shared_ptr destDocument = std::make_shared(UBPersistenceManager::persistenceManager()->generateUniqueDocumentPath()); - QDir dir; - dir.mkdir(destDocument->persistencePath()); - if (pGroup.length() > 0) - destDocument->setMetaData(UBSettings::documentGroupName, pGroup); - if (!fi.baseName().isEmpty()) - destDocument->setMetaData(UBSettings::documentName, fi.baseName()); - - destDocument->setMetaData(UBSettings::documentVersion, UBSettings::currentFileVersion); - destDocument->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(QDateTime::currentDateTime())); - - std::shared_ptr newDocument = nullptr; - //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.")); - } - - if (documentRootFolder.length() != 0) - UBFileSystemUtils::deleteDir(documentRootFolder); - return newDocument; - } -} diff --git a/src/adaptors/UBImportCFF.h b/src/adaptors/UBImportCFF.h deleted file mode 100644 index a8c058832..000000000 --- a/src/adaptors/UBImportCFF.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2015-2022 Département de l'Instruction Publique (DIP-SEM) - * - * Copyright (C) 2013 Open Education Foundation - * - * 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 - * 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, - * 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 OpenBoard. 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(std::shared_ptr pDocument, const QFile& pFile); - virtual std::shared_ptr importFile(const QFile& pFile, const QString& pGroup); - - private: - QString expandFileToDir(const QFile& pZipFile, const QString& pDir); -}; - -#endif // UBIMPORTCFF_H diff --git a/src/adaptors/UBMetadataDcSubsetAdaptor.cpp b/src/adaptors/UBMetadataDcSubsetAdaptor.cpp index 7fe4ae0f1..236b4a002 100644 --- a/src/adaptors/UBMetadataDcSubsetAdaptor.cpp +++ b/src/adaptors/UBMetadataDcSubsetAdaptor.cpp @@ -117,7 +117,6 @@ void UBMetadataDcSubsetAdaptor::persist(std::shared_ptr proxy) // introduced in UB 4.2 xmlWriter.writeTextElement(nsDc, "identifier", proxy->metaData(UBSettings::documentIdentifer).toString()); - xmlWriter.writeTextElement(UBSettings::uniboardDocumentNamespaceUri, "version", UBSettings::currentFileVersion); QString width = QString::number(proxy->defaultDocumentSize().width()); QString height = QString::number(proxy->defaultDocumentSize().height()); xmlWriter.writeTextElement(UBSettings::uniboardDocumentNamespaceUri, "size", QString("%1x%2").arg(width).arg(height)); @@ -125,6 +124,9 @@ void UBMetadataDcSubsetAdaptor::persist(std::shared_ptr proxy) // introduced in UB 4.4 xmlWriter.writeTextElement(UBSettings::uniboardDocumentNamespaceUri, "updated-at", UBStringUtils::toUtcIsoDateTime(QDateTime::currentDateTimeUtc())); + // write as last element to cope with parsing problem + xmlWriter.writeTextElement(UBSettings::uniboardDocumentNamespaceUri, "version", UBSettings::currentFileVersion); + xmlWriter.writeEndElement(); //dc:Description xmlWriter.writeEndElement(); //RDF @@ -157,6 +159,7 @@ QMap UBMetadataDcSubsetAdaptor::load(QString pPath) } QXmlStreamReader xml(&file); + QString docVersion = "4.1"; // untagged doc version 4.1 while (!xml.atEnd()) { @@ -164,7 +167,6 @@ QMap UBMetadataDcSubsetAdaptor::load(QString pPath) if (xml.isStartElement()) { - QString docVersion = "4.1"; // untagged doc version 4.1 QString name = xml.name().toString(); if (name == "title") @@ -225,7 +227,6 @@ QMap UBMetadataDcSubsetAdaptor::load(QString pPath) metadata.insert(UBSettings::documentUpdatedAt, xml.readElementText()); updatedAtFound = true; } - metadata.insert(UBSettings::documentVersion, docVersion); } if (xml.hasError()) @@ -234,6 +235,7 @@ QMap UBMetadataDcSubsetAdaptor::load(QString pPath) } } + metadata.insert(UBSettings::documentVersion, docVersion); file.close(); } diff --git a/src/adaptors/UBPageMapper.cpp b/src/adaptors/UBPageMapper.cpp new file mode 100644 index 000000000..79e01637d --- /dev/null +++ b/src/adaptors/UBPageMapper.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2015-2025 Département de l'Instruction Publique (DIP-SEM) + * + * This file is part of OpenBoard. + * + * OpenBoard 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, + * 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 OpenBoard. If not, see . + */ + + +#include "UBPageMapper.h" + +#include "core/UBPersistenceManager.h" +#include "document/UBDocumentToc.h" + +UBPageMapper::UBPageMapper(const QString& documentPath, UBDocumentToc* toc) + : mDocumentPath{documentPath} + , mSourceToc{toc} +{ + if (!mTempDir.isValid()) + { + qWarning() << "UBPageMapper: unable to create temporary directory"; + return; + } + + auto pm = UBPersistenceManager::persistenceManager(); + mMappedToc = std::unique_ptr{new UBDocumentToc(*mSourceToc, mTempDir.path())}; + + // create page map and target TOC + for (int index = 0; index < mSourceToc->pageCount(); ++index) + { + const auto id = mSourceToc->pageId(index); + + // remove the leading "/" from the scene and thumbnail file names + mFileMap[pm->sceneFilenameForId(id).mid(1)] = pm->sceneFilenameForId(index).mid(1); + mFileMap[pm->thumbnailFilenameForId(id).mid(1)] = pm->thumbnailFilenameForId(index).mid(1); + + // set id to be same as index in the mapped TOC + mMappedToc->setPageId(index, index); + } + + mMappedToc->save(); +} + +UBPageMapper::MapResult UBPageMapper::map(const QString& filename) const +{ + if (!mMappedToc) + { + return {QFileInfo{mDocumentPath + "/" + filename}, filename}; + } + + if (filename.startsWith("toc.")) + { + // return the mapped TOC + return {QFileInfo(mTempDir.path() + "/" + filename), filename}; + } + + const auto mappedFilename = mFileMap.value(filename); + + if (!mappedFilename.isEmpty()) + { + // return the mapped file + return {QFileInfo{mDocumentPath + "/" + filename}, mappedFilename}; + } + + // return unmapped file + return {QFileInfo{mDocumentPath + "/" + filename}, filename}; +} diff --git a/src/adaptors/UBPageMapper.h b/src/adaptors/UBPageMapper.h new file mode 100644 index 000000000..f4016c218 --- /dev/null +++ b/src/adaptors/UBPageMapper.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2015-2025 Département de l'Instruction Publique (DIP-SEM) + * + * This file is part of OpenBoard. + * + * OpenBoard 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, + * 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 OpenBoard. If not, see . + */ + + +#pragma once + +#include +#include +#include + +#include + +// forward +class UBDocumentToc; + +/** + * @brief The UBPageMapper maps a file name which should be saved in a ZIP + * file for export to an input file and an output file name. + * + * This function is used to sequentially renumber pages on export. Page file + * names are created according to the page sequence in the TOC. Additionally + * an updated TOC is created which reflects the renamed page files. + */ +class UBPageMapper +{ +public: + struct MapResult + { + QFileInfo input; ///< file for reading + QString output; ///< file name for the output file in the ZIP archive + }; + +public: + UBPageMapper(const QString& documentPath, UBDocumentToc* toc); + + MapResult map(const QString& filename) const; + +private: + const QString mDocumentPath; + const UBDocumentToc* mSourceToc{nullptr}; + std::unique_ptr mMappedToc; + QHash mFileMap; + QTemporaryDir mTempDir; +}; diff --git a/src/adaptors/UBSvgSubsetAdaptor.cpp b/src/adaptors/UBSvgSubsetAdaptor.cpp index 5f2a30840..03cf4e22d 100644 --- a/src/adaptors/UBSvgSubsetAdaptor.cpp +++ b/src/adaptors/UBSvgSubsetAdaptor.cpp @@ -51,6 +51,8 @@ #include "domain/UBGraphicsGroupContainerItemDelegate.h" #include "domain/UBItem.h" +#include "gui/UBBackgroundRuling.h" +#include "gui/UBBackgroundManager.h" #include "tools/UBGraphicsRuler.h" #include "tools/UBGraphicsAxes.h" #include "tools/UBGraphicsCompass.h" @@ -63,7 +65,6 @@ #include "board/UBBoardView.h" #include "board/UBBoardController.h" -#include "board/UBDrawingController.h" #include "board/UBBoardPaletteManager.h" #include "frameworks/UBFileSystemUtils.h" @@ -145,26 +146,9 @@ static bool itemZIndexComp(const QGraphicsItem* item1, } -void UBSvgSubsetAdaptor::upgradeScene(std::shared_ptr proxy, const int pageIndex) +QDomDocument UBSvgSubsetAdaptor::loadSceneDocument(std::shared_ptr proxy, const int pageId) { - //4.2 - QDomDocument doc = loadSceneDocument(proxy, pageIndex); - QDomElement elSvg = doc.documentElement(); // SVG tag - QString ubVersion = elSvg.attributeNS(UBSettings::uniboardDocumentNamespaceUri, "version", "4.1"); // default to 4.1 - - if (ubVersion.startsWith("4.1") || ubVersion.startsWith("4.2") || ubVersion.startsWith("4.3")) - { - // migrate to 4.2.1 (or latter) - std::shared_ptr scene = loadScene(proxy, pageIndex); - scene->setModified(true); - persistScene(proxy, scene, pageIndex); - } -} - - -QDomDocument UBSvgSubsetAdaptor::loadSceneDocument(std::shared_ptr proxy, const int pPageIndex) -{ - QString fileName = proxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.svg",pPageIndex); + QString fileName = proxy->persistencePath() + UBPersistenceManager::persistenceManager()->sceneFilenameForId(pageId); QFile file(fileName); QDomDocument doc("page"); @@ -177,7 +161,11 @@ QDomDocument UBSvgSubsetAdaptor::loadSceneDocument(std::shared_ptr= QT_VERSION_CHECK(6, 5, 0)) + doc.setContent(&file, QDomDocument::ParseOption::UseNamespaceProcessing); +#else doc.setContent(&file, true); +#endif file.close(); } @@ -185,54 +173,63 @@ QDomDocument UBSvgSubsetAdaptor::loadSceneDocument(std::shared_ptr proxy, const int pageIndex, QUuid pUuid) +void UBSvgSubsetAdaptor::setSceneUuid(std::shared_ptr proxy, const int pageId, QUuid pUuid) { - QString fileName = proxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.svg",pageIndex); + const QString path = proxy->persistencePath() + UBPersistenceManager::persistenceManager()->sceneFilenameForId(pageId); + replicateScene(path, path, pUuid); +} - QFile file(fileName); +void UBSvgSubsetAdaptor::replicateScene(const QString& sourcePath, const QString& targetPath, QUuid uuid) +{ + QFile fromFile(sourcePath); - if (!file.exists() || !file.open(QIODevice::ReadOnly)) + if (!fromFile.exists() || !fromFile.open(QIODevice::ReadOnly)) + { return; + } - QTextStream textReadStream(&file); + QTextStream textReadStream(&fromFile); QString xmlContent = textReadStream.readAll(); - int uuidIndex = xmlContent.indexOf("uuid"); + fromFile.close(); + const int uuidIndex = xmlContent.indexOf("uuid"); + if (-1 == uuidIndex) { - qWarning() << "Cannot read UUID from file" << fileName << "to set new UUID"; - file.close(); + qWarning() << "Cannot read UUID from file" << sourcePath << "to set new UUID"; return; } + int quoteStartIndex = xmlContent.indexOf('"', uuidIndex); + if (-1 == quoteStartIndex) { - qWarning() << "Cannot read UUID from file" << fileName << "to set new UUID"; - file.close(); + qWarning() << "Cannot read UUID from file" << sourcePath << "to set new UUID"; return; } + int quoteEndIndex = xmlContent.indexOf('"', quoteStartIndex + 1); + if (-1 == quoteEndIndex) { - qWarning() << "Cannot read UUID from file" << fileName << "to set new UUID"; - file.close(); + qWarning() << "Cannot read UUID from file" << sourcePath << "to set new UUID"; return; } - file.close(); - QString newXmlContent = xmlContent.left(quoteStartIndex + 1); - newXmlContent.append(UBStringUtils::toCanonicalUuid(pUuid)); + newXmlContent.append(uuid.toString(QUuid::WithoutBraces)); newXmlContent.append(xmlContent.right(xmlContent.length() - quoteEndIndex)); - if (file.open(QIODevice::WriteOnly | QIODevice::Truncate)) + QFile toFile(targetPath); + + if (toFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { - QTextStream textWriteStream(&file); + QTextStream textWriteStream(&toFile); textWriteStream << newXmlContent; - file.close(); + toFile.close(); } else { - qWarning() << "Cannot open file" << fileName << "to write UUID"; + qWarning() << "Cannot open file" << targetPath << "to write UUID"; } } @@ -242,35 +239,9 @@ QString UBSvgSubsetAdaptor::uniboardDocumentNamespaceUriFromVersion(int mFileVer } -std::shared_ptr UBSvgSubsetAdaptor::loadScene(std::shared_ptr proxy, const int pageIndex) +QByteArray UBSvgSubsetAdaptor::loadSceneAsText(std::shared_ptr proxy, const int pageId) { - UBApplication::showMessage(QObject::tr("Loading scene (%1/%2)").arg(pageIndex+1).arg(proxy->pageCount())); - QString fileName = proxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.svg", pageIndex); - qInfo() << "loading scene. Filename is : " << fileName; - QFile file(fileName); - - if (file.exists()) - { - if (!file.open(QIODevice::ReadOnly)) - { - qWarning() << "Cannot open file " << fileName << " for reading ..."; - return 0; - } - - std::shared_ptr scene = loadScene(proxy, file.readAll()); - - file.close(); - - return scene; - } - - return 0; -} - - -QByteArray UBSvgSubsetAdaptor::loadSceneAsText(std::shared_ptr proxy, const int pageIndex) -{ - QString fileName = proxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.svg", pageIndex); + QString fileName = proxy->persistencePath() + UBPersistenceManager::persistenceManager()->sceneFilenameForId(pageId); qDebug() << fileName; QFile file(fileName); @@ -291,9 +262,9 @@ QByteArray UBSvgSubsetAdaptor::loadSceneAsText(std::shared_ptr } -QUuid UBSvgSubsetAdaptor::sceneUuid(std::shared_ptr proxy, const int pageIndex) +QUuid UBSvgSubsetAdaptor::sceneUuid(std::shared_ptr proxy, const int pageId) { - QString fileName = proxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.svg", pageIndex); + QString fileName = proxy->persistencePath() + UBPersistenceManager::persistenceManager()->sceneFilenameForId(pageId); QFile file(fileName); @@ -337,6 +308,64 @@ QUuid UBSvgSubsetAdaptor::sceneUuid(std::shared_ptr proxy, cons return uuid; } +QUuid UBSvgSubsetAdaptor::sceneUuid(const QString& xmlContent) +{ + const int uuidIndex = xmlContent.indexOf("uuid"); + + if (-1 == uuidIndex) + { + qWarning() << "Cannot find uuid attribute"; + return {}; + } + + int quoteStartIndex = xmlContent.indexOf('"', uuidIndex); + + if (-1 == quoteStartIndex) + { + qWarning() << "Cannot find start of uuid attribute value"; + return {}; + } + + int quoteEndIndex = xmlContent.indexOf('"', quoteStartIndex + 1); + + if (-1 == quoteEndIndex) + { + qWarning() << "Cannot find end of uuid attribute value"; + return {}; + } + + return QUuid{xmlContent.mid(quoteStartIndex + 1, quoteEndIndex - quoteStartIndex - 1)}; +} + +QVersionNumber UBSvgSubsetAdaptor::sceneVersion(const QString& xmlContent) +{ + const int versionIndex = xmlContent.indexOf("ub:version"); + + if (-1 == versionIndex) + { + qWarning() << "Cannot find ub:version attribute"; + return {}; + } + + int quoteStartIndex = xmlContent.indexOf('"', versionIndex); + + if (-1 == quoteStartIndex) + { + qWarning() << "Cannot find start of ub:version attribute value"; + return {}; + } + + int quoteEndIndex = xmlContent.indexOf('"', quoteStartIndex + 1); + + if (-1 == quoteEndIndex) + { + qWarning() << "Cannot find end of ub:version attribute value"; + return {}; + } + + return QVersionNumber::fromString(xmlContent.mid(quoteStartIndex + 1, quoteEndIndex - quoteStartIndex - 1)); +} + std::shared_ptr UBSvgSubsetAdaptor::loadScene(std::shared_ptr proxy, const QByteArray& pArray) { @@ -344,9 +373,10 @@ std::shared_ptr UBSvgSubsetAdaptor::loadScene(std::shared_ptr UBSvgSubsetAdaptor::prepareLoadingScene(std::shared_ptr proxy, const int pageIndex) +std::shared_ptr UBSvgSubsetAdaptor::prepareLoadingScene(std::shared_ptr proxy, + const int pageId, std::optional xmlContent) { - const auto fileContent = loadSceneAsText(proxy, pageIndex); + const auto fileContent = xmlContent.value_or(loadSceneAsText(proxy, pageId)); auto context = std::make_shared(proxy, fileContent); return context; } @@ -481,6 +511,7 @@ void UBSvgSubsetAdaptor::UBSvgSubsetReader::processElement() bool darkBackground = false; bool crossedBackground = false; bool ruledBackground = false; + bool intermediateLines = false; auto ubDarkBackground = mXmlReader.attributes().value(mNamespaceUri, "dark-background"); @@ -500,42 +531,30 @@ void UBSvgSubsetAdaptor::UBSvgSubsetReader::processElement() mScene->setBackgroundGridSize(gridSize); } - if (crossedBackground) { - - auto ubIntermediateLines = mXmlReader.attributes().value(mNamespaceUri, "intermediate-lines"); - - if (!ubIntermediateLines.isNull()) { - bool intermediateLines = ubIntermediateLines.toInt(); - - mScene->setIntermediateLines(intermediateLines); - } - } - auto ubRuledBackground = mXmlReader.attributes().value(mNamespaceUri, "ruled-background"); if (!ubRuledBackground.isNull()) ruledBackground = (ubRuledBackground.toString() == xmlTrue); - if (ruledBackground && !crossedBackground) { // if for some reason both are true, the background will be a grid + if (ruledBackground || crossedBackground) { auto ubIntermediateLines = mXmlReader.attributes().value(mNamespaceUri, "intermediate-lines"); if (!ubIntermediateLines.isNull()) { - bool intermediateLines = ubIntermediateLines.toInt(); - - mScene->setIntermediateLines(intermediateLines); + intermediateLines = ubIntermediateLines.toInt(); } } - UBPageBackground bg; - if (crossedBackground) - bg = UBPageBackground::crossed; - else if (ruledBackground) - bg = UBPageBackground::ruled; - else - bg = UBPageBackground::plain; + const UBBackgroundRuling* background{nullptr}; + + // guess background pattern from attributes + if (crossedBackground || ruledBackground) + { + const auto bgManager = UBApplication::boardController->backgroundManager(); + background = bgManager->guessBackground(crossedBackground, ruledBackground, intermediateLines); + } - mScene->setBackground(darkBackground, bg); + mScene->setSceneBackground(darkBackground, background); auto pageNominalSize = mXmlReader.attributes().value(mNamespaceUri, "nominal-size"); if (!pageNominalSize.isNull()) @@ -557,6 +576,18 @@ void UBSvgSubsetAdaptor::UBSvgSubsetReader::processElement() } } + else if (name == "background") + { + UBBackgroundRuling bg; + bg.parseXml(mXmlReader); + + if (bg.isValid()) + { + const auto bgManager = UBApplication::boardController->backgroundManager(); + bgManager->addBackground(bg); + mScene->setSceneBackground(mScene->isDarkBackground(), bgManager->background(bg.uuid())); + } + } else if (name == "g") { strokesGroup = new UBGraphicsStrokesGroup(); @@ -894,22 +925,6 @@ void UBSvgSubsetAdaptor::UBSvgSubsetReader::processElement() currentWidget = 0; } } - else if (src.contains(".wdgt")) // NOTE @letsfindaway obsolete - { - UBGraphicsAppleWidgetItem* appleWidgetItem = graphicsAppleWidgetFromSvg(); - if (appleWidgetItem) - { - appleWidgetItem->setFlag(QGraphicsItem::ItemIsSelectable, true); - - appleWidgetItem->resize(foreignObjectWidth, foreignObjectHeight); - - mScene->addItem(appleWidgetItem); - - appleWidgetItem->show(); - - currentWidget = appleWidgetItem; - } - } else if (src.contains(".wgt")) { UBGraphicsW3CWidgetItem* w3cWidgetItem = graphicsW3CWidgetFromSvg(); @@ -1177,17 +1192,17 @@ QGraphicsItem *UBSvgSubsetAdaptor::UBSvgSubsetReader::readElementFromGroup() return result; } -void UBSvgSubsetAdaptor::persistScene(std::shared_ptr proxy, std::shared_ptr pScene, const int pageIndex) +void UBSvgSubsetAdaptor::persistScene(std::shared_ptr proxy, std::shared_ptr pScene, const int pageId) { - UBSvgSubsetWriter writer(proxy, pScene, pageIndex); - writer.persistScene(proxy, pageIndex); + UBSvgSubsetWriter writer(proxy, pScene, pageId); + writer.persistScene(proxy, pageId); } -UBSvgSubsetAdaptor::UBSvgSubsetWriter::UBSvgSubsetWriter(std::shared_ptr proxy, std::shared_ptr pScene, const int pageIndex) +UBSvgSubsetAdaptor::UBSvgSubsetWriter::UBSvgSubsetWriter(std::shared_ptr proxy, std::shared_ptr pScene, const int pageId) : mScene(pScene) , mDocumentPath(proxy->persistencePath()) - , mPageIndex(pageIndex) + , mPageId(pageId) { // NOOP @@ -1217,8 +1232,9 @@ void UBSvgSubsetAdaptor::UBSvgSubsetWriter::writeSvgElement(std::shared_ptrisDarkBackground() ? xmlTrue : xmlFalse); - bool crossedBackground = mScene->pageBackground() == UBPageBackground::crossed; - bool ruledBackground = mScene->pageBackground() == UBPageBackground::ruled; + bool crossedBackground = mScene->background() && mScene->background()->isCrossed(); + bool ruledBackground = mScene->background() && mScene->background()->isRuled(); + bool intermediateLines = mScene->background() && mScene->background()->hasIntermediateLines(); mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri, "crossed-background", crossedBackground ? xmlTrue : xmlFalse); mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri, "ruled-background", ruledBackground ? xmlTrue : xmlFalse); @@ -1227,8 +1243,6 @@ void UBSvgSubsetAdaptor::UBSvgSubsetWriter::writeSvgElement(std::shared_ptrintermediateLines(); - mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri, "intermediate-lines", QString::number(intermediateLines)); } @@ -1248,9 +1262,9 @@ void UBSvgSubsetAdaptor::UBSvgSubsetWriter::writeSvgElement(std::shared_ptr proxy, int pageIndex) +bool UBSvgSubsetAdaptor::UBSvgSubsetWriter::persistScene(std::shared_ptr proxy, int pageId) { - Q_UNUSED(pageIndex); + Q_UNUSED(pageId); //Creating dom structure to store information QDomDocument groupDomDocument; @@ -1271,6 +1285,11 @@ bool UBSvgSubsetAdaptor::UBSvgSubsetWriter::persistScene(std::shared_ptrbackground()) + { + mScene->background()->toXml(mXmlWriter); + } + // Get the items from the scene QList items = mScene->items(); @@ -1409,14 +1428,6 @@ bool UBSvgSubsetAdaptor::UBSvgSubsetWriter::persistScene(std::shared_ptr (item); - if (appleWidgetItem && appleWidgetItem->isVisible()) - { - graphicsAppleWidgetToSvg(appleWidgetItem); - continue; - } - // Is the item a W3C? UBGraphicsW3CWidgetItem *w3cWidgetItem = qgraphicsitem_cast (item); if (w3cWidgetItem && w3cWidgetItem->isVisible()) @@ -1550,7 +1561,7 @@ bool UBSvgSubsetAdaptor::UBSvgSubsetWriter::persistScene(std::shared_ptrsceneFilenameForId(mPageId); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) @@ -1567,7 +1578,8 @@ bool UBSvgSubsetAdaptor::UBSvgSubsetWriter::persistScene(std::shared_ptr(groupItem); + QUuid uuid = ubitem ? ubitem->uuid() : QUuid{}; if (!uuid.isNull()) { QDomElement curGroupElement = groupDomDocument->createElement(tGroup); curGroupElement.setAttribute(aId, uuid.toString()); @@ -1591,7 +1603,8 @@ void UBSvgSubsetAdaptor::UBSvgSubsetWriter::persistGroupToDom(QGraphicsItem *gro } curParent->appendChild(curGroupElement); foreach (QGraphicsItem *item, groupItem->childItems()) { - QUuid tmpUuid = UBGraphicsScene::getPersonalUuid(item); + UBItem* ubitem = dynamic_cast(item); + QUuid tmpUuid = ubitem ? ubitem->uuid() : QUuid{}; if (!tmpUuid.isNull()) { if (item->type() == UBGraphicsGroupContainerItem::Type && item->childItems().count()) persistGroupToDom(item, curParent, groupDomDocument); @@ -1688,7 +1701,7 @@ void UBSvgSubsetAdaptor::UBSvgSubsetWriter::strokeToSvgPolyline(UBGraphicsStroke mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri, "uuid", UBStringUtils::toCanonicalUuid(firstPolygonItem->uuid())); if (firstPolygonItem->parentItem()) { - mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri, "parent", UBStringUtils::toCanonicalUuid(UBGraphicsItem::getOwnUuid(firstPolygonItem->strokesGroup()))); + mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri, "parent", firstPolygonItem->strokesGroup()->uuid().toString(QUuid::WithoutBraces)); } mXmlWriter.writeEndElement(); @@ -2147,15 +2160,11 @@ QList UBSvgSubsetAdaptor::UBSvgSubsetReader::polygonItem void UBSvgSubsetAdaptor::UBSvgSubsetWriter::pixmapItemToLinkedImage(UBGraphicsPixmapItem* pixmapItem) { - // find image file - QDir imageDir = mDocumentPath + "/" + UBPersistenceManager::imageDirectory; - QStringList imageFiles = imageDir.entryList({pixmapItem->uuid().toString() + ".*"}); - - if (imageFiles.size() >= 1) + if (!pixmapItem->mediaAssets().isEmpty()) { mXmlWriter.writeStartElement("image"); - QString fileName = UBPersistenceManager::imageDirectory + "/" + imageFiles.last(); + QString fileName = pixmapItem->mediaAssets().at(0); mXmlWriter.writeAttribute(nsXLink, "href", fileName); @@ -2176,11 +2185,13 @@ UBGraphicsPixmapItem* UBSvgSubsetAdaptor::UBSvgSubsetReader::pixmapItemFromSvg() { pixmapItem = new UBGraphicsPixmapItem(); QString href = imageHref.toString(); - QImageReader rdr(mDocumentPath + "/" + UBFileSystemUtils::normalizeFilePath(href)); + const auto asset = UBFileSystemUtils::normalizeFilePath(href); + QImageReader rdr(mDocumentPath + "/" + asset); rdr.setAutoTransform(true); QImage img = rdr.read(); QPixmap pix = QPixmap::fromImage(img); pixmapItem->setPixmap(pix); + pixmapItem->setMediaAsset(mDocumentPath, asset); graphicsItemFromSvg(pixmapItem); } else @@ -2197,7 +2208,7 @@ void UBSvgSubsetAdaptor::UBSvgSubsetWriter::svgItemToLinkedSvg(UBGraphicsSvgItem mXmlWriter.writeStartElement("image"); - QString fileName = UBPersistenceManager::imageDirectory + "/" + svgItem->uuid().toString() + ".svg"; + QString fileName = svgItem->mediaAssets().at(0); mXmlWriter.writeAttribute(nsXLink, "href", fileName); @@ -2219,6 +2230,7 @@ UBGraphicsSvgItem* UBSvgSubsetAdaptor::UBSvgSubsetReader::svgItemFromSvg() QString href = imageHref.toString(); svgItem = new UBGraphicsSvgItem(mDocumentPath + "/" + UBFileSystemUtils::normalizeFilePath(href)); + svgItem->setMediaAsset(mDocumentPath, href); } else { @@ -2238,25 +2250,7 @@ void UBSvgSubsetAdaptor::UBSvgSubsetWriter::pdfItemToLinkedPDF(UBGraphicsPDFItem mXmlWriter.writeStartElement("foreignObject"); mXmlWriter.writeAttribute("requiredExtensions", "http://ns.adobe.com/pdf/1.3/"); - QString fileName = UBPersistenceManager::objectDirectory + "/" + pdfItem->fileUuid().toString() + ".pdf"; - - QString path = mDocumentPath + "/" + fileName; - - if (!QFile::exists(path)) - { - QDir dir; - dir.mkdir(mDocumentPath + "/" + UBPersistenceManager::objectDirectory); - - QFile file(path); - if (!file.open(QIODevice::WriteOnly)) - { - qWarning() << "cannot open file for writing embeded pdf content " << path; - return; - } - - file.write(pdfItem->fileData()); - file.close(); - } + const auto fileName = pdfItem->mediaAssets().at(0); mXmlWriter.writeAttribute(nsXLink, "href", fileName + "#page=" + QString::number(pdfItem->pageNumber())); @@ -2302,7 +2296,7 @@ void UBSvgSubsetAdaptor::UBSvgSubsetWriter::audioItemToLinkedAudio(UBGraphicsAud mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri, "position", QString("%1").arg(pos)); } - QString audioFileHref = "audios/" + audioItem->mediaFileUrl().fileName(); + QString audioFileHref = audioItem->mediaAssets().at(0); mXmlWriter.writeAttribute(nsXLink, "href", audioFileHref); mXmlWriter.writeEndElement(); @@ -2329,7 +2323,7 @@ void UBSvgSubsetAdaptor::UBSvgSubsetWriter::videoItemToLinkedVideo(UBGraphicsVid mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri, "position", QString("%1").arg(pos)); } - QString videoFileHref = "videos/" + videoItem->mediaFileUrl().fileName(); + QString videoFileHref = videoItem->mediaAssets().at(0); mXmlWriter.writeAttribute(nsXLink, "href", videoFileHref); mXmlWriter.writeEndElement(); @@ -2480,11 +2474,6 @@ void UBSvgSubsetAdaptor::UBSvgSubsetReader::graphicsItemFromSvg(QGraphicsItem* g ubItem->setUuid(QUuid(ubUuid.toString())); else ubItem->setUuid(QUuid::createUuid()); - - auto ubSource = mXmlReader.attributes().value(mNamespaceUri, "source"); - - if (!ubSource.isNull()) - ubItem->setSourceUrl(QUrl(ubSource.toString())); } auto ubLocked = mXmlReader.attributes().value(mNamespaceUri, "locked"); @@ -2582,11 +2571,6 @@ void UBSvgSubsetAdaptor::UBSvgSubsetWriter::graphicsItemToSvg(QGraphicsItem* ite if (ubItem) { mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri, "uuid", UBStringUtils::toCanonicalUuid(ubItem->uuid())); - - QUrl sourceUrl = ubItem->sourceUrl(); - - if (!sourceUrl.isEmpty()) - mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri, "source", sourceUrl.path()); } QVariant layer = item->data(UBGraphicsItemData::ItemLayerType); @@ -2611,14 +2595,6 @@ void UBSvgSubsetAdaptor::UBSvgSubsetWriter::graphicsItemToSvg(QGraphicsItem* ite } } - - -// NOTE @letsfindaway obsolete -void UBSvgSubsetAdaptor::UBSvgSubsetWriter::graphicsAppleWidgetToSvg(UBGraphicsAppleWidgetItem* item) -{ - graphicsWidgetToSvg(item); -} - void UBSvgSubsetAdaptor::UBSvgSubsetWriter::graphicsW3CWidgetToSvg(UBGraphicsW3CWidgetItem* item) { graphicsWidgetToSvg(item); @@ -2634,7 +2610,7 @@ void UBSvgSubsetAdaptor::UBSvgSubsetWriter::graphicsWidgetToSvg(UBGraphicsWidget QFileInfo fi(widgetRootDir); QString extension = fi.suffix(); - QString widgetTargetDir = widgetDirectoryPath + "/" + item->uuid().toString() + "." + extension; + QString widgetTargetDir = item->mediaAssets().at(0); QString path = mDocumentPath + "/" + widgetTargetDir; QDir dir(path); @@ -2715,34 +2691,6 @@ void UBSvgSubsetAdaptor::UBSvgSubsetWriter::graphicsWidgetToSvg(UBGraphicsWidget mXmlWriter.writeEndElement(); } -// NOTE @letsfindaway obsolete -UBGraphicsAppleWidgetItem* UBSvgSubsetAdaptor::UBSvgSubsetReader::graphicsAppleWidgetFromSvg() -{ - - auto widgetUrl = mXmlReader.attributes().value(mNamespaceUri, "src"); - - if (widgetUrl.isNull()) - { - qWarning() << "cannot make sens of widget src value"; - return 0; - } - - QString href = widgetUrl.toString(); - - QUrl url(href); - - if (url.isRelative()) - { - href = mDocumentPath + "/" + UBFileSystemUtils::normalizeFilePath(widgetUrl.toString()); - } - - UBGraphicsAppleWidgetItem* widgetItem = new UBGraphicsAppleWidgetItem(QUrl::fromLocalFile(href)); - - graphicsItemFromSvg(widgetItem); - - return widgetItem; -} - UBGraphicsW3CWidgetItem* UBSvgSubsetAdaptor::UBSvgSubsetReader::graphicsW3CWidgetFromSvg() { auto widgetUrl = mXmlReader.attributes().value(mNamespaceUri, "src"); @@ -3436,75 +3384,6 @@ void UBSvgSubsetAdaptor::UBSvgSubsetWriter::cacheToSvg(UBGraphicsCache* item) mXmlWriter.writeEndElement(); } -void UBSvgSubsetAdaptor::convertPDFObjectsToImages(std::shared_ptr proxy) -{ - for (int i = 0; i < proxy->pageCount(); i++) - { - std::shared_ptr scene = loadScene(proxy, i); - - if (scene) - { - bool foundPDFItem = false; - - foreach(QGraphicsItem* item, scene->items()) - { - UBGraphicsPDFItem *pdfItem = dynamic_cast(item); - - if (pdfItem) - { - foundPDFItem = true; - UBGraphicsPixmapItem* pixmapItem = pdfItem->toPixmapItem(); - - scene->removeItem(pdfItem); - scene->addItem(pixmapItem); - - } - } - - if (foundPDFItem) - { - scene->setModified(true); - persistScene(proxy, scene, i); - } - } - - } -} - - -void UBSvgSubsetAdaptor::convertSvgImagesToImages(std::shared_ptr proxy) -{ - for (int i = 0; i < proxy->pageCount(); i++) - { - std::shared_ptr scene = loadScene(proxy, i); - - if (scene) - { - bool foundSvgItem = false; - - foreach(QGraphicsItem* item, scene->items()) - { - UBGraphicsSvgItem *svgItem = dynamic_cast(item); - - if (svgItem) - { - foundSvgItem = true; - UBGraphicsPixmapItem* pixmapItem = svgItem->toPixmapItem(); - - scene->removeItem(svgItem); - scene->addItem(pixmapItem); - } - } - - if (foundSvgItem) - { - scene->setModified(true); - persistScene(proxy, scene, i); - } - } - } -} - UBSvgSubsetAdaptor::UBSvgReaderContext::UBSvgReaderContext(std::shared_ptr proxy, const QByteArray& pXmlData) { reader = new UBSvgSubsetReader(proxy, pXmlData); @@ -3530,3 +3409,8 @@ std::shared_ptr UBSvgSubsetAdaptor::UBSvgReaderContext::scene() { return reader->scene(); } + +std::shared_ptr UBSvgSubsetAdaptor::UBSvgReaderContext::proxy() const +{ + return reader->mProxy; +} diff --git a/src/adaptors/UBSvgSubsetAdaptor.h b/src/adaptors/UBSvgSubsetAdaptor.h index 689d4034b..66913ab13 100644 --- a/src/adaptors/UBSvgSubsetAdaptor.h +++ b/src/adaptors/UBSvgSubsetAdaptor.h @@ -34,6 +34,8 @@ #include #include +#include + #include "frameworks/UBGeometryUtils.h" class UBGraphicsSvgItem; @@ -44,7 +46,6 @@ class UBGraphicsWidgetItem; class UBGraphicsMediaItem; class UBGraphicsVideoItem; class UBGraphicsAudioItem; -class UBGraphicsAppleWidgetItem; class UBGraphicsW3CWidgetItem; class UBGraphicsTextItem; class UBGraphicsCurtainItem; @@ -78,24 +79,23 @@ class UBSvgSubsetAdaptor bool isFinished() const; void step(); std::shared_ptr scene() const; + std::shared_ptr proxy() const; private: UBSvgSubsetReader* reader = nullptr; }; - static std::shared_ptr loadScene(std::shared_ptr proxy, const int pageIndex); - static QByteArray loadSceneAsText(std::shared_ptr proxy, const int pageIndex); + static QByteArray loadSceneAsText(std::shared_ptr proxy, const int pageId); static std::shared_ptr loadScene(std::shared_ptr proxy, const QByteArray& pArray); - static std::shared_ptr prepareLoadingScene(std::shared_ptr proxy, const int pageIndex); - - static void persistScene(std::shared_ptr proxy, std::shared_ptr pScene, const int pageIndex); - static void upgradeScene(std::shared_ptr proxy, const int pageIndex); + static std::shared_ptr prepareLoadingScene(std::shared_ptr proxy, const int pageId, std::optional xmlContent = {}); - static QUuid sceneUuid(std::shared_ptr proxy, const int pageIndex); - static void setSceneUuid(std::shared_ptr proxy, const int pageIndex, QUuid pUuid); + static void persistScene(std::shared_ptr proxy, std::shared_ptr pScene, const int pageId); - static void convertPDFObjectsToImages(std::shared_ptr proxy); - static void convertSvgImagesToImages(std::shared_ptr proxy); + static QUuid sceneUuid(std::shared_ptr proxy, const int pageId); + static QUuid sceneUuid(const QString& xmlContent); + static QVersionNumber sceneVersion(const QString& xmlContent); + static void setSceneUuid(std::shared_ptr proxy, const int pageId, QUuid pUuid); + static void replicateScene(const QString& sourcePath, const QString& targetPath, QUuid uuid); static const QString nsSvg; static const QString nsXLink; @@ -111,7 +111,7 @@ class UBSvgSubsetAdaptor private: - static QDomDocument loadSceneDocument(std::shared_ptr proxy, const int pPageIndex); + static QDomDocument loadSceneDocument(std::shared_ptr proxy, const int pageId); static QString uniboardDocumentNamespaceUriFromVersion(int fileVersion); @@ -137,6 +137,7 @@ class UBSvgSubsetAdaptor std::shared_ptr scene(); private: + friend class UBSvgReaderContext; UBGraphicsPolygonItem* polygonItemFromLineSvg(const QColor& pDefaultBrushColor); @@ -154,8 +155,6 @@ class UBSvgSubsetAdaptor UBGraphicsMediaItem* audioItemFromSvg(); - UBGraphicsAppleWidgetItem* graphicsAppleWidgetFromSvg(); - UBGraphicsW3CWidgetItem* graphicsW3CWidgetFromSvg(); UBGraphicsTextItem* textItemFromSvg(); @@ -209,9 +208,9 @@ class UBSvgSubsetAdaptor { public: - UBSvgSubsetWriter(std::shared_ptr proxy, std::shared_ptr pScene, const int pageIndex); + UBSvgSubsetWriter(std::shared_ptr proxy, std::shared_ptr pScene, const int pageId); - bool persistScene(std::shared_ptr proxy, int pageIndex); + bool persistScene(std::shared_ptr proxy, int pageId); virtual ~UBSvgSubsetWriter(){} @@ -270,7 +269,6 @@ class UBSvgSubsetAdaptor void videoItemToLinkedVideo(UBGraphicsVideoItem *videoItem); void audioItemToLinkedAudio(UBGraphicsAudioItem *audioItem); void graphicsItemToSvg(QGraphicsItem *item); - void graphicsAppleWidgetToSvg(UBGraphicsAppleWidgetItem *item); void graphicsW3CWidgetToSvg(UBGraphicsW3CWidgetItem *item); void graphicsWidgetToSvg(UBGraphicsWidgetItem *item); void textItemToSvg(UBGraphicsTextItem *item); @@ -288,7 +286,7 @@ class UBSvgSubsetAdaptor std::shared_ptr mScene; QXmlStreamWriter mXmlWriter; QString mDocumentPath; - int mPageIndex; + int mPageId; }; }; diff --git a/src/adaptors/UBThumbnailAdaptor.cpp b/src/adaptors/UBThumbnailAdaptor.cpp index 3e7c44c28..d198b2ebe 100644 --- a/src/adaptors/UBThumbnailAdaptor.cpp +++ b/src/adaptors/UBThumbnailAdaptor.cpp @@ -29,116 +29,69 @@ #include "UBThumbnailAdaptor.h" -#include - -#include "frameworks/UBFileSystemUtils.h" - -#include "core/UBPersistenceManager.h" #include "core/UBApplication.h" #include "core/UBSettings.h" -#include "board/UBBoardController.h" -#include "board/UBBoardPaletteManager.h" - -#include "document/UBDocumentProxy.h" +#include "document/UBDocument.h" #include "domain/UBGraphicsScene.h" -#include "UBSvgSubsetAdaptor.h" - #include "core/memcheck.h" -void UBThumbnailAdaptor::generateMissingThumbnails(std::shared_ptr proxy) -{ - int existingPageCount = proxy->pageCount(); - - for (int iPageNo = 0; iPageNo < existingPageCount; ++iPageNo) - { - QString thumbFileName = proxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.thumbnail.jpg", iPageNo); - - QFile thumbFile(thumbFileName); - - if (!thumbFile.exists()) - { - bool displayMessage = (existingPageCount > 5); - int thumbCount = 0; - std::shared_ptr scene = UBSvgSubsetAdaptor::loadScene(proxy, iPageNo); - - if (scene) - { - thumbCount++; - - if (displayMessage && thumbCount == 1) - UBApplication::showMessage(tr("Generating preview thumbnails ...")); - - persistScene(proxy, scene, iPageNo); - } - - if (displayMessage && thumbCount > 0) - UBApplication::showMessage(tr("%1 thumbnails generated ...").arg(thumbCount)); +QPixmap UBThumbnailAdaptor::generateMissingThumbnail(UBDocument* document, int pageIndex, UBGraphicsScene* scene) +{ + QString thumbFileName = document->thumbnailFile(pageIndex); - } + if (QFile::exists(thumbFileName)) + { + QPixmap pix; + pix.load(thumbFileName, 0, Qt::AutoColor); + return pix; } -} -QPixmap UBThumbnailAdaptor::generateMissingThumbnail(std::shared_ptr proxy, int pageIndex) -{ - QString thumbFileName = proxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.thumbnail.jpg", pageIndex); + std::shared_ptr scenePtr; - QFile thumbFile(thumbFileName); + if (scene) + { + scenePtr = scene->shared_from_this(); + } - if (!thumbFile.exists()) + if (!scenePtr) { - std::shared_ptr scene = UBSvgSubsetAdaptor::loadScene(proxy, pageIndex); + scenePtr = document->loadScene(pageIndex, false); + } - if (scene) - { - persistScene(proxy, scene, pageIndex); - } + if (scenePtr) + { + return persistScene(document, scenePtr, pageIndex); } - QPixmap pix; - pix.load(thumbFileName); - return pix; + return {}; } -QPixmap UBThumbnailAdaptor::get(std::shared_ptr proxy, int pageIndex) +QPixmap UBThumbnailAdaptor::get(UBDocument* document, int pageIndex) { - QString fileName = proxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.thumbnail.jpg", pageIndex); + QString fileName = document->thumbnailFile(pageIndex); QFile file(fileName); + if (!file.exists()) { - generateMissingThumbnails(proxy); + return generateMissingThumbnail(document, pageIndex); } QPixmap pix; - if (file.exists()) - { - pix.load(fileName, 0, Qt::AutoColor); - } + pix.load(fileName, 0, Qt::AutoColor); return pix; } -void UBThumbnailAdaptor::load(std::shared_ptr proxy, QList>& list) +QPixmap UBThumbnailAdaptor::persistScene(UBDocument* document, std::shared_ptr pScene, int pageIndex, bool overrideModified) { - list.clear(); - UBApplication::showMessage(tr("Loading thumbnails (%1 pages)").arg(proxy->pageCount())); - for(int i=0; ipageCount(); i++) - { - list.append(std::make_shared(get(proxy, i))); - } -} + QString fileName = document->thumbnailFile(pageIndex); -void UBThumbnailAdaptor::persistScene(std::shared_ptr proxy, std::shared_ptr pScene, int pageIndex, bool overrideModified) -{ - QString fileName = proxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.thumbnail.jpg", pageIndex); - - QFile thumbFile(fileName); - - if (pScene->isModified() || overrideModified || !thumbFile.exists()) + if (pScene->isModified() || overrideModified || !QFile::exists(fileName)) { qreal nominalWidth = pScene->nominalSize().width(); qreal nominalHeight = pScene->nominalSize().height(); @@ -173,14 +126,18 @@ void UBThumbnailAdaptor::persistScene(std::shared_ptr proxy, st pScene->setRenderingContext(UBGraphicsScene::Screen); pScene->setRenderingQuality(UBItem::RenderingQualityNormal, UBItem::CacheAllowed); - thumb.scaled(width, height, Qt::KeepAspectRatio, Qt::SmoothTransformation).save(fileName, "JPG"); + thumb.save(fileName, "JPG"); + + return QPixmap::fromImage(thumb); } + + return {}; } -QUrl UBThumbnailAdaptor::thumbnailUrl(std::shared_ptr proxy, int pageIndex) +QUrl UBThumbnailAdaptor::thumbnailUrl(UBDocument* document, int pageIndex) { - QString fileName = proxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.thumbnail.jpg", pageIndex); + QString fileName = document->thumbnailFile(pageIndex); return QUrl::fromLocalFile(fileName); } diff --git a/src/adaptors/UBThumbnailAdaptor.h b/src/adaptors/UBThumbnailAdaptor.h index 767c7a2ac..d4b63c714 100644 --- a/src/adaptors/UBThumbnailAdaptor.h +++ b/src/adaptors/UBThumbnailAdaptor.h @@ -33,7 +33,6 @@ #include class UBDocument; -class UBDocumentProxy; class UBGraphicsScene; class UBThumbnailAdaptor //static class @@ -41,17 +40,14 @@ class UBThumbnailAdaptor //static class Q_DECLARE_TR_FUNCTIONS(UBThumbnailAdaptor) public: - static QUrl thumbnailUrl(std::shared_ptr proxy, int pageIndex); + static QUrl thumbnailUrl(UBDocument* document, int pageIndex); - static void persistScene(std::shared_ptr proxy, std::shared_ptr pScene, int pageIndex, bool overrideModified = false); + static QPixmap persistScene(UBDocument* document, std::shared_ptr pScene, int pageIndex, bool overrideModified = false); - static QPixmap get(std::shared_ptr proxy, int index); - static void load(std::shared_ptr proxy, QList>& list); - static QPixmap generateMissingThumbnail(std::shared_ptr proxy, int pageIndex); + static QPixmap get(UBDocument* document, int index); + static QPixmap generateMissingThumbnail(UBDocument* document, int pageIndex, UBGraphicsScene* scene = {}); private: - static void generateMissingThumbnails(std::shared_ptr proxy); - UBThumbnailAdaptor() {} }; diff --git a/src/adaptors/adaptors.pri b/src/adaptors/adaptors.pri index eb63272bb..242ab0ad7 100644 --- a/src/adaptors/adaptors.pri +++ b/src/adaptors/adaptors.pri @@ -12,11 +12,9 @@ HEADERS += src/adaptors/UBExportAdaptor.h\ src/adaptors/UBImportPDF.h \ src/adaptors/UBImportImage.h \ src/adaptors/UBExportWeb.h \ + src/adaptors/UBPageMapper.h \ $$PWD/UBExportDocumentSetAdaptor.h \ - $$PWD/UBImportDocumentSetAdaptor.h \ - $$PWD/UBExportCFF.h \ - $$PWD/UBImportCFF.h \ - $$PWD/UBCFFSubsetAdaptor.h + $$PWD/UBImportDocumentSetAdaptor.h SOURCES += src/adaptors/UBExportAdaptor.cpp\ @@ -32,8 +30,6 @@ SOURCES += src/adaptors/UBExportAdaptor.cpp\ src/adaptors/UBImportPDF.cpp \ src/adaptors/UBImportImage.cpp \ src/adaptors/UBExportWeb.cpp \ + src/adaptors/UBPageMapper.cpp \ $$PWD/UBExportDocumentSetAdaptor.cpp \ - $$PWD/UBImportDocumentSetAdaptor.cpp \ - $$PWD/UBExportCFF.cpp \ - $$PWD/UBImportCFF.cpp \ - $$PWD/UBCFFSubsetAdaptor.cpp + $$PWD/UBImportDocumentSetAdaptor.cpp diff --git a/src/api/UBWidgetUniboardAPI.cpp b/src/api/UBWidgetUniboardAPI.cpp index 6dbbde6b8..06955397f 100644 --- a/src/api/UBWidgetUniboardAPI.cpp +++ b/src/api/UBWidgetUniboardAPI.cpp @@ -35,7 +35,9 @@ #include "core/UB.h" #include "core/UBApplication.h" #include "core/UBSettings.h" +#include "core/UBThemeManager.h" +#include "document/UBDocument.h" #include "document/UBDocumentProxy.h" #include "board/UBBoardController.h" @@ -49,6 +51,7 @@ #include "UBWidgetMessageAPI.h" #include "frameworks/UBFileSystemUtils.h" #include "core/UBDownloadManager.h" +#include "gui/UBBackgroundManager.h" #include "core/memcheck.h" @@ -233,10 +236,9 @@ void UBWidgetUniboardAPI::setBackground(bool pIsDark, bool pIsCrossed) auto scene = mScene.lock(); if (scene) { - if (pIsCrossed) - scene->setBackground(pIsDark, UBPageBackground::crossed); - else - scene->setBackground(pIsDark, UBPageBackground::plain); + const auto backgroundManager = UBApplication::boardController->backgroundManager(); + const auto background = backgroundManager->guessBackground(pIsCrossed, false, false); + scene->setSceneBackground(pIsDark, background); } } @@ -353,7 +355,7 @@ int UBWidgetUniboardAPI::pageCount() const auto scene = mScene.lock(); if (scene && scene->document()) - return scene->document()->pageCount(); + return UBDocument::getDocument(scene->document())->pageCount(); else return -1; } @@ -394,14 +396,15 @@ QString UBWidgetUniboardAPI::pageThumbnail(const int pageNumber) return ""; std::shared_ptr doc = UBApplication::boardController->selectedDocument(); + auto document = UBDocument::getDocument(doc); - if (!doc) + if (!document) return ""; - if (pageNumber > doc->pageCount()) + if (pageNumber > document->pageCount()) return ""; - QUrl url = UBThumbnailAdaptor::thumbnailUrl(doc, pageNumber - 1); + QUrl url = UBThumbnailAdaptor::thumbnailUrl(document.get(), pageNumber - 1); return url.toString(); @@ -478,6 +481,11 @@ QString UBWidgetUniboardAPI::lang() const return lang; } +bool UBWidgetUniboardAPI::isDarkMode() const +{ + return UBThemeManager::instance()->isDarkMode(); +} + void UBWidgetUniboardAPI::sendFileMetadata(QString metaData) { // Build the QMap of metadata and send it to application diff --git a/src/api/UBWidgetUniboardAPI.h b/src/api/UBWidgetUniboardAPI.h index b4e2df989..831bedeed 100644 --- a/src/api/UBWidgetUniboardAPI.h +++ b/src/api/UBWidgetUniboardAPI.h @@ -87,6 +87,11 @@ class UBWidgetUniboardAPI : public QObject Q_PROPERTY(QString dropData MEMBER mDropData WRITE setDropData NOTIFY dropDataChanged SCRIPTABLE true) + /** + * Returns true if the application is currently in dark mode + */ + Q_PROPERTY(bool isDarkMode READ isDarkMode NOTIFY themeChanged SCRIPTABLE true) + public: UBWidgetUniboardAPI(std::shared_ptr pScene, UBGraphicsWidgetItem *widget = 0); ~UBWidgetUniboardAPI(); @@ -166,7 +171,7 @@ class UBWidgetUniboardAPI : public QObject /** * add any supported objects (pictures/video/widget) centered at scene position x/y. - * width and height may be supplied, this is useful for flash (.swf) objects + * width and height may be supplied * if background is true, the object is not selectable and sits in the lowest z pos possible * */ @@ -264,6 +269,7 @@ class UBWidgetUniboardAPI : public QObject signals: void dropDataChanged(const QString& data); + void themeChanged(bool isDark); private slots: void onDownloadFinished(bool pSuccess, sDownloadFileDesc desc, QByteArray pData); @@ -276,6 +282,8 @@ private slots: QString lang() const; + bool isDarkMode() const; + void setDropData(const QString& data); QString getObjDir(); diff --git a/src/board/CMakeLists.txt b/src/board/CMakeLists.txt index 8e20f849e..6ebccd022 100644 --- a/src/board/CMakeLists.txt +++ b/src/board/CMakeLists.txt @@ -5,6 +5,8 @@ target_sources(${PROJECT_NAME} PRIVATE UBBoardPaletteManager.h UBBoardView.cpp UBBoardView.h + UBColorPreferencesDialog.cpp + UBColorPreferencesDialog.h UBDrawingController.cpp UBDrawingController.h UBFeaturesController.cpp diff --git a/src/board/UBBoardController.cpp b/src/board/UBBoardController.cpp index 48a5163e5..af91b5f59 100644 --- a/src/board/UBBoardController.cpp +++ b/src/board/UBBoardController.cpp @@ -36,6 +36,7 @@ #include "board/UBBoardPaletteManager.h" #include "board/UBBoardView.h" +#include "board/UBColorPreferencesDialog.h" #include "board/UBDrawingController.h" #include "board/UBFeaturesController.h" @@ -68,6 +69,7 @@ #include "domain/UBItem.h" #include "domain/UBPageSizeUndoCommand.h" +#include "gui/UBBackgroundManager.h" #include "gui/UBFeaturesWidget.h" #include "gui/UBKeyboardPalette.h" #include "gui/UBMagnifer.h" @@ -110,6 +112,8 @@ UBBoardController::UBBoardController(UBMainWindow* mainWindow) , mActionUngroupText(tr("Ungroup")) , mAutosaveTimer(0) { + mBgManager = new UBBackgroundManager(this); + mZoomFactor = UBSettings::settings()->boardZoomFactor->get().toDouble(); int penColorIndex = UBSettings::settings()->penColorIndex(); @@ -349,31 +353,23 @@ void UBBoardController::setupToolbar() { UBSettings *settings = UBSettings::settings(); - // Setup color choice widget - QList colorActions; - colorActions.append(mMainWindow->actionColor0); - colorActions.append(mMainWindow->actionColor1); - colorActions.append(mMainWindow->actionColor2); - colorActions.append(mMainWindow->actionColor3); - colorActions.append(mMainWindow->actionColor4); + buildColorActions(); - UBToolbarButtonGroup *colorChoice = - new UBToolbarButtonGroup(mMainWindow->boardToolBar, colorActions); - colorChoice->setLabel(tr("Color")); + mColorChoice = new UBToolbarButtonGroup(mMainWindow->boardToolBar, mColorActions, QString(), settings->colorPaletteSize); + mColorChoice->setLabel(tr("Color")); - mMainWindow->boardToolBar->insertWidget(mMainWindow->actionBackgrounds, colorChoice); + mMainWindow->boardToolBar->insertWidget(mMainWindow->actionBackgrounds, mColorChoice); - connect(settings->appToolBarDisplayText, SIGNAL(changed(QVariant)), colorChoice, SLOT(displayText(QVariant))); - connect(colorChoice, SIGNAL(activated(int)), this, SLOT(setColorIndex(int))); - connect(UBDrawingController::drawingController(), SIGNAL(colorIndexChanged(int)), colorChoice, SLOT(setCurrentIndex(int))); + connect(settings->appToolBarDisplayText, SIGNAL(changed(QVariant)), mColorChoice, SLOT(displayText(QVariant))); + connect(mColorChoice, SIGNAL(activated(int)), this, SLOT(setColorIndex(int))); + connect(UBDrawingController::drawingController(), SIGNAL(colorIndexChanged(int)), mColorChoice, SLOT(setCurrentIndex(int))); connect(UBDrawingController::drawingController(), SIGNAL(colorIndexChanged(int)), UBDrawingController::drawingController(), SIGNAL(colorPaletteChanged())); - connect(UBDrawingController::drawingController(), SIGNAL(colorPaletteChanged()), colorChoice, SLOT(colorPaletteChanged())); + connect(UBDrawingController::drawingController(), SIGNAL(colorPaletteChanged()), mColorChoice, SLOT(colorPaletteChanged())); connect(UBDrawingController::drawingController(), SIGNAL(colorPaletteChanged()), this, SLOT(colorPaletteChanged())); + connect(mMainWindow->actionColorPreferences, SIGNAL(triggered()), this, SLOT(openColorPreferencesDialog())); - colorChoice->displayText(QVariant(settings->appToolBarDisplayText->get().toBool())); - colorChoice->colorPaletteChanged(); - colorChoice->setCurrentIndex(settings->penColorIndex()); - colorActions.at(settings->penColorIndex())->setChecked(true); + mColorChoice->displayText(QVariant(settings->appToolBarDisplayText->get().toBool())); + updateColorButtonsForPaletteSize(); // Setup line width choice widget QList lineWidthActions; @@ -438,6 +434,73 @@ void UBBoardController::setupToolbar() } +void UBBoardController::buildColorActions() +{ + mColorActions.clear(); + + mColorActions << mMainWindow->actionColor0 + << mMainWindow->actionColor1 + << mMainWindow->actionColor2 + << mMainWindow->actionColor3 + << mMainWindow->actionColor4 + << mMainWindow->actionColor5 + << mMainWindow->actionColor6 + << mMainWindow->actionColor7 + << mMainWindow->actionColor8 + << mMainWindow->actionColor9 + << mMainWindow->actionColorPreferences; +} + + +int UBBoardController::currentToolColorIndex() const +{ + if (UBDrawingController::drawingController()->stylusTool() == UBStylusTool::Marker) + { + return UBSettings::settings()->markerColorIndex(); + } + + return UBSettings::settings()->penColorIndex(); +} + + +void UBBoardController::updateColorButtonsForPaletteSize() +{ + const int paletteSize = UBSettings::settings()->colorPaletteSize; + + for (int i = 0; i < mColorActions.size(); ++i) + { + QAction* action = mColorActions.at(i); + + if (!action) + continue; + + if (!action->isCheckable()) + { + action->setEnabled(true); + continue; + } + + const bool enabled = i < paletteSize; + action->setEnabled(enabled); + action->setVisible(true); + + if (!enabled) + { + action->setChecked(false); + } + } + + if (mColorChoice) + { + mColorChoice->setSelectableCount(paletteSize); + mColorChoice->colorPaletteChanged(); + + int index = qBound(0, currentToolColorIndex(), paletteSize - 1); + mColorChoice->setCurrentIndex(index); + } +} + + void UBBoardController::setToolCursor(int tool) { if (mActiveScene) @@ -493,10 +556,10 @@ void UBBoardController::saveData(SaveFlags fls) } } -void UBBoardController::documentSceneDuplicated(std::shared_ptr proxy, int index) +void UBBoardController::documentSceneDuplicated(UBDocument* document, int index) { // index is duplicated page - if (selectedDocument() == proxy) + if (selectedDocument() == document->proxy()) { if (UBApplication::applicationController->displayMode() == UBApplicationController::Board) { @@ -511,9 +574,9 @@ void UBBoardController::documentSceneDuplicated(std::shared_ptr } } -void UBBoardController::documentSceneMoved(std::shared_ptr proxy, int fromIndex, int toIndex) +void UBBoardController::documentSceneMoved(UBDocument* document, int fromIndex, int toIndex) { - if (selectedDocument() == proxy) + if (selectedDocument() == document->proxy()) { int nextSceneIndex = mActiveSceneIndex; @@ -549,13 +612,13 @@ void UBBoardController::documentSceneMoved(std::shared_ptr prox } } -void UBBoardController::documentSceneDeleted(std::shared_ptr proxy, int index) +void UBBoardController::documentSceneDeleted(UBDocument* document, int index) { - if (selectedDocument() == proxy) + if (selectedDocument() == document->proxy()) { int nextSceneIndex = mActiveSceneIndex; - if (index < mActiveSceneIndex || (index == mActiveSceneIndex && index == proxy->pageCount() && index > 0)) + if (index < mActiveSceneIndex || (index == mActiveSceneIndex && index == document->pageCount() && index > 0)) { --nextSceneIndex; } @@ -563,7 +626,9 @@ void UBBoardController::documentSceneDeleted(std::shared_ptr pr if (UBApplication::applicationController->displayMode() == UBApplicationController::Board) { // directly change scene + mDeletingSceneIndex = index; setActiveDocumentScene(nextSceneIndex); + mDeletingSceneIndex = -1; } else { @@ -658,52 +723,14 @@ void UBBoardController::addScene() UBPersistenceManager::persistenceManager()->persistDocumentMetadata(selectedDocument()); } -void UBBoardController::addScene(std::shared_ptr scene, bool replaceActiveIfEmpty) -{ - if (scene) - { - std::shared_ptr clone = scene->sceneDeepCopy(); - - if (scene->document() && (scene->document() != selectedDocument())) - { - foreach(QUrl relativeFile, scene->relativeDependencies()) - { - QString source = scene->document()->persistencePath() + "/" + relativeFile.path(); - QString destination = selectedDocument()->persistencePath() + "/" + relativeFile.path(); - - UBFileSystemUtils::copy(source, destination, true); - } - } - - auto document = UBDocument::getDocument(selectedDocument()); - - if (replaceActiveIfEmpty && mActiveScene->isEmpty()) - { - document->insertPage(clone, mActiveSceneIndex); - setActiveDocumentScene(mActiveSceneIndex); - deleteScene(mActiveSceneIndex + 1); - } - else - { - persistCurrentScene(false,true); - document->insertPage(clone, mActiveSceneIndex + 1); - setActiveDocumentScene(mActiveSceneIndex + 1); - } - - QDateTime now = QDateTime::currentDateTime(); - selectedDocument()->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(now)); - } -} - void UBBoardController::addScene(std::shared_ptr proxy, int sceneIndex, bool replaceActiveIfEmpty) { - std::shared_ptr scene = UBPersistenceManager::persistenceManager()->loadDocumentScene(proxy, sceneIndex); - - if (scene) - { - addScene(scene, replaceActiveIfEmpty); - } + // copy scene after active scene index + auto sourceDocument = UBDocument::getDocument(proxy); + auto targetDocument = UBDocument::getDocument(selectedDocument()); + sourceDocument->copyPage(sceneIndex, targetDocument, mActiveSceneIndex + 1); + setActiveDocumentScene(mActiveSceneIndex + 1); } void UBBoardController::duplicateScene(int nIndex) @@ -730,104 +757,46 @@ void UBBoardController::duplicateScene() UBGraphicsItem *UBBoardController::duplicateItem(UBItem *item) { if (!item) - return NULL; - - UBGraphicsItem *retItem = NULL; + { + return {}; + } - mLastCreatedItem = NULL; + UBGraphicsItem* retItem{nullptr}; - QUrl sourceUrl; - QByteArray pData; + mLastCreatedItem = nullptr; //common parameters for any item QPointF itemPos; QSizeF itemSize; - QGraphicsItem *commonItem = dynamic_cast(item); + QGraphicsItem* commonItem = dynamic_cast(item); + if (commonItem) { qreal shifting = UBSettings::settings()->objectFrameWidth; itemPos = commonItem->pos() + QPointF(shifting,shifting); itemSize = commonItem->boundingRect().size(); commonItem->setSelected(false); - } - UBMimeType::Enum itemMimeType; - - QString srcFile = item->sourceUrl().toLocalFile(); - if (srcFile.isEmpty()) - srcFile = item->sourceUrl().toString(); - - QString contentTypeHeader; - if (!srcFile.isEmpty()) - contentTypeHeader = UBFileSystemUtils::mimeTypeFromFileName(srcFile); - - if(NULL != qgraphicsitem_cast(commonItem)) - itemMimeType = UBMimeType::Group; - else - itemMimeType = UBFileSystemUtils::mimeTypeFromString(contentTypeHeader); - - switch(static_cast(itemMimeType)) - { - case UBMimeType::AppleWidget: - case UBMimeType::W3CWidget: - { - UBGraphicsWidgetItem *witem = dynamic_cast(item); - if (witem) - { - sourceUrl = witem->getOwnFolder(); - } - }break; - - case UBMimeType::Video: - case UBMimeType::Audio: - { - UBGraphicsMediaItem *mitem = dynamic_cast(item); - if (mitem) - { - sourceUrl = mitem->mediaFileUrl(); - downloadURL(sourceUrl, srcFile, itemPos, QSize(itemSize.width(), itemSize.height()), false, false); - return NULL; // async operation - } - }break; - - case UBMimeType::VectorImage: - { - UBGraphicsSvgItem *viitem = dynamic_cast(item); - if (viitem) - { - pData = viitem->fileData(); - sourceUrl = item->sourceUrl(); - } - }break; - - case UBMimeType::RasterImage: - { - UBGraphicsPixmapItem *pixitem = dynamic_cast(item); - if (pixitem) - { - QBuffer buffer(&pData); - buffer.open(QIODevice::WriteOnly); - QString format = UBFileSystemUtils::extension(item->sourceUrl().toString(QUrl::DecodeReserved)); - pixitem->pixmap().save(&buffer, format.toLatin1()); - } - }break; - - case UBMimeType::Group: + if (commonItem->type() == UBGraphicsItemType::groupContainerType) { UBGraphicsGroupContainerItem* groupItem = dynamic_cast(item); - UBGraphicsGroupContainerItem* duplicatedGroup = NULL; + UBGraphicsGroupContainerItem* duplicatedGroup{nullptr}; QList duplicatedItems; - QList children = groupItem->childItems(); + const QList children = groupItem->childItems(); mActiveScene->setURStackEnable(false); - foreach(QGraphicsItem* pIt, children){ + + for (QGraphicsItem* pIt : children) + { UBItem* pItem = dynamic_cast(pIt); - if(pItem) + + if (pItem) { - QGraphicsItem * itemToGroup = dynamic_cast(duplicateItem(pItem)); + QGraphicsItem* itemToGroup = dynamic_cast(duplicateItem(pItem)); + if (itemToGroup) { itemToGroup->setZValue(pIt->zValue()); @@ -836,100 +805,59 @@ UBGraphicsItem *UBBoardController::duplicateItem(UBItem *item) } } } + duplicatedGroup = mActiveScene->createGroup(duplicatedItems); duplicatedGroup->setTransform(groupItem->transform()); groupItem->copyItemParameters(duplicatedGroup); groupItem->setSelected(false); + mActiveScene->addItem(duplicatedGroup); + duplicatedGroup->setSelected(true); - retItem = dynamic_cast(duplicatedGroup); - - QGraphicsItem * itemToAdd = dynamic_cast(retItem); - if (itemToAdd) - { - mActiveScene->addItem(itemToAdd); - itemToAdd->setSelected(true); - } mActiveScene->setURStackEnable(true); - }break; - case UBMimeType::UNKNOWN: + retItem = duplicatedGroup; + } + else + { + QGraphicsItem *copiedItem = dynamic_cast(item); + QGraphicsItem *gitem = dynamic_cast(item->deepCopy()); + + if (gitem) { - QGraphicsItem *copiedItem = dynamic_cast(item); - QGraphicsItem *gitem = dynamic_cast(item->deepCopy()); - if (gitem) - { - mActiveScene->addItem(gitem); + mActiveScene->addItem(gitem); - if (copiedItem) + if (copiedItem) + { + if (mActiveScene->tools().contains(copiedItem)) { - if (mActiveScene->tools().contains(copiedItem)) - { - mActiveScene->registerTool(gitem); - } + mActiveScene->registerTool(gitem); } - gitem->setPos(itemPos); - - mLastCreatedItem = gitem; - gitem->setSelected(true); } - retItem = dynamic_cast(gitem); - }break; + gitem->setPos(itemPos); + + mLastCreatedItem = gitem; + gitem->setSelected(true); + } + + retItem = dynamic_cast(gitem); } if (retItem) { QGraphicsItem *graphicsRetItem = dynamic_cast(retItem); - if (mActiveScene->isURStackIsEnabled()) { //should be deleted after scene own undo stack implemented + + if (mActiveScene->isURStackIsEnabled()) + { + //should be deleted after scene own undo stack implemented UBGraphicsItemUndoCommand* uc = new UBGraphicsItemUndoCommand(mActiveScene, 0, graphicsRetItem); UBApplication::undoStack->push(uc); } - return retItem; - } - - UBItem *createdItem = downloadFinished(true, sourceUrl, QUrl::fromLocalFile(srcFile), contentTypeHeader, pData, itemPos, QSize(itemSize.width(), itemSize.height()), false); - if (createdItem) - { - createdItem->setSourceUrl(item->sourceUrl()); - item->copyItemParameters(createdItem); - - QGraphicsItem *createdGitem = dynamic_cast(createdItem); - if (createdGitem) - createdGitem->setPos(itemPos); - mLastCreatedItem = dynamic_cast(createdItem); - mLastCreatedItem->setSelected(true); - - retItem = dynamic_cast(createdItem); } return retItem; } -void UBBoardController::deleteScene(int nIndex) -{ - if (selectedDocument()->pageCount()>=2) - { - mDeletingSceneIndex = nIndex; - QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); - persistCurrentScene(); - UBApplication::showMessage(tr("Deleting page %1").arg(nIndex+1), true); - - auto document = UBDocument::getDocument(selectedDocument()); - document->deletePages({nIndex}); - - QDateTime now = QDateTime::currentDateTime(); - selectedDocument()->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(now)); - UBMetadataDcSubsetAdaptor::persist(selectedDocument()); - - if (nIndex >= pageCount()) - nIndex = pageCount()-1; - setActiveDocumentScene(nIndex); - UBApplication::showMessage(tr("Page %1 deleted").arg(nIndex+1)); - QApplication::restoreOverrideCursor(); - mDeletingSceneIndex = -1; - } -} - void UBBoardController::clearScene() { @@ -1176,7 +1104,7 @@ void UBBoardController::previousScene() void UBBoardController::nextScene() { - if (mActiveSceneIndex < selectedDocument()->pageCount() - 1) + if (mActiveSceneIndex < activeDocument()->pageCount() - 1) { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); setActiveDocumentScene(mActiveSceneIndex + 1); @@ -1202,10 +1130,10 @@ void UBBoardController::firstScene() void UBBoardController::lastScene() { - if (mActiveSceneIndex < selectedDocument()->pageCount() - 1) + if (mActiveSceneIndex < activeDocument()->pageCount() - 1) { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); - setActiveDocumentScene(selectedDocument()->pageCount() - 1); + setActiveDocumentScene(activeDocument()->pageCount() - 1); QApplication::restoreOverrideCursor(); } @@ -1319,20 +1247,6 @@ UBItem *UBBoardController::downloadFinished(bool pSuccess, QUrl sourceUrl, QUrl UBGraphicsPixmapItem* pixItem = mActiveScene->addImage(pData, nullptr, pPos, 1.); - if (scheme == "data") - { - // create a shorter, but still unique URL using a hash function - QCryptographicHash hash(QCryptographicHash::Md5); - hash.addData(sourceUrl.toString().toLatin1()); - QByteArray result = hash.result(); - QString hashedUrl = "md5:" + result.toBase64(); - pixItem->setSourceUrl(hashedUrl); - } - else - { - pixItem->setSourceUrl(sourceUrl); - } - if (isBackground) { mActiveScene->setAsBackgroundObject(pixItem, true); @@ -1350,7 +1264,6 @@ UBItem *UBBoardController::downloadFinished(bool pSuccess, QUrl sourceUrl, QUrl qDebug() << "accepting mime type" << mimeType << "as vector image"; UBGraphicsSvgItem* svgItem = mActiveScene->addSvg(sourceUrl, pPos, pData); - svgItem->setSourceUrl(sourceUrl); if (isBackground) { @@ -1364,32 +1277,6 @@ UBItem *UBBoardController::downloadFinished(bool pSuccess, QUrl sourceUrl, QUrl return svgItem; } - else if (UBMimeType::AppleWidget == itemMimeType) //mime type invented by us :-( - { - qDebug() << "accepting mime type" << mimeType << "as Apple widget"; - - QUrl widgetUrl = sourceUrl; - - if (pData.length() > 0) - { - widgetUrl = expandWidgetToTempDir(pData, "wdgt"); - } - - UBGraphicsWidgetItem* appleWidgetItem = mActiveScene->addAppleWidget(widgetUrl, pPos); - - appleWidgetItem->setSourceUrl(sourceUrl); - - if (isBackground) - { - mActiveScene->setAsBackgroundObject(appleWidgetItem); - } - else - { - UBDrawingController::drawingController()->setStylusTool(UBStylusTool::Selector); - } - - return appleWidgetItem; - } else if (UBMimeType::W3CWidget == itemMimeType) { qDebug() << "accepting mime type" << mimeType << "as W3C widget"; @@ -1418,17 +1305,16 @@ UBItem *UBBoardController::downloadFinished(bool pSuccess, QUrl sourceUrl, QUrl qDebug() << "accepting mime type" << mimeType << "as video"; UBGraphicsMediaItem *mediaVideoItem = 0; - QUuid uuid = QUuid::createUuid(); + if (pData.length() > 0) { QString destFile; - bool b = UBPersistenceManager::persistenceManager()->addFileToDocument(selectedDocument(), + const auto uuid = UBPersistenceManager::persistenceManager()->addFileToDocument(selectedDocument(), sourceUrl.toString(), UBPersistenceManager::videoDirectory, - uuid, destFile, &pData); - if (!b) + if (uuid.isNull()) { UBApplication::showMessage(tr("Add file operation failed: file copying error")); return NULL; @@ -1441,15 +1327,11 @@ UBItem *UBBoardController::downloadFinished(bool pSuccess, QUrl sourceUrl, QUrl else { qDebug() << sourceUrl.toString(); - mediaVideoItem = addVideo(sourceUrl, false, pPos, true); + mediaVideoItem = addVideo(sourceUrl, false, pPos); } - if(mediaVideoItem){ - if (contentUrl.isEmpty()) - mediaVideoItem->setSourceUrl(sourceUrl); - else - mediaVideoItem->setSourceUrl(contentUrl); - mediaVideoItem->setUuid(uuid); + if (mediaVideoItem) + { connect(this, SIGNAL(activeSceneChanged()), mediaVideoItem, SLOT(activeSceneChanged())); } @@ -1463,17 +1345,15 @@ UBItem *UBBoardController::downloadFinished(bool pSuccess, QUrl sourceUrl, QUrl UBGraphicsMediaItem *audioMediaItem = 0; - QUuid uuid = QUuid::createUuid(); if (pData.length() > 0) { QString destFile; - bool b = UBPersistenceManager::persistenceManager()->addFileToDocument(selectedDocument(), + const auto uuid = UBPersistenceManager::persistenceManager()->addFileToDocument(selectedDocument(), sourceUrl.toString(), UBPersistenceManager::audioDirectory, - uuid, destFile, &pData); - if (!b) + if (uuid.isNull()) { UBApplication::showMessage(tr("Add file operation failed: file copying error")); return NULL; @@ -1485,15 +1365,11 @@ UBItem *UBBoardController::downloadFinished(bool pSuccess, QUrl sourceUrl, QUrl } else { - audioMediaItem = addAudio(sourceUrl, false, pPos, true); + audioMediaItem = addAudio(sourceUrl, false, pPos); } - if(audioMediaItem){ - if (contentUrl.isEmpty()) - audioMediaItem->setSourceUrl(sourceUrl); - else - audioMediaItem->setSourceUrl(contentUrl); - audioMediaItem->setUuid(uuid); + if (audioMediaItem) + { connect(this, SIGNAL(activeSceneChanged()), audioMediaItem, SLOT(activeSceneChanged())); } @@ -1501,46 +1377,6 @@ UBItem *UBBoardController::downloadFinished(bool pSuccess, QUrl sourceUrl, QUrl return audioMediaItem; } - else if (UBMimeType::Flash == itemMimeType) - { - - qDebug() << "accepting mime type" << mimeType << "as flash"; - - QString sUrl = sourceUrl.toString(); - - if (sUrl.startsWith("file://") || sUrl.startsWith("/")) - { - sUrl = sourceUrl.toLocalFile(); - } - - QSize size; - - if (pSize.height() > 0 && pSize.width() > 0) - size = pSize; - else - size = mActiveScene->nominalSize() * .8; - - Q_UNUSED(internalData) - - QString widgetUrl = UBGraphicsW3CWidgetItem::createNPAPIWrapper(sUrl, mimeType, size); - UBFileSystemUtils::deleteFile(sourceUrl.toLocalFile()); - emit npapiWidgetCreated(widgetUrl); - - if (widgetUrl.length() > 0) - { - UBGraphicsWidgetItem *widgetItem = mActiveScene->addW3CWidget(QUrl::fromLocalFile(widgetUrl), pPos); - widgetItem->setUuid(QUuid::createUuid()); - widgetItem->setSourceUrl(QUrl::fromLocalFile(widgetUrl)); - qDebug() << widgetItem->getOwnFolder(); - qDebug() << widgetItem->getSnapshotPath(); - - widgetItem->setSnapshotPath(widgetItem->getOwnFolder()); - - UBDrawingController::drawingController()->setStylusTool(UBStylusTool::Selector); - - return widgetItem; - } - } else if (UBMimeType::PDF == itemMimeType) { qDebug() << "accepting mime type" << mimeType << "as PDF"; @@ -1690,12 +1526,13 @@ std::shared_ptr UBBoardController::setActiveDocumentScene(std:: bool documentChange = selectedDocument() != pDocumentProxy; + auto document = UBDocument::getDocument(pDocumentProxy); int index = pSceneIndex; - int sceneCount = pDocumentProxy->pageCount(); + int sceneCount = document->pageCount(); if (index >= sceneCount && sceneCount > 0) index = sceneCount - 1; - std::shared_ptr targetScene = UBPersistenceManager::persistenceManager()->loadDocumentScene(pDocumentProxy, index); + std::shared_ptr targetScene = document->loadScene(index); bool sceneChange = targetScene != mActiveScene; @@ -1734,14 +1571,16 @@ std::shared_ptr UBBoardController::setActiveDocumentScene(std:: adjustDisplayViews(); UBSettings::settings()->setDarkBackground(mActiveScene->isDarkBackground()); - UBSettings::settings()->setPageBackground(mActiveScene->pageBackground()); + + if (mActiveScene->background()) + { + UBSettings::settings()->setPageBackgroundUuid(mActiveScene->background()->uuid()); + } freezeW3CWidgets(false); selectionChanged(); - updateBackgroundActionsState(mActiveScene->isDarkBackground(), mActiveScene->pageBackground()); - if (documentChange) { UBGraphicsTextItem::lastUsedTextColor = QColor(Qt::black); @@ -1845,14 +1684,14 @@ void UBBoardController::ClearUndoStack() // This ensures that we can cut and paste a media item, widget, etc. from one page to the next. QClipboard *clipboard = QApplication::clipboard(); const QMimeData* data = clipboard->mimeData(); - QList sourceURLs; + QList sourceUUIDs; if (data && data->hasFormat(UBApplication::mimeTypeUniboardPageItem)) { const UBMimeDataGraphicsItem* mimeDataGI = qobject_cast (data); if (mimeDataGI) { foreach (UBItem* sourceItem, mimeDataGI->items()) { - sourceURLs << sourceItem->sourceUrl(); + sourceUUIDs << sourceItem->uuid(); } } } @@ -1870,7 +1709,7 @@ void UBBoardController::ClearUndoStack() bool inClipboard = false; UBItem* ubi = dynamic_cast(item); - if (ubi && sourceURLs.contains(ubi->sourceUrl())) + if (ubi && sourceUUIDs.contains(ubi->uuid())) inClipboard = true; if(!scene && !inClipboard) @@ -1904,20 +1743,14 @@ int UBBoardController::autosaveTimeoutFromSettings() const return value * minute; } -void UBBoardController::changeBackground(bool isDark, UBPageBackground pageBackground) +void UBBoardController::setBackground(bool isDark, const UBBackgroundRuling* background) { - bool currentIsDark = mActiveScene->isDarkBackground(); - UBPageBackground currentBackgroundType = mActiveScene->pageBackground(); + UBSettings::settings()->setDarkBackground(isDark); + UBSettings::settings()->setPageBackgroundUuid(background ? background->uuid() : QUuid{}); - if ((isDark != currentIsDark) || (currentBackgroundType != pageBackground)) - { - UBSettings::settings()->setDarkBackground(isDark); - UBSettings::settings()->setPageBackground(pageBackground); + mActiveScene->setSceneBackground(isDark, background); - mActiveScene->setBackground(isDark, pageBackground); - - emit backgroundChanged(); - } + emit backgroundChanged(); } void UBBoardController::boardViewResized(QResizeEvent* event) @@ -1991,7 +1824,7 @@ void UBBoardController::undoRedoStateChange(bool canUndo) void UBBoardController::updateActionStates() { mMainWindow->actionBack->setEnabled(selectedDocument() && (mActiveSceneIndex > 0)); - mMainWindow->actionForward->setEnabled(selectedDocument() && (mActiveSceneIndex < selectedDocument()->pageCount() - 1)); + mMainWindow->actionForward->setEnabled(selectedDocument() && (mActiveSceneIndex < activeDocument()->pageCount() - 1)); mMainWindow->actionErase->setEnabled(mActiveScene && !mActiveScene->isEmpty()); } @@ -2007,11 +1840,6 @@ int UBBoardController::activeSceneIndex() const return mActiveSceneIndex; } -void UBBoardController::setActiveSceneIndex(int i) -{ - mActiveSceneIndex = i; -} - void UBBoardController::documentSceneChanged(std::shared_ptr pDocumentProxy, int pIndex) { Q_UNUSED(pIndex); @@ -2108,6 +1936,9 @@ void UBBoardController::setColorIndex(int pColorIndex) { UBDrawingController::drawingController()->setColorIndex(pColorIndex); + const bool isMarkerTool = (UBDrawingController::drawingController()->stylusTool() == UBStylusTool::Marker); + const int appliedIndex = isMarkerTool ? UBSettings::settings()->markerColorIndex() : UBSettings::settings()->penColorIndex(); + if (UBDrawingController::drawingController()->stylusTool() != UBStylusTool::Marker && UBDrawingController::drawingController()->stylusTool() != UBStylusTool::Line && UBDrawingController::drawingController()->stylusTool() != UBStylusTool::Text && @@ -2121,8 +1952,10 @@ void UBBoardController::setColorIndex(int pColorIndex) UBDrawingController::drawingController()->stylusTool() == UBStylusTool::Text || UBDrawingController::drawingController()->stylusTool() == UBStylusTool::Selector) { - mPenColorOnDarkBackground = UBSettings::settings()->penColors(true).at(pColorIndex); - mPenColorOnLightBackground = UBSettings::settings()->penColors(false).at(pColorIndex); + QList darkColors = UBSettings::settings()->penColors(true); + QList lightColors = UBSettings::settings()->penColors(false); + mPenColorOnDarkBackground = darkColors.value(appliedIndex, darkColors.value(0, Qt::white)); + mPenColorOnLightBackground = lightColors.value(appliedIndex, lightColors.value(0, Qt::black)); if (UBDrawingController::drawingController()->stylusTool() == UBStylusTool::Selector) { @@ -2136,10 +1969,12 @@ void UBBoardController::setColorIndex(int pColorIndex) emit penColorChanged(); } - else if (UBDrawingController::drawingController()->stylusTool() == UBStylusTool::Marker) + else if (isMarkerTool) { - mMarkerColorOnDarkBackground = UBSettings::settings()->markerColors(true).at(pColorIndex); - mMarkerColorOnLightBackground = UBSettings::settings()->markerColors(false).at(pColorIndex); + QList darkColors = UBSettings::settings()->markerColors(true); + QList lightColors = UBSettings::settings()->markerColors(false); + mMarkerColorOnDarkBackground = darkColors.value(appliedIndex, darkColors.value(0, Qt::white)); + mMarkerColorOnLightBackground = lightColors.value(appliedIndex, lightColors.value(0, Qt::black)); } } @@ -2152,6 +1987,84 @@ void UBBoardController::colorPaletteChanged() } +void UBBoardController::openColorPreferencesDialog() +{ + if (mColorPreferencesDialog) + { + mColorPreferencesDialog->raise(); + mColorPreferencesDialog->activateWindow(); + return; + } + + UBSettings* settings = UBSettings::settings(); + const int paletteSize = settings->colorPaletteSize; + + QList penLightColors = settings->boardPenLightBackgroundSelectedColors->colors(); + QList penDarkColors = settings->boardPenDarkBackgroundSelectedColors->colors(); + QList markerLightColors = settings->boardMarkerLightBackgroundSelectedColors->colors(); + QList markerDarkColors = settings->boardMarkerDarkBackgroundSelectedColors->colors(); + + mColorPreferencesDialog = new UBColorPreferencesDialog(mMainWindow, + paletteSize, + penLightColors, + penDarkColors, + markerLightColors, + markerDarkColors, + qRound(settings->boardMarkerAlpha->get().toDouble() * 100.0)); + mColorPreferencesDialog->setAttribute(Qt::WA_DeleteOnClose); + connect(mColorPreferencesDialog, SIGNAL(accepted()), this, SLOT(colorPreferencesDialogAccepted())); + mColorPreferencesDialog->open(); +} + + +void UBBoardController::colorPreferencesDialogAccepted() +{ + UBColorPreferencesDialog* dialog = qobject_cast(sender()); + if (!dialog) + return; + + UBSettings* settings = UBSettings::settings(); + + const int selectedPaletteSize = dialog->paletteSize(); + settings->setColorPaletteSize(selectedPaletteSize); + + auto applyColors = [](UBColorListSetting* target, const QList& defaults, const QList& updated) + { + QList stored = target->colors(); + + QList normalized = stored; + for (int i = normalized.size(); i < UBSettings::maxColorPaletteSize; ++i) + { + normalized.append(defaults.value(i % defaults.size(), QColor(Qt::black))); + } + + if (normalized.size() > UBSettings::maxColorPaletteSize) + { + normalized = normalized.mid(0, UBSettings::maxColorPaletteSize); + } + + const int limit = qMin(normalized.size(), updated.size()); + for (int i = 0; i < limit; ++i) + { + normalized[i] = updated.at(i); + } + + target->setColors(normalized); + }; + + applyColors(settings->boardPenLightBackgroundSelectedColors, settings->defaultPenColors(false), dialog->penLightColors()); + applyColors(settings->boardPenDarkBackgroundSelectedColors, settings->defaultPenColors(true), dialog->penDarkColors()); + applyColors(settings->boardMarkerLightBackgroundSelectedColors, settings->defaultMarkerColors(false), dialog->markerLightColors()); + applyColors(settings->boardMarkerDarkBackgroundSelectedColors, settings->defaultMarkerColors(true), dialog->markerDarkColors()); + + const qreal markerAlpha = dialog->markerOpacity() / 100.0; + UBDrawingController::drawingController()->setMarkerAlpha(markerAlpha); + + updateColorButtonsForPaletteSize(); + UBDrawingController::drawingController()->refreshColorPalette(); +} + + qreal UBBoardController::currentZoom() const { if (mControlView) @@ -2384,71 +2297,35 @@ void UBBoardController::grabScene(const QRectF& pSceneRect) } } -UBGraphicsMediaItem* UBBoardController::addVideo(const QUrl& pSourceUrl, bool startPlay, const QPointF& pos, bool bUseSource) +UBGraphicsMediaItem* UBBoardController::addVideo(const QUrl& pSourceUrl, bool startPlay, const QPointF& pos) { QUuid uuid = QUuid::createUuid(); QUrl concreteUrl = pSourceUrl; - // media file is not in document folder yet - if (!bUseSource) - { - QString destFile; - bool b = UBPersistenceManager::persistenceManager()->addFileToDocument(selectedDocument(), - pSourceUrl.toLocalFile(), - UBPersistenceManager::videoDirectory, - uuid, - destFile); - if (!b) - { - UBApplication::showMessage(tr("Add file operation failed: file copying error")); - return NULL; - } - concreteUrl = QUrl::fromLocalFile(destFile); - }// else we just use source Url. - - UBGraphicsMediaItem* vi = mActiveScene->addMedia(concreteUrl, startPlay, pos); QDateTime now = QDateTime::currentDateTime(); selectedDocument()->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(now)); if (vi) { vi->setUuid(uuid); - vi->setSourceUrl(pSourceUrl); } return vi; } -UBGraphicsMediaItem* UBBoardController::addAudio(const QUrl& pSourceUrl, bool startPlay, const QPointF& pos, bool bUseSource) +UBGraphicsMediaItem* UBBoardController::addAudio(const QUrl& pSourceUrl, bool startPlay, const QPointF& pos) { QUuid uuid = QUuid::createUuid(); QUrl concreteUrl = pSourceUrl; - // media file is not in document folder yet - if (!bUseSource) - { - QString destFile; - bool b = UBPersistenceManager::persistenceManager()->addFileToDocument(selectedDocument(), - pSourceUrl.toLocalFile(), - UBPersistenceManager::audioDirectory, - uuid, - destFile); - if (!b) - { - UBApplication::showMessage(tr("Add file operation failed: file copying error")); - return NULL; - } - concreteUrl = QUrl::fromLocalFile(destFile); - }// else we just use source Url. - UBGraphicsMediaItem* ai = mActiveScene->addMedia(concreteUrl, startPlay, pos); QDateTime now = QDateTime::currentDateTime(); selectedDocument()->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(now)); - if (ai){ + if (ai) + { ai->setUuid(uuid); - ai->setSourceUrl(pSourceUrl); } return ai; @@ -2459,21 +2336,26 @@ UBGraphicsWidgetItem *UBBoardController::addW3cWidget(const QUrl &pUrl, const QP { UBGraphicsWidgetItem* w3cWidgetItem = 0; - QUuid uuid = QUuid::createUuid(); + QDir widgetDir{pUrl.toLocalFile()}; + + if (!widgetDir.exists()) + { + return nullptr; + } + + QUuid assetUuid = UBMediaAssetItem::createMediaAssetUuid(pUrl.toLocalFile()); QString destPath; - if (!UBPersistenceManager::persistenceManager()->addGraphicsWidgetToDocument(selectedDocument(), pUrl.toLocalFile(), uuid, destPath)) + if (!UBPersistenceManager::persistenceManager()->addGraphicsWidgetToDocument(selectedDocument(), pUrl.toLocalFile(), assetUuid, destPath)) return NULL; QUrl newUrl = QUrl::fromLocalFile(destPath); w3cWidgetItem = mActiveScene->addW3CWidget(newUrl, pos); if (w3cWidgetItem) { - w3cWidgetItem->setUuid(uuid); w3cWidgetItem->setOwnFolder(newUrl); - w3cWidgetItem->setSourceUrl(pUrl); - QString struuid = UBStringUtils::toCanonicalUuid(uuid); + QString struuid = w3cWidgetItem->uuid().toString(QUuid::WithoutBraces); QString snapshotPath = selectedDocument()->persistencePath() + "/" + UBPersistenceManager::widgetDirectory + "/" + struuid + ".png"; w3cWidgetItem->setSnapshotPath(QUrl::fromLocalFile(snapshotPath)); } @@ -2758,45 +2640,6 @@ void UBBoardController::moveToolWidgetToScene(UBToolWidget* toolWidget) } -void UBBoardController::updateBackgroundActionsState(bool isDark, UBPageBackground pageBackground) -{ - switch (pageBackground) { - - case UBPageBackground::crossed: - if (isDark) - mMainWindow->actionCrossedDarkBackground->setChecked(true); - else - mMainWindow->actionCrossedLightBackground->setChecked(true); - break; - - case UBPageBackground::ruled: - { - QAction* actionRuledBackground = nullptr; - if(UBSettings::settings()->isSeyesRuledBackground()) - if(isDark) - actionRuledBackground = mMainWindow->actionSeyesRuledDarkBackground; - else - actionRuledBackground = mMainWindow->actionSeyesRuledLightBackground; - else - if(isDark) - actionRuledBackground = mMainWindow->actionRuledDarkBackground; - else - actionRuledBackground = mMainWindow->actionRuledLightBackground; - if(actionRuledBackground) - actionRuledBackground->setChecked(true); - } - break; - - default: - if (isDark) - mMainWindow->actionPlainDarkBackground->setChecked(true); - else - mMainWindow->actionPlainLightBackground->setChecked(true); - break; - } -} - - void UBBoardController::addItem() { QString defaultPath = UBSettings::settings()->lastImportToLibraryPath->get().toString(); @@ -2822,7 +2665,7 @@ void UBBoardController::addItem() void UBBoardController::importPage() { - int pageCount = selectedDocument()->pageCount(); + int pageCount = activeDocument()->pageCount(); if (UBApplication::documentController->addFileToDocument(selectedDocument())) { setActiveDocumentScene(selectedDocument(), pageCount, true); diff --git a/src/board/UBBoardController.h b/src/board/UBBoardController.h index 82c0355c8..7cb5ad239 100644 --- a/src/board/UBBoardController.h +++ b/src/board/UBBoardController.h @@ -34,6 +34,7 @@ #include #include +#include #include #include "core/UB.h" @@ -44,6 +45,8 @@ class UBMainWindow; class UBApplication; class UBBoardView; +class UBBackgroundRuling; +class UBBackgroundManager; class UBDocument; class UBDocumentController; class UBMessageWindow; @@ -60,6 +63,8 @@ class UBGraphicsWidgetItem; class UBBoardPaletteManager; class UBItem; class UBGraphicsItem; +class UBToolbarButtonGroup; +class UBColorPreferencesDialog; class UBBoardController : public UBDocumentContainer @@ -82,7 +87,6 @@ class UBBoardController : public UBDocumentContainer std::shared_ptr activeScene() const; int activeSceneIndex() const; - void setActiveSceneIndex(int i); void closing(); int currentPage() const; @@ -102,6 +106,11 @@ class UBBoardController : public UBDocumentContainer return mDisplayView; } + UBBackgroundManager* backgroundManager() const + { + return mBgManager; + } + void setPenColorOnDarkBackground(const QColor& pColor) { if (mPenColorOnDarkBackground == pColor) @@ -181,7 +190,6 @@ class UBBoardController : public UBDocumentContainer void duplicateScene(int index); UBGraphicsItem *duplicateItem(UBItem *item); - void deleteScene(int index); bool cacheIsVisible() const {return mCacheWidgetIsEnabled;} @@ -200,7 +208,6 @@ class UBBoardController : public UBDocumentContainer void blackout(); void addScene(); void addScene(std::shared_ptr proxy, int sceneIndex, bool replaceActiveIfEmpty = false); - void addScene(std::shared_ptr scene, bool replaceActiveIfEmpty = false); void duplicateScene(); void importPage(); void clearScene(); @@ -222,7 +229,7 @@ class UBBoardController : public UBDocumentContainer UBItem *downloadFinished(bool pSuccess, QUrl sourceUrl, QUrl contentUrl, QString pHeader, QByteArray pData, QPointF pPos, QSize pSize, bool isBackground = false, bool internalData = false); - void changeBackground(bool isDark, UBPageBackground pageBackground); + void setBackground(bool isDark, const UBBackgroundRuling* background); void setToolCursor(int tool); void showMessage(const QString& message, bool showSpinningWheel = false); void hideMessage(); @@ -235,8 +242,8 @@ class UBBoardController : public UBDocumentContainer void setRegularPageSize(bool checked); void stylusToolChanged(int tool); void grabScene(const QRectF& pSceneRect); - UBGraphicsMediaItem* addVideo(const QUrl& pUrl, bool startPlay, const QPointF& pos, bool bUseSource = false); - UBGraphicsMediaItem* addAudio(const QUrl& pUrl, bool startPlay, const QPointF& pos, bool bUseSource = false); + UBGraphicsMediaItem* addVideo(const QUrl& pUrl, bool startPlay, const QPointF& pos); + UBGraphicsMediaItem* addAudio(const QUrl& pUrl, bool startPlay, const QPointF& pos); UBGraphicsWidgetItem *addW3cWidget(const QUrl& pUrl, const QPointF& pos); void adjustDisplayViews(); void cut(); @@ -254,9 +261,9 @@ class UBBoardController : public UBDocumentContainer void saveData(SaveFlags fls = sf_none); - void documentSceneDuplicated(std::shared_ptr proxy, int index); - void documentSceneMoved(std::shared_ptr proxy, int fromIndex, int toIndex); - void documentSceneDeleted(std::shared_ptr proxy, int index); + void documentSceneDuplicated(UBDocument* document, int index); + void documentSceneMoved(UBDocument* document, int fromIndex, int toIndex); + void documentSceneDeleted(UBDocument* document, int index); signals: void newPageAdded(); @@ -269,7 +276,6 @@ class UBBoardController : public UBDocumentContainer void documentReorganized(int index); void displayMetadata(QMap metadata); void pageSelectionChanged(int index); - void npapiWidgetCreated(const QString &Url); protected: void setupViews(); @@ -293,12 +299,16 @@ class UBBoardController : public UBDocumentContainer void initBackgroundGridSize(); void updatePageSizeState(); int autosaveTimeoutFromSettings() const; + void buildColorActions(); + void updateColorButtonsForPaletteSize(); + int currentToolColorIndex() const; UBMainWindow *mMainWindow; std::shared_ptr mActiveScene; int mActiveSceneIndex; int mSwitchToSceneIndex{-1}; UBBoardPaletteManager *mPaletteManager; + UBBackgroundManager* mBgManager{nullptr}; UBSoftwareUpdateDialog *mSoftwareUpdateDialog; UBMessageWindow *mMessageWindow; UBEmbedController *mEmbedController; @@ -312,6 +322,7 @@ class UBBoardController : public UBDocumentContainer QColor mPenColorOnLightBackground; QColor mMarkerColorOnDarkBackground; QColor mMarkerColorOnLightBackground; + QPointer mColorPreferencesDialog; qreal mSystemScaleFactor; bool mCleanupDone; QMap > mActionTexts; @@ -323,14 +334,17 @@ class UBBoardController : public UBDocumentContainer QString mActionUngroupText; std::shared_ptr mInitialDocumentScene; QList> mRecentDocuments; + QList mColorActions; + UBToolbarButtonGroup* mColorChoice{nullptr}; QTimer *mAutosaveTimer; private slots: void stylusToolDoubleClicked(int tool); void boardViewResized(QResizeEvent* event); - void updateBackgroundActionsState(bool isDark, UBPageBackground pageBackground); void colorPaletteChanged(); + void openColorPreferencesDialog(); + void colorPreferencesDialogAccepted(); void libraryDialogClosed(int ret); void lastWindowClosed(); void onDownloadModalFinished(); diff --git a/src/board/UBBoardPaletteManager.cpp b/src/board/UBBoardPaletteManager.cpp index 71b895837..931748463 100644 --- a/src/board/UBBoardPaletteManager.cpp +++ b/src/board/UBBoardPaletteManager.cpp @@ -38,6 +38,8 @@ #include "core/UBSetting.h" #include "core/UBDisplayManager.h" +#include "document/UBDocument.h" + #include "gui/UBMainWindow.h" #include "gui/UBStylusPalette.h" #include "gui/UBKeyboardPalette.h" @@ -54,7 +56,6 @@ #include "desktop/UBDesktopAnnotationController.h" - #include "network/UBNetworkAccessManager.h" #include "network/UBServerXMLHttpRequest.h" @@ -119,6 +120,7 @@ UBBoardPaletteManager::~UBBoardPaletteManager() void UBBoardPaletteManager::initPalettesPosAtStartup() { mStylusPalette->initPosition(); + updateLeftDockPaletteOffset(); } void UBBoardPaletteManager::setupLayout() @@ -245,22 +247,8 @@ void UBBoardPaletteManager::setupPalettes() mStylusPalette->stackUnder(mZoomPalette); mTipPalette = new UBStartupHintsPalette(mContainer); - QList backgroundsActions; - - backgroundsActions << UBApplication::mainWindow->actionPlainLightBackground; - backgroundsActions << UBApplication::mainWindow->actionCrossedLightBackground; - if(UBSettings::settings()->isSeyesRuledBackground()) - backgroundsActions << UBApplication::mainWindow->actionSeyesRuledLightBackground; - else - backgroundsActions << UBApplication::mainWindow->actionRuledLightBackground; - backgroundsActions << UBApplication::mainWindow->actionPlainDarkBackground; - backgroundsActions << UBApplication::mainWindow->actionCrossedDarkBackground; - if(UBSettings::settings()->isSeyesRuledBackground()) - backgroundsActions << UBApplication::mainWindow->actionSeyesRuledDarkBackground; - else - backgroundsActions << UBApplication::mainWindow->actionRuledDarkBackground; - mBackgroundsPalette = new UBBackgroundPalette(backgroundsActions, mContainer); + mBackgroundsPalette = new UBBackgroundPalette(mContainer); mBackgroundsPalette->setButtonIconSize(QSize(128, 128)); mBackgroundsPalette->groupActions(); mBackgroundsPalette->setClosable(true); @@ -275,6 +263,7 @@ void UBBoardPaletteManager::setupPalettes() addItemActions << UBApplication::mainWindow->actionAddItemToLibrary; mAddItemPalette = new UBActionPalette(addItemActions, Qt::Horizontal, mContainer); + mAddItemPalette->setObjectName("AddItemPalette"); mAddItemPalette->setButtonIconSize(QSize(128, 128)); mAddItemPalette->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); mAddItemPalette->groupActions(); @@ -450,14 +439,6 @@ void UBBoardPaletteManager::connectPalettes() connect(UBApplication::mainWindow->actionBackgrounds, SIGNAL(toggled(bool)), this, SLOT(toggleBackgroundPalette(bool))); connect(mBackgroundsPalette, SIGNAL(closed()), this, SLOT(backgroundPaletteClosed())); - connect(UBApplication::mainWindow->actionPlainLightBackground, SIGNAL(triggered()), this, SLOT(changeBackground())); - connect(UBApplication::mainWindow->actionCrossedLightBackground, SIGNAL(triggered()), this, SLOT(changeBackground())); - connect(UBApplication::mainWindow->actionRuledLightBackground, SIGNAL(triggered()), this, SLOT(changeBackground())); - connect(UBApplication::mainWindow->actionSeyesRuledLightBackground, SIGNAL(triggered()), this, SLOT(changeBackground())); - connect(UBApplication::mainWindow->actionPlainDarkBackground, SIGNAL(triggered()), this, SLOT(changeBackground())); - connect(UBApplication::mainWindow->actionCrossedDarkBackground, SIGNAL(triggered()), this, SLOT(changeBackground())); - connect(UBApplication::mainWindow->actionRuledDarkBackground, SIGNAL(triggered()), this, SLOT(changeBackground())); - connect(UBApplication::mainWindow->actionSeyesRuledDarkBackground, SIGNAL(triggered()), this, SLOT(changeBackground())); connect(UBApplication::mainWindow->actionPodcast, SIGNAL(triggered(bool)), this, SLOT(tooglePodcastPalette(bool))); connect(UBApplication::mainWindow->actionAddItemToCurrentPage, SIGNAL(triggered()), this, SLOT(addItemToCurrentPage())); @@ -521,6 +502,7 @@ void UBBoardPaletteManager::containerResized() mStylusPalette->move(userLeft, userTop); mStylusPalette->adjustSizeAndPosition(); mStylusPalette->initPosition(); + updateLeftDockPaletteOffset(); } if(mZoomPalette) @@ -541,32 +523,6 @@ void UBBoardPaletteManager::containerResized() } -void UBBoardPaletteManager::changeBackground() -{ - if (UBApplication::mainWindow->actionCrossedLightBackground->isChecked()) - UBApplication::boardController->changeBackground(false, UBPageBackground::crossed); - - else if (UBApplication::mainWindow->actionRuledLightBackground->isChecked() || - UBApplication::mainWindow->actionSeyesRuledLightBackground->isChecked()) - UBApplication::boardController->changeBackground(false, UBPageBackground::ruled); - - else if (UBApplication::mainWindow->actionPlainDarkBackground->isChecked()) - UBApplication::boardController->changeBackground(true, UBPageBackground::plain); - - else if (UBApplication::mainWindow->actionCrossedDarkBackground->isChecked()) - UBApplication::boardController->changeBackground(true, UBPageBackground::crossed); - - else if (UBApplication::mainWindow->actionRuledDarkBackground->isChecked() || - UBApplication::mainWindow->actionSeyesRuledDarkBackground->isChecked()) - UBApplication::boardController->changeBackground(true, UBPageBackground::ruled); - - else - UBApplication::boardController->changeBackground(false, UBPageBackground::plain); - - mBackgroundsPalette->backgroundChanged(); -} - - void UBBoardPaletteManager::activeSceneChanged() { std::shared_ptr activeScene = UBApplication::boardController->activeScene(); @@ -577,7 +533,7 @@ void UBBoardPaletteManager::activeSceneChanged() if (mpPageNavigWidget) { - mpPageNavigWidget->setPageNumber(UBDocumentContainer::pageFromSceneIndex(pageIndex), activeScene->document()->pageCount()); + mpPageNavigWidget->setPageNumber(UBDocumentContainer::pageFromSceneIndex(pageIndex), UBApplication::boardController->activeDocument()->pageCount()); } if (mZoomPalette) @@ -615,6 +571,7 @@ void UBBoardPaletteManager::backgroundPaletteClosed() void UBBoardPaletteManager::toggleStylusPalette(bool checked) { mStylusPalette->setVisible(checked); + updateLeftDockPaletteOffset(); } @@ -872,17 +829,15 @@ void UBBoardPaletteManager::addItemToCurrentPage() { UBApplication::applicationController->showBoard(); mAddItemPalette->hide(); - if(mPixmap.isNull()) + + if (mPixmap.isNull()) + { UBApplication::boardController->downloadURL(mItemUrl); + } else { UBGraphicsPixmapItem* item = UBApplication::boardController->activeScene()->addPixmap(mPixmap, NULL, mPos, mScaleFactor); - QString documentPath = UBApplication::boardController->selectedDocument()->persistencePath(); - QString fileName = UBPersistenceManager::imageDirectory + "/" + item->uuid().toString() + ".png"; - QString path = documentPath + "/" + fileName; - - item->setSourceUrl(QUrl(path)); item->setSelected(true); UBDrawingController::drawingController()->setStylusTool(UBStylusTool::Selector); @@ -1011,6 +966,8 @@ void UBBoardPaletteManager::changeStylusPaletteOrientation(QVariant var) connect(mStylusPalette, SIGNAL(stylusToolDoubleClicked(int)), UBApplication::boardController, SLOT(stylusToolDoubleClicked(int))); mStylusPalette->setVisible(bVisible); // always show stylus palette at startup + mStylusPalette->initPosition(); + updateLeftDockPaletteOffset(); } @@ -1039,3 +996,25 @@ void UBBoardPaletteManager::stopDownloads() mRightPalette->removeTab(mpDownloadWidget); } } + +int UBBoardPaletteManager::leftDockPaletteOffset() const +{ + if (!mStylusPalette) + return 0; + + if (!mStylusPalette->isVisible()) + return 0; + + if (!UBSettings::settings()->appToolBarOrientationVertical->get().toBool()) + return 0; + + return mStylusPalette->geometry().right() + 1; +} + +void UBBoardPaletteManager::updateLeftDockPaletteOffset() +{ + if (!mLeftPalette) + return; + + mLeftPalette->setAdditionalLeftOffset(leftDockPaletteOffset()); +} diff --git a/src/board/UBBoardPaletteManager.h b/src/board/UBBoardPaletteManager.h index e7434534f..5d4f07c88 100644 --- a/src/board/UBBoardPaletteManager.h +++ b/src/board/UBBoardPaletteManager.h @@ -102,6 +102,8 @@ class UBBoardPaletteManager : public QObject void connectPalettes(); void positionFreeDisplayPalette(); void setupDockPaletteWidgets(); + int leftDockPaletteOffset() const; + void updateLeftDockPaletteOffset(); QWidget* mContainer; UBBoardController *mBoardControler; @@ -153,8 +155,6 @@ class UBBoardPaletteManager : public QObject private slots: - void changeBackground(); - void toggleBackgroundPalette(bool checked); void backgroundPaletteClosed(); diff --git a/src/board/UBBoardView.cpp b/src/board/UBBoardView.cpp index 74b41215c..77df2b5d4 100644 --- a/src/board/UBBoardView.cpp +++ b/src/board/UBBoardView.cpp @@ -1105,11 +1105,6 @@ void UBBoardView::mousePressEvent (QMouseEvent *event) mIsDragInProgress = false; - if (isAbsurdPoint (event->pos ())) { - event->accept (); - return; - } - setMultiselection(event->modifiers() & Qt::ControlModifier); #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) @@ -1118,7 +1113,7 @@ void UBBoardView::mousePressEvent (QMouseEvent *event) QPointF eventPosition = event->localPos(); #endif mMouseDownPos = eventPosition.toPoint(); - + setMovingItem(scene()->itemAt(this->mapToScene(eventPosition.toPoint()), QTransform())); if (event->button () == Qt::LeftButton && isInteractive()) @@ -1258,11 +1253,6 @@ void UBBoardView::mouseMoveEvent (QMouseEvent *event) mWidgetMoved = true; mLongPressTimer.stop(); - if (isAbsurdPoint (event->pos ())) { - event->accept (); - return; - } - if ((UBDrawingController::drawingController()->isDrawingTool()) && !mMouseButtonIsPressed) QGraphicsView::mouseMoveEvent(event); @@ -1980,14 +1970,6 @@ void UBBoardView::virtualKeyboardActivated(bool b) setInteractive(!b); } - -// Apple remote desktop sends funny events when the transmission is bad - -bool UBBoardView::isAbsurdPoint(QPoint point) -{ - return QGuiApplication::screenAt(mapToGlobal(point)) == nullptr; -} - void UBBoardView::focusOutEvent (QFocusEvent * event) { Q_UNUSED (event); diff --git a/src/board/UBBoardView.h b/src/board/UBBoardView.h index 5b722b9d8..5454ce09a 100644 --- a/src/board/UBBoardView.h +++ b/src/board/UBBoardView.h @@ -159,8 +159,6 @@ class UBBoardView : public QGraphicsView bool mIsCreatingTextZone; bool mIsCreatingSceneGrabZone; - bool isAbsurdPoint(QPoint point); - bool mVirtualKeyboardActive; bool mOkOnWidget; diff --git a/src/board/UBColorPreferencesDialog.cpp b/src/board/UBColorPreferencesDialog.cpp new file mode 100644 index 000000000..c41f37695 --- /dev/null +++ b/src/board/UBColorPreferencesDialog.cpp @@ -0,0 +1,729 @@ +/* + * Copyright (C) 2015-2022 Département de l'Instruction Publique (DIP-SEM) + * + * Copyright (C) 2013 Open Education Foundation + * + * 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 + * 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, + * 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 OpenBoard. If not, see . + */ + +#include "UBColorPreferencesDialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core/UBApplication.h" +#include "core/UBSettings.h" +#include "gui/UBMainWindow.h" + + +UBColorPreferencesDialog::UBColorPreferencesDialog(QWidget* parent, + int paletteSize, + const QList& penLightColors, + const QList& penDarkColors, + const QList& markerLightColors, + const QList& markerDarkColors, + int markerOpacityPercent) + : QDialog(parent) + , mPenLightColors(penLightColors) + , mPenDarkColors(penDarkColors) + , mMarkerLightColors(opaqueColors(markerLightColors)) + , mMarkerDarkColors(opaqueColors(markerDarkColors)) + , mDefaultPenLightColors(UBSettings::settings()->defaultPenColors(false)) + , mDefaultPenDarkColors(UBSettings::settings()->defaultPenColors(true)) + , mDefaultMarkerLightColors(opaqueColors(UBSettings::settings()->defaultMarkerColors(false))) + , mDefaultMarkerDarkColors(opaqueColors(UBSettings::settings()->defaultMarkerColors(true))) + , mDefaultMarkerOpacityPercent(qBound(1, qRound(UBSettings::settings()->boardMarkerAlpha->defaultValue().toDouble() * 100.0), 100)) + , mMinPaletteSize(UBSettings::minColorPaletteSize) + , mMaxPaletteSize(UBSettings::maxColorPaletteSize) +{ + setModal(true); + setWindowTitle(tr("Color Preferences")); + setMinimumWidth(520); + setObjectName(QStringLiteral("colorPreferencesDialog")); + + const int boundedPaletteSize = qBound(mMinPaletteSize, paletteSize, mMaxPaletteSize); + + ensureSize(mPenLightColors, mDefaultPenLightColors); + ensureSize(mPenDarkColors, mDefaultPenDarkColors); + ensureSize(mMarkerLightColors, mDefaultMarkerLightColors); + ensureSize(mMarkerDarkColors, mDefaultMarkerDarkColors); + + QVBoxLayout* mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(20, 18, 20, 18); + mainLayout->setSpacing(16); + + const QString paletteSizeToolTip = tr("This setting applies to the pen and marker palettes."); + + QWidget* sizeWidget = new QWidget(this); + sizeWidget->setObjectName(QStringLiteral("colorPreferencesHeader")); + QHBoxLayout* sizeLayout = new QHBoxLayout(sizeWidget); + sizeLayout->setContentsMargins(14, 12, 14, 12); + sizeLayout->setSpacing(12); + + QLabel* sizeLabel = new QLabel(tr("Palette size"), sizeWidget); + sizeLabel->setObjectName(QStringLiteral("colorPreferencesHeaderLabel")); + sizeLabel->setToolTip(paletteSizeToolTip); + mPaletteSizeSlider = new QSlider(Qt::Horizontal, sizeWidget); + mPaletteSizeSlider->setObjectName(QStringLiteral("colorPreferencesPaletteSizeSlider")); + mPaletteSizeSlider->setRange(mMinPaletteSize, mMaxPaletteSize); + mPaletteSizeSlider->setSingleStep(1); + mPaletteSizeSlider->setPageStep(1); + mPaletteSizeSlider->setTickInterval(1); + mPaletteSizeSlider->setTickPosition(QSlider::TicksBelow); + mPaletteSizeSlider->setValue(boundedPaletteSize); + mPaletteSizeSlider->setToolTip(paletteSizeToolTip); + connect(mPaletteSizeSlider, SIGNAL(valueChanged(int)), this, SLOT(paletteSizeChanged(int))); + mPaletteSizeValue = new QLabel(QString::number(boundedPaletteSize), sizeWidget); + mPaletteSizeValue->setObjectName(QStringLiteral("colorPreferencesPaletteSizeValue")); + mPaletteSizeValue->setAlignment(Qt::AlignCenter); + mPaletteSizeValue->setToolTip(paletteSizeToolTip); + sizeLayout->addWidget(sizeLabel); + sizeLayout->addWidget(mPaletteSizeSlider, 1); + sizeLayout->addWidget(mPaletteSizeValue); + mainLayout->addWidget(sizeWidget); + + mTabWidget = new QTabWidget(this); + mTabWidget->setObjectName(QStringLiteral("colorPreferencesTabWidget")); + mTabWidget->setIconSize(QSize(18, 18)); + + QWidget* penTab = createToolTab(tr("Light background"), + tr("Dark background"), + mPenLightFrame, + mPenDarkFrame, + mPenLightShortcutLabels, + mPenDarkShortcutLabels, + mPenLightButtons, + mPenDarkButtons, + mPenLightColors, + mPenDarkColors, + QStringLiteral("penLight"), + QStringLiteral("penDark"), + mResetPenButton); + penTab->setObjectName(QStringLiteral("colorPreferencesPenTab")); + mResetPenButton->setText(tr("Reset pen")); + mResetPenButton->setObjectName(QStringLiteral("colorPreferencesResetButton")); + connect(mResetPenButton, SIGNAL(clicked()), this, SLOT(resetPenSettings())); + mTabWidget->addTab(penTab, tr("Pen")); + + QWidget* markerTab = createToolTab(tr("Light background"), + tr("Dark background"), + mMarkerLightFrame, + mMarkerDarkFrame, + mMarkerLightShortcutLabels, + mMarkerDarkShortcutLabels, + mMarkerLightButtons, + mMarkerDarkButtons, + mMarkerLightColors, + mMarkerDarkColors, + QStringLiteral("markerLight"), + QStringLiteral("markerDark"), + mResetMarkerButton); + markerTab->setObjectName(QStringLiteral("colorPreferencesMarkerTab")); + mResetMarkerButton->setText(tr("Reset marker")); + mResetMarkerButton->setObjectName(QStringLiteral("colorPreferencesResetButton")); + connect(mResetMarkerButton, SIGNAL(clicked()), this, SLOT(resetMarkerSettings())); + + QVBoxLayout* markerLayout = qobject_cast(markerTab->layout()); + if (markerLayout) + { + QHBoxLayout* opacityLayout = new QHBoxLayout(); + QLabel* opacityLabel = new QLabel(tr("Marker opacity"), markerTab); + opacityLabel->setObjectName(QStringLiteral("colorPreferencesSectionTitle")); + mMarkerOpacitySlider = new QSlider(Qt::Horizontal, markerTab); + mMarkerOpacitySlider->setObjectName(QStringLiteral("colorPreferencesOpacitySlider")); + mMarkerOpacitySlider->setRange(1, 100); + mMarkerOpacitySlider->setValue(qBound(1, markerOpacityPercent, 100)); + connect(mMarkerOpacitySlider, SIGNAL(valueChanged(int)), this, SLOT(markerOpacityChanged(int))); + mMarkerOpacityValue = new QLabel(markerTab); + mMarkerOpacityValue->setObjectName(QStringLiteral("colorPreferencesOpacityValue")); + mMarkerOpacityValue->setMinimumWidth(40); + opacityLayout->addWidget(opacityLabel); + opacityLayout->addWidget(mMarkerOpacitySlider); + opacityLayout->addWidget(mMarkerOpacityValue); + markerLayout->insertLayout(markerLayout->count() - 2, opacityLayout); + } + + mTabWidget->addTab(markerTab, tr("Marker")); + connect(mTabWidget, SIGNAL(currentChanged(int)), this, SLOT(updateTabIcons(int))); + updateTabIcons(mTabWidget->currentIndex()); + mainLayout->addWidget(mTabWidget); + + QWidget* hintWidget = new QWidget(this); + hintWidget->setObjectName(QStringLiteral("colorPreferencesHint")); + QHBoxLayout* hintLayout = new QHBoxLayout(hintWidget); + hintLayout->setContentsMargins(14, 10, 14, 10); + hintLayout->setSpacing(10); + + QLabel* hintIcon = new QLabel(hintWidget); + hintIcon->setObjectName(QStringLiteral("colorPreferencesHintIcon")); + hintIcon->setPixmap(QIcon(QStringLiteral(":/images/toolbar/tip.png")).pixmap(20, 20)); + hintIcon->setAlignment(Qt::AlignTop | Qt::AlignHCenter); + + QLabel* hintLabel = new QLabel( + tr("When you switch between light and dark background in OpenBoard, each color is replaced by its counterpart so your annotations stay readable."), + hintWidget); + hintLabel->setObjectName(QStringLiteral("colorPreferencesHintText")); + hintLabel->setWordWrap(true); + + hintLayout->addWidget(hintIcon, 0, Qt::AlignTop); + hintLayout->addWidget(hintLabel, 1); + mainLayout->addWidget(hintWidget); + + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + buttons->setObjectName(QStringLiteral("colorPreferencesButtonBox")); + QPushButton* defaultSettingsButton = buttons->addButton(tr("Default settings"), QDialogButtonBox::ResetRole); + defaultSettingsButton->setObjectName(QStringLiteral("colorPreferencesResetButton")); + connect(defaultSettingsButton, SIGNAL(clicked()), this, SLOT(resetDefaultSettings())); + connect(buttons, SIGNAL(accepted()), this, SLOT(accept())); + connect(buttons, SIGNAL(rejected()), this, SLOT(reject())); + mainLayout->addWidget(buttons); + + updateBackgroundFrames(); + paletteSizeChanged(boundedPaletteSize); + markerOpacityChanged(mMarkerOpacitySlider->value()); +} + + +int UBColorPreferencesDialog::paletteSize() const +{ + return mPaletteSizeSlider->value(); +} + + +QList UBColorPreferencesDialog::penLightColors() const +{ + return mPenLightColors; +} + + +QList UBColorPreferencesDialog::penDarkColors() const +{ + return mPenDarkColors; +} + + +QList UBColorPreferencesDialog::markerLightColors() const +{ + return mMarkerLightColors; +} + + +QList UBColorPreferencesDialog::markerDarkColors() const +{ + return mMarkerDarkColors; +} + + +int UBColorPreferencesDialog::markerOpacity() const +{ + return mMarkerOpacitySlider->value(); +} + + +void UBColorPreferencesDialog::paletteSizeChanged(int value) +{ + Q_UNUSED(value); + + ensureSize(mPenLightColors, mDefaultPenLightColors); + ensureSize(mPenDarkColors, mDefaultPenDarkColors); + ensureSize(mMarkerLightColors, mDefaultMarkerLightColors); + ensureSize(mMarkerDarkColors, mDefaultMarkerDarkColors); + + const int visibleCount = mPaletteSizeSlider->value(); + if (mPaletteSizeValue) + mPaletteSizeValue->setText(QString::number(visibleCount)); + + auto setRowWidth = [visibleCount](QWidget* frame, const QList& buttons) + { + if (!frame) + return; + + const int buttonWidth = buttons.isEmpty() ? 0 : buttons.first()->width(); + int rowWidth = visibleCount * buttonWidth; + + if (QGridLayout* layout = qobject_cast(frame->layout())) + { + const QMargins margins = layout->contentsMargins(); + rowWidth += margins.left() + margins.right(); + rowWidth += qMax(0, visibleCount - 1) * layout->horizontalSpacing(); + } + + frame->setMinimumWidth(rowWidth); + frame->setMaximumWidth(rowWidth); + }; + + refreshPenSwatches(); + refreshMarkerSwatches(); + + setRowWidth(mPenLightFrame, mPenLightButtons); + setRowWidth(mPenDarkFrame, mPenDarkButtons); + setRowWidth(mMarkerLightFrame, mMarkerLightButtons); + setRowWidth(mMarkerDarkFrame, mMarkerDarkButtons); +} + + +void UBColorPreferencesDialog::penSwatchClicked() +{ + QToolButton* button = qobject_cast(sender()); + if (!button) + return; + + const QString role = button->property("role").toString(); + const int index = button->property("index").toInt(); + QList* colors = role == QLatin1String("penDark") ? &mPenDarkColors : &mPenLightColors; + + if (!colors || index < 0 || index >= colors->size()) + return; + + const QColor chosen = QColorDialog::getColor(colors->at(index), this, tr("Select Pen Color")); + if (!chosen.isValid()) + return; + + (*colors)[index] = chosen; + refreshPenSwatches(); +} + + +void UBColorPreferencesDialog::markerSwatchClicked() +{ + QToolButton* button = qobject_cast(sender()); + if (!button) + return; + + const QString role = button->property("role").toString(); + const int index = button->property("index").toInt(); + QList* colors = role == QLatin1String("markerDark") ? &mMarkerDarkColors : &mMarkerLightColors; + + if (!colors || index < 0 || index >= colors->size()) + return; + + QColor initialColor = colors->at(index); + initialColor.setAlpha(255); + + const QColor chosen = QColorDialog::getColor(initialColor, this, tr("Select Marker Color")); + if (!chosen.isValid()) + return; + + QColor opaqueChosen = chosen; + opaqueChosen.setAlpha(255); + (*colors)[index] = opaqueChosen; + refreshMarkerSwatches(); +} + + +void UBColorPreferencesDialog::markerOpacityChanged(int value) +{ + mMarkerOpacityValue->setText(QString::number(value) + QStringLiteral("%")); + refreshMarkerSwatches(); +} + + +void UBColorPreferencesDialog::resetPenSettings() +{ + mPenLightColors = mDefaultPenLightColors; + mPenDarkColors = mDefaultPenDarkColors; + + ensureSize(mPenLightColors, mDefaultPenLightColors); + ensureSize(mPenDarkColors, mDefaultPenDarkColors); + refreshPenSwatches(); +} + + +void UBColorPreferencesDialog::resetMarkerSettings() +{ + mMarkerLightColors = mDefaultMarkerLightColors; + mMarkerDarkColors = mDefaultMarkerDarkColors; + + ensureSize(mMarkerLightColors, mDefaultMarkerLightColors); + ensureSize(mMarkerDarkColors, mDefaultMarkerDarkColors); + mMarkerOpacitySlider->setValue(mDefaultMarkerOpacityPercent); + refreshMarkerSwatches(); +} + + +void UBColorPreferencesDialog::resetDefaultSettings() +{ + mPaletteSizeSlider->setValue(UBSettings::defaultColorPaletteSize); + resetPenSettings(); + resetMarkerSettings(); +} + + +QWidget* UBColorPreferencesDialog::createToolTab(const QString& lightTitle, + const QString& darkTitle, + QWidget*& lightFrame, + QWidget*& darkFrame, + QList& lightShortcutLabels, + QList& darkShortcutLabels, + QList& lightButtons, + QList& darkButtons, + const QList& lightColors, + const QList& darkColors, + const QString& lightRole, + const QString& darkRole, + QPushButton*& resetButton) +{ + QWidget* tab = new QWidget(this); + QVBoxLayout* tabLayout = new QVBoxLayout(tab); + tabLayout->setContentsMargins(8, 14, 8, 6); + tabLayout->setSpacing(14); + + buildColorSection(tabLayout, lightTitle, lightFrame, lightShortcutLabels, lightButtons, lightColors, lightRole); + buildColorSection(tabLayout, darkTitle, darkFrame, darkShortcutLabels, darkButtons, darkColors, darkRole); + + tabLayout->addStretch(); + + QHBoxLayout* resetLayout = new QHBoxLayout(); + resetLayout->addStretch(); + resetButton = new QPushButton(tab); + resetLayout->addWidget(resetButton); + tabLayout->addLayout(resetLayout); + + return tab; +} + + +void UBColorPreferencesDialog::buildColorSection(QVBoxLayout* parentLayout, + const QString& title, + QWidget*& frame, + QList& shortcutLabels, + QList& buttons, + const QList& colors, + const QString& role) +{ + QWidget* parentWidget = parentLayout ? parentLayout->parentWidget() : this; + QWidget* sectionWidget = new QWidget(parentWidget); + sectionWidget->setObjectName(QStringLiteral("colorPreferencesSection")); + QVBoxLayout* sectionLayout = new QVBoxLayout(sectionWidget); + sectionLayout->setContentsMargins(14, 12, 14, 12); + sectionLayout->setSpacing(8); + + QLabel* titleLabel = new QLabel(title, sectionWidget); + titleLabel->setObjectName(QStringLiteral("colorPreferencesSectionTitle")); + sectionLayout->addWidget(titleLabel); + + frame = new QFrame(sectionWidget); + frame->setObjectName(QStringLiteral("colorPreviewFrame")); + frame->setProperty("previewMode", role.endsWith(QLatin1String("Dark")) ? QStringLiteral("dark") : QStringLiteral("light")); + frame->setAutoFillBackground(true); + frame->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + + QGridLayout* layout = new QGridLayout(frame); + layout->setSizeConstraint(QLayout::SetFixedSize); + layout->setContentsMargins(10, 8, 10, 8); + layout->setHorizontalSpacing(0); + layout->setVerticalSpacing(4); + buildSwatches(layout, shortcutLabels, buttons, colors, role); + sectionLayout->addWidget(frame, 0, Qt::AlignHCenter); + parentLayout->addWidget(sectionWidget); +} + + +void UBColorPreferencesDialog::buildSwatches(QGridLayout* layout, + QList& shortcutLabels, + QList& buttons, + const QList& colors, + const QString& role) +{ + QWidget* parent = layout ? layout->parentWidget() : this; + + for (int i = 0; i < mMaxPaletteSize; ++i) + { + QLabel* shortcut = new QLabel(colorIndexLabel(i), parent); + shortcut->setObjectName(QStringLiteral("colorShortcutLabel")); + shortcut->setAlignment(Qt::AlignCenter); + shortcut->setFixedSize(32, 14); + shortcut->setToolTip(colorToolTip(i)); + shortcutLabels.append(shortcut); + + QToolButton* button = new QToolButton(parent); + button->setAutoRaise(false); + button->setIconSize(QSize(18, 18)); + button->setFixedSize(34, 30); + button->setProperty("index", i); + button->setProperty("role", role); + button->setToolTip(colorToolTip(i)); + buttons.append(button); + + if (layout) + { + layout->addWidget(shortcut, 0, i); + layout->addWidget(button, 1, i); + } + } + + const bool isMarkerRow = role.startsWith(QLatin1String("marker")); + + for (QToolButton* button : buttons) + { + if (isMarkerRow) + connect(button, SIGNAL(clicked()), this, SLOT(markerSwatchClicked())); + else + connect(button, SIGNAL(clicked()), this, SLOT(penSwatchClicked())); + } + + refreshSwatches(shortcutLabels, buttons, colors, mPaletteSizeSlider->value()); +} + + +void UBColorPreferencesDialog::refreshSwatches(QList& shortcutLabels, + QList& buttons, + const QList& colors, + int visibleCount) +{ + for (int i = 0; i < buttons.size(); ++i) + { + QLabel* shortcut = i < shortcutLabels.size() ? shortcutLabels.at(i) : nullptr; + QToolButton* button = buttons.at(i); + const bool visible = i < visibleCount; + + if (shortcut) + { + shortcut->setVisible(visible); + shortcut->setText(colorIndexLabel(i)); + shortcut->setToolTip(colorToolTip(i)); + } + + button->setVisible(visible); + button->setEnabled(visible); + button->setToolTip(colorToolTip(i)); + + if (visible) + { + QString objectName; + if (i == 0) + objectName = QStringLiteral("ubButtonGroupLeft"); + else if (i == visibleCount - 1) + objectName = QStringLiteral("ubButtonGroupRight"); + else + objectName = QStringLiteral("ubButtonGroupCenter"); + + if (button->objectName() != objectName) + { + button->setObjectName(objectName); + button->style()->unpolish(button); + button->style()->polish(button); + } + } + + if (i < colors.size()) + { + button->setIcon(swatchIcon(colors.at(i))); + } + } +} + + +void UBColorPreferencesDialog::refreshPenSwatches() +{ + const int visibleCount = mPaletteSizeSlider->value(); + refreshSwatches(mPenLightShortcutLabels, mPenLightButtons, mPenLightColors, visibleCount); + refreshSwatches(mPenDarkShortcutLabels, mPenDarkButtons, mPenDarkColors, visibleCount); +} + + +void UBColorPreferencesDialog::refreshMarkerSwatches() +{ + const int visibleCount = mPaletteSizeSlider->value(); + const QList markerLightWithOpacity = markerColorsWithOpacity(mMarkerLightColors); + const QList markerDarkWithOpacity = markerColorsWithOpacity(mMarkerDarkColors); + refreshSwatches(mMarkerLightShortcutLabels, mMarkerLightButtons, markerLightWithOpacity, visibleCount); + refreshSwatches(mMarkerDarkShortcutLabels, mMarkerDarkButtons, markerDarkWithOpacity, visibleCount); +} + + +void UBColorPreferencesDialog::ensureSize(QList& colors, const QList& defaults) +{ + if (defaults.isEmpty()) + return; + + for (int i = colors.size(); i < mMaxPaletteSize; ++i) + { + colors.append(defaults.value(i % defaults.size(), QColor(Qt::black))); + } + + if (colors.size() > mMaxPaletteSize) + { + colors = colors.mid(0, mMaxPaletteSize); + } +} + + +void UBColorPreferencesDialog::updateBackgroundFrames() +{ + auto applyBackground = [](QWidget* widget, const QList& labels, const QColor& background, const QColor& foreground) + { + if (!widget) + return; + + QPalette palette = widget->palette(); + palette.setColor(QPalette::Window, background); + palette.setColor(QPalette::WindowText, foreground); + widget->setPalette(palette); + + for (QLabel* label : labels) + { + if (!label) + continue; + + QPalette labelPalette = label->palette(); + labelPalette.setColor(QPalette::WindowText, foreground); + labelPalette.setColor(QPalette::Text, foreground); + label->setPalette(labelPalette); + } + }; + + applyBackground(mPenLightFrame, mPenLightShortcutLabels, QColor(Qt::white), QColor(Qt::black)); + applyBackground(mMarkerLightFrame, mMarkerLightShortcutLabels, QColor(Qt::white), QColor(Qt::black)); + applyBackground(mPenDarkFrame, mPenDarkShortcutLabels, QColor(Qt::black), QColor(Qt::white)); + applyBackground(mMarkerDarkFrame, mMarkerDarkShortcutLabels, QColor(Qt::black), QColor(Qt::white)); +} + + +QList UBColorPreferencesDialog::opaqueColors(const QList& colors) +{ + QList opaque; + + for (const auto& color : colors) + { + QColor opaqueColor = color; + opaqueColor.setAlpha(255); + opaque.append(opaqueColor); + } + + return opaque; +} + + +QIcon UBColorPreferencesDialog::swatchIcon(const QColor& color) +{ + const QSize size(12, 12); + QPixmap pixmap(size); + pixmap.fill(color); + return QIcon(pixmap); +} + + +QList UBColorPreferencesDialog::markerColorsWithOpacity(const QList& colors) const +{ + QList adjusted; + adjusted.reserve(colors.size()); + + const qreal opacityFactor = mMarkerOpacitySlider ? (mMarkerOpacitySlider->value() / 100.0) : 1.0; + + for (const QColor& color : colors) + { + QColor c = color; + c.setAlphaF(qBound(0.0, c.alphaF() * opacityFactor, 1.0)); + adjusted.append(c); + } + + return adjusted; +} + + +QString UBColorPreferencesDialog::colorIndexLabel(int index) const +{ + return QString::number(index + 1); +} + + +QAction* UBColorPreferencesDialog::colorActionForIndex(int index) const +{ + if (!UBApplication::mainWindow) + return nullptr; + + switch (index) + { + case 0: return UBApplication::mainWindow->actionColor0; + case 1: return UBApplication::mainWindow->actionColor1; + case 2: return UBApplication::mainWindow->actionColor2; + case 3: return UBApplication::mainWindow->actionColor3; + case 4: return UBApplication::mainWindow->actionColor4; + case 5: return UBApplication::mainWindow->actionColor5; + case 6: return UBApplication::mainWindow->actionColor6; + case 7: return UBApplication::mainWindow->actionColor7; + case 8: return UBApplication::mainWindow->actionColor8; + case 9: return UBApplication::mainWindow->actionColor9; + default: return nullptr; + } +} + + +QString UBColorPreferencesDialog::shortcutTextForIndex(int index) const +{ + QAction* action = colorActionForIndex(index); + if (!action) + return QString(); + + QStringList shortcutTexts; + for (const auto& shortcut : action->shortcuts()) + { + const QString text = shortcut.toString(QKeySequence::NativeText); + if (!text.isEmpty()) + shortcutTexts.append(text); + } + + return shortcutTexts.join(QStringLiteral(", ")); +} + + +QString UBColorPreferencesDialog::colorToolTip(int index) const +{ + const QString shortcutText = shortcutTextForIndex(index); + if (shortcutText.isEmpty()) + return tr("Color %1").arg(index + 1); + + return tr("Shortcut: %1").arg(shortcutText); +} + + +void UBColorPreferencesDialog::updateTabIcons(int currentIndex) +{ + const int penIndex = 0; + const int markerIndex = 1; + + if (!mTabWidget || mTabWidget->count() <= markerIndex) + return; + + mTabWidget->setTabIcon(penIndex, QIcon(currentIndex == penIndex + ? QStringLiteral(":/images/stylusPalette/penOn.svg") + : QStringLiteral(":/images/stylusPalette/pen.svg"))); + + mTabWidget->setTabIcon(markerIndex, QIcon(currentIndex == markerIndex + ? QStringLiteral(":/images/stylusPalette/markerOn.svg") + : QStringLiteral(":/images/stylusPalette/marker.svg"))); +} diff --git a/src/board/UBColorPreferencesDialog.h b/src/board/UBColorPreferencesDialog.h new file mode 100644 index 000000000..1386fa8bd --- /dev/null +++ b/src/board/UBColorPreferencesDialog.h @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2015-2022 Département de l'Instruction Publique (DIP-SEM) + * + * Copyright (C) 2013 Open Education Foundation + * + * 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 + * 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, + * 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 OpenBoard. If not, see . + */ + + + + +#ifndef UBCOLORPREFERENCESDIALOG_H +#define UBCOLORPREFERENCESDIALOG_H + +#include +#include +#include +#include + +class QWidget; +class QGridLayout; +class QLabel; +class QVBoxLayout; +class QSlider; +class QTabWidget; +class QToolButton; +class QPushButton; +class QAction; +class QIcon; + +class UBColorPreferencesDialog : public QDialog +{ + Q_OBJECT + + public: + UBColorPreferencesDialog(QWidget* parent, + int paletteSize, + const QList& penLightColors, + const QList& penDarkColors, + const QList& markerLightColors, + const QList& markerDarkColors, + int markerOpacityPercent); + + int paletteSize() const; + QList penLightColors() const; + QList penDarkColors() const; + QList markerLightColors() const; + QList markerDarkColors() const; + int markerOpacity() const; + + private slots: + void paletteSizeChanged(int value); + void penSwatchClicked(); + void markerSwatchClicked(); + void markerOpacityChanged(int value); + void resetPenSettings(); + void resetMarkerSettings(); + void resetDefaultSettings(); + void updateTabIcons(int currentIndex); + + private: + QWidget* createToolTab(const QString& lightTitle, + const QString& darkTitle, + QWidget*& lightFrame, + QWidget*& darkFrame, + QList& lightShortcutLabels, + QList& darkShortcutLabels, + QList& lightButtons, + QList& darkButtons, + const QList& lightColors, + const QList& darkColors, + const QString& lightRole, + const QString& darkRole, + QPushButton*& resetButton); + void buildColorSection(QVBoxLayout* parentLayout, + const QString& title, + QWidget*& frame, + QList& shortcutLabels, + QList& buttons, + const QList& colors, + const QString& role); + void buildSwatches(QGridLayout* layout, + QList& shortcutLabels, + QList& buttons, + const QList& colors, + const QString& role); + void refreshSwatches(QList& shortcutLabels, QList& buttons, const QList& colors, int visibleCount); + void refreshPenSwatches(); + void refreshMarkerSwatches(); + void ensureSize(QList& colors, const QList& defaults); + void updateBackgroundFrames(); + static QIcon swatchIcon(const QColor& color); + static QList opaqueColors(const QList& colors); + QList markerColorsWithOpacity(const QList& colors) const; + QString colorIndexLabel(int index) const; + QAction* colorActionForIndex(int index) const; + QString shortcutTextForIndex(int index) const; + QString colorToolTip(int index) const; + + QSlider* mPaletteSizeSlider{nullptr}; + QLabel* mPaletteSizeValue{nullptr}; + QTabWidget* mTabWidget{nullptr}; + QSlider* mMarkerOpacitySlider{nullptr}; + QLabel* mMarkerOpacityValue{nullptr}; + QWidget* mPenLightFrame{nullptr}; + QWidget* mPenDarkFrame{nullptr}; + QWidget* mMarkerLightFrame{nullptr}; + QWidget* mMarkerDarkFrame{nullptr}; + QPushButton* mResetPenButton{nullptr}; + QPushButton* mResetMarkerButton{nullptr}; + QList mPenLightShortcutLabels; + QList mPenDarkShortcutLabels; + QList mMarkerLightShortcutLabels; + QList mMarkerDarkShortcutLabels; + QList mPenLightButtons; + QList mPenDarkButtons; + QList mMarkerLightButtons; + QList mMarkerDarkButtons; + QList mPenLightColors; + QList mPenDarkColors; + QList mMarkerLightColors; + QList mMarkerDarkColors; + QList mDefaultPenLightColors; + QList mDefaultPenDarkColors; + QList mDefaultMarkerLightColors; + QList mDefaultMarkerDarkColors; + const int mDefaultMarkerOpacityPercent; + const int mMinPaletteSize; + const int mMaxPaletteSize; +}; + + + +#endif // UBCOLORPREFERENCESDIALOG_H diff --git a/src/board/UBDrawingController.cpp b/src/board/UBDrawingController.cpp index 3af026399..130d61adb 100644 --- a/src/board/UBDrawingController.cpp +++ b/src/board/UBDrawingController.cpp @@ -283,7 +283,11 @@ QColor UBDrawingController::toolColor(bool onDarkBackground) void UBDrawingController::setColorIndex(int index) { - Q_ASSERT(index >= 0 && index < UBSettings::settings()->colorPaletteSize); + int paletteSize = UBSettings::settings()->colorPaletteSize; + if (paletteSize <= 0) + return; + + index = qBound(0, index, paletteSize - 1); if (stylusTool() == UBStylusTool::Marker) { @@ -347,6 +351,12 @@ void UBDrawingController::setMarkerAlpha(qreal alpha) emit colorPaletteChanged(); } + +void UBDrawingController::refreshColorPalette() +{ + emit colorPaletteChanged(); +} + void UBDrawingController::setActiveRuler(UBAbstractDrawRuler* ruler) { mActiveRuler = ruler; @@ -445,4 +455,3 @@ void UBDrawingController::captureToolSelected(bool checked) if (checked) setStylusTool(UBStylusTool::Capture); } - diff --git a/src/board/UBDrawingController.h b/src/board/UBDrawingController.h index a7affa6ed..8286938c1 100644 --- a/src/board/UBDrawingController.h +++ b/src/board/UBDrawingController.h @@ -82,6 +82,7 @@ class UBDrawingController : public QObject void setLineWidthIndex(int index); void setColorIndex(int index); void setEraserWidthIndex(int index); + void refreshColorPalette(); signals: void stylusToolChanged(int tool, int previousTool = -1); diff --git a/src/board/UBFeaturesController.cpp b/src/board/UBFeaturesController.cpp index c93d9e9ae..000968124 100644 --- a/src/board/UBFeaturesController.cpp +++ b/src/board/UBFeaturesController.cpp @@ -31,24 +31,26 @@ #include #include -#include "core/UBApplication.h" -#include "board/UBBoardController.h" -#include "document/UBDocumentController.h" #include "UBFeaturesController.h" -#include "core/UBSettings.h" -#include "tools/UBToolsManager.h" -#include "frameworks/UBFileSystemUtils.h" -#include "frameworks/UBPlatformUtils.h" -#include "core/UBDownloadManager.h" +#include "board/UBBoardController.h" + +#include "core/UBApplication.h" #include "core/UBPersistenceManager.h" +#include "core/UBSettings.h" + +#include "document/UBDocumentController.h" + #include "domain/UBGraphicsScene.h" -#include "domain/UBGraphicsSvgItem.h" -#include "domain/UBGraphicsPixmapItem.h" #include "domain/UBGraphicsWidgetItem.h" +#include "frameworks/UBFileSystemUtils.h" + #include "gui/UBFeaturesWidget.h" +#include "tools/UBToolsManager.h" + + const QString UBFeaturesController::virtualRootName = "root"; const QString UBFeaturesController::rootPath = "/" + virtualRootName; @@ -56,7 +58,6 @@ const QString UBFeaturesController::appPath = rootPath + "/Applications"; const QString UBFeaturesController::audiosPath = rootPath + "/Audios"; const QString UBFeaturesController::moviesPath = rootPath + "/Movies"; const QString UBFeaturesController::picturesPath = rootPath + "/Pictures"; -const QString UBFeaturesController::flashPath = rootPath + "/Animations"; const QString UBFeaturesController::interactPath = rootPath + "/Interactivities"; const QString UBFeaturesController::shapesPath = rootPath + "/Shapes"; const QString UBFeaturesController::trashPath = rootPath + "/Trash"; @@ -299,7 +300,6 @@ bool UBFeature::isDeletable() const || elementType == FEATURE_AUDIO || elementType == FEATURE_VIDEO || elementType == FEATURE_IMAGE - || elementType == FEATURE_FLASH || elementType == FEATURE_INTERACTIVE || elementType == FEATURE_FOLDER //Ilia. Just a hotfix. Permission mechanism for UBFeatures should be reworked @@ -321,12 +321,10 @@ UBFeaturesController::UBFeaturesController(QWidget *pParentWidget) : mUserVideoDirectoryPath = QUrl::fromLocalFile(UBSettings::settings()->userVideoDirectory()); mUserPicturesDirectoryPath = QUrl::fromLocalFile(UBSettings::settings()->userImageDirectory()); mUserInteractiveDirectoryPath = QUrl::fromLocalFile(UBSettings::settings()->userInteractiveDirectory()); - mUserAnimationDirectoryPath = QUrl::fromLocalFile(UBSettings::settings()->userAnimationDirectory()); mLibPicturesDirectoryPath = QUrl::fromLocalFile(UBSettings::settings()->applicationImageLibraryDirectory()); mLibAudiosDirectoryPath = QUrl::fromLocalFile(UBSettings::settings()->applicationAudiosLibraryDirectory()); mLibVideosDirectoryPath = QUrl::fromLocalFile(UBSettings::settings()->applicationVideosLibraryDirectory()); - mLibAnimationsDirectoryPath = QUrl::fromLocalFile(UBSettings::settings()->applicationAnimationsLibraryDirectory()); mLibInteractiveDirectoryPath = QUrl::fromLocalFile(UBSettings::settings()->applicationInteractivesDirectory()); mLibApplicationsDirectoryPath = QUrl::fromLocalFile(UBSettings::settings()->applicationApplicationsLibraryDirectory()); mLibShapesDirectoryPath = QUrl::fromLocalFile(UBSettings::settings()->applicationShapeLibraryDirectory()); @@ -337,7 +335,6 @@ UBFeaturesController::UBFeaturesController(QWidget *pParentWidget) : audiosElement = UBFeature( audiosPath, QImage(":images/libpalette/AudiosCategory.svg"), tr("Audios") , mUserAudioDirectoryPath, FEATURE_CATEGORY); moviesElement = UBFeature( moviesPath, QImage(":images/libpalette/MoviesCategory.svg"), tr("Movies") , mUserVideoDirectoryPath, FEATURE_CATEGORY); picturesElement = UBFeature( picturesPath, QImage(":images/libpalette/PicturesCategory.svg"), tr("Pictures") , mUserPicturesDirectoryPath, FEATURE_CATEGORY); - //flashElement = UBFeature( flashPath, QImage(":images/libpalette/FlashCategory.svg"), tr("Animations") , mUserAnimationDirectoryPath, FEATURE_CATEGORY); interactElement = UBFeature( interactPath, QImage(":images/libpalette/InteractivesCategory.svg"), tr("Interactivities") , mLibInteractiveDirectoryPath, FEATURE_CATEGORY); applicationsElement = UBFeature( appPath, QImage(":images/libpalette/ApplicationsCategory.svg"), tr("Applications") , mUserInteractiveDirectoryPath, FEATURE_CATEGORY); shapesElement = UBFeature( shapesPath, QImage(":images/libpalette/ShapesCategory.svg"), tr("Shapes") , mLibShapesDirectoryPath, FEATURE_CATEGORY ); @@ -374,7 +371,6 @@ UBFeaturesController::UBFeaturesController(QWidget *pParentWidget) : connect(&mCThread, SIGNAL(maxFilesCountEvaluated(int)), this, SIGNAL(maxFilesCountEvaluated(int))); connect(&mCThread, SIGNAL(scanCategory(QString)), this, SIGNAL(scanCategory(QString))); connect(&mCThread, SIGNAL(scanPath(QString)), this, SIGNAL(scanPath(QString))); - connect(UBApplication::boardController, SIGNAL(npapiWidgetCreated(QString)), this, SLOT(createNpApiFeature(QString))); QTimer::singleShot(0, this, SLOT(startThread())); } @@ -385,14 +381,12 @@ void UBFeaturesController::startThread() computingData << QPair(mLibAudiosDirectoryPath, audiosElement) << QPair(mLibVideosDirectoryPath, moviesElement) - << QPair(mLibAnimationsDirectoryPath, flashElement) << QPair(mLibPicturesDirectoryPath, picturesElement) << QPair(mUserInteractiveDirectoryPath, applicationsElement) << QPair(mUserAudioDirectoryPath, audiosElement) << QPair(mUserPicturesDirectoryPath, picturesElement) << QPair(mUserVideoDirectoryPath, moviesElement) - << QPair(mUserAnimationDirectoryPath, flashElement) << QPair(mLibApplicationsDirectoryPath, applicationsElement) << QPair(mLibShapesDirectoryPath, shapesElement) @@ -403,15 +397,6 @@ void UBFeaturesController::startThread() mCThread.compute(computingData, favoriteSet); } -void UBFeaturesController::createNpApiFeature(const QString &str) -{ - Q_ASSERT(QFileInfo(str).exists() && QFileInfo(str).isDir()); - - QString widgetName = QFileInfo(str).fileName(); - - featuresModel->addItem(UBFeature(QString(appPath + "/Web/" + widgetName), QImage(UBGraphicsWidgetItem::iconFilePath(QUrl::fromLocalFile(str))), widgetName, QUrl::fromLocalFile(str), FEATURE_INTERACTIVE)); -} - void UBFeaturesController::scanFS() { featuresList->clear(); @@ -420,7 +405,6 @@ void UBFeaturesController::scanFS() *featuresList << audiosElement << moviesElement << picturesElement - << flashElement << interactElement << applicationsElement << shapesElement @@ -564,9 +548,8 @@ void UBFeaturesController::loadFavoriteList() { favoriteSet = new QSet(); QFile file( UBSettings::userDataDirectory() + "/favorites.dat" ); - if ( file.exists() ) + if ( file.open(QIODevice::ReadOnly) ) { - file.open(QIODevice::ReadOnly); QDataStream in(&file); int elementsNumber; in >> elementsNumber; @@ -577,21 +560,31 @@ void UBFeaturesController::loadFavoriteList() favoriteSet->insert( path ); } } + else + { + qDebug() << "Could not open favorites list"; + } } void UBFeaturesController::saveFavoriteList() { QFile file( UBSettings::userDataDirectory() + "/favorites.dat" ); file.resize(0); - file.open(QIODevice::WriteOnly); - QDataStream out(&file); - int elementsNumber = favoriteSet->size(); - out << elementsNumber; - for ( QSet::iterator it = favoriteSet->begin(); it != favoriteSet->end(); ++it ) + if (file.open(QIODevice::WriteOnly)) { - out << (*it); + QDataStream out(&file); + int elementsNumber = favoriteSet->size(); + out << elementsNumber; + for ( QSet::iterator it = favoriteSet->begin(); it != favoriteSet->end(); ++it ) + { + out << (*it); + } + file.close(); + } + else + { + qDebug() << "Could not write favorites list"; } - file.close(); } QString UBFeaturesController::uniqNameForFeature(const UBFeature &feature, const QString &pName, const QString &pExtention) const @@ -790,10 +783,6 @@ UBFeatureElementType UBFeaturesController::fileTypeFromUrl(const QString &path) { fileType = FEATURE_SEARCH; } - else if (mimeString.contains("application/x-shockwave-flash")) - { - fileType = FEATURE_FLASH; - } else if (mimeString.contains("application/openboard-document")) { fileType = FEATURE_DOCUMENT; @@ -827,8 +816,6 @@ QImage UBFeaturesController::getIcon(const QString &path, UBFeatureElementType p return QImage(UBGraphicsWidgetItem::iconFilePath(QUrl::fromLocalFile(path))); } else if (pFType == FEATURE_INTERNAL) { return QImage(UBToolsManager::manager()->iconFromToolId(path)); - } else if (pFType == FEATURE_FLASH) { - return QImage(":images/libpalette/FlashIcon.svg"); } else if (pFType == FEATURE_AUDIO) { return QImage(":images/libpalette/soundIcon.svg"); } else if (pFType == FEATURE_VIDEO) { @@ -1017,10 +1004,7 @@ UBFeature UBFeaturesController::getDestinationFeatureForMimeType(const QString & return picturesElement; else if ( pMmimeType.contains("application") ) { - if ( pMmimeType.contains( "x-shockwave-flash") ) - return flashElement; - else - return interactElement; + return interactElement; } return UBFeature(); } @@ -1318,7 +1302,7 @@ void UBFeaturesController::assignPathListView(UBFeaturesListView *pList) pList->setFixedHeight(60); pList->setSelectionMode(QAbstractItemView::NoSelection); pList->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - pList->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + pList->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); pList->setFlow(QListView::LeftToRight); pList->setWrapping(false); pList->setDragDropMode(QAbstractItemView::DropOnly); diff --git a/src/board/UBFeaturesController.h b/src/board/UBFeaturesController.h index 407b5aa6e..51f411ff0 100644 --- a/src/board/UBFeaturesController.h +++ b/src/board/UBFeaturesController.h @@ -108,7 +108,6 @@ enum UBFeatureElementType FEATURE_AUDIO, FEATURE_VIDEO, FEATURE_IMAGE, - FEATURE_FLASH, FEATURE_TRASH, FEATURE_FAVORITE, FEATURE_SEARCH, @@ -230,7 +229,6 @@ Q_OBJECT static const QString moviesPath; static const QString picturesPath; static const QString appPath; - static const QString flashPath; static const QString shapesPath; static const QString interactPath; static const QString trashPath; @@ -248,7 +246,6 @@ Q_OBJECT private slots: void addNewFolder(QString name); void startThread(); - void createNpApiFeature(const QString &str); private: @@ -278,14 +275,12 @@ private slots: QUrl mUserVideoDirectoryPath; QUrl mUserPicturesDirectoryPath; QUrl mUserInteractiveDirectoryPath; - QUrl mUserAnimationDirectoryPath; QString libraryPath; QUrl mLibPicturesDirectoryPath; QUrl mLibAudiosDirectoryPath; QUrl mLibVideosDirectoryPath; QUrl mLibInteractiveDirectoryPath; - QUrl mLibAnimationsDirectoryPath; QUrl mLibApplicationsDirectoryPath; QUrl mLibShapesDirectoryPath; @@ -304,7 +299,6 @@ private slots: UBFeature picturesElement; UBFeature interactElement; UBFeature applicationsElement; - UBFeature flashElement; UBFeature shapesElement; UBFeature webSearchElement; diff --git a/src/board/board.pri b/src/board/board.pri index c8d421e4e..3d9b399b0 100644 --- a/src/board/board.pri +++ b/src/board/board.pri @@ -2,16 +2,17 @@ HEADERS += src/board/UBBoardController.h \ src/board/UBBoardPaletteManager.h \ src/board/UBBoardView.h \ + src/board/UBColorPreferencesDialog.h \ src/board/UBDrawingController.h \ src/board/UBFeaturesController.h SOURCES += src/board/UBBoardController.cpp \ src/board/UBBoardPaletteManager.cpp \ src/board/UBBoardView.cpp \ + src/board/UBColorPreferencesDialog.cpp \ src/board/UBDrawingController.cpp \ src/board/UBFeaturesController.cpp - diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 4ac038907..31e9e45b2 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -13,8 +13,6 @@ target_sources(${PROJECT_NAME} PRIVATE UBDownloadManager.h UBDownloadThread.cpp UBDownloadThread.h - UBForeignObjectsHandler.cpp - UBForeignObjectsHandler.h UBIdleTimer.cpp UBIdleTimer.h UBMimeData.cpp @@ -35,4 +33,8 @@ target_sources(${PROJECT_NAME} PRIVATE UBShortcutManager.h UBTextTools.cpp UBTextTools.h + UBThemeManager.cpp + UBThemeManager.h + UBWidgetInspector.cpp + UBWidgetInspector.h ) diff --git a/src/core/UB.h b/src/core/UB.h index bfa6a7654..4dca9233f 100644 --- a/src/core/UB.h +++ b/src/core/UB.h @@ -45,11 +45,9 @@ struct UBMimeType { RasterImage = 0, VectorImage, - AppleWidget, W3CWidget, Video, Audio, - Flash, PDF, OpenboardTool, Group, @@ -139,7 +137,6 @@ struct UBGraphicsItemData , ItemEditable//for text only , ItemOwnZValue , itemLayerType //use instead of deprecated ItemLayerType - , ItemUuid //storing uuid in QGraphicsItem for fast finding operations //Duplicating delegate's functions to make possible working with pure QGraphicsItem , ItemFlippable // (bool) , ItemRotatable // (bool) @@ -223,13 +220,6 @@ struct UBUndoType }; }; -enum UBPageBackground -{ - plain = 0, - crossed, - ruled -}; - /* * Qt Version Compatibility */ diff --git a/src/core/UBApplication.cpp b/src/core/UBApplication.cpp index 640fad511..94d2bb472 100644 --- a/src/core/UBApplication.cpp +++ b/src/core/UBApplication.cpp @@ -46,6 +46,7 @@ #include "UBIdleTimer.h" #include "UBApplicationController.h" #include "UBShortcutManager.h" +#include "UBThemeManager.h" #include "board/UBBoardController.h" #include "board/UBDrawingController.h" @@ -106,11 +107,9 @@ UBApplication::UBApplication(const QString &id, int &argc, char **argv) : Single setOrganizationName("Open Education Foundation"); setOrganizationDomain("oe-f.org"); setApplicationName("OpenBoard"); - -#ifdef Q_OS_OSX + setDesktopFileName("ch.openboard.OpenBoard"); // With Qt 6.9 on macOS 15 (at least), icons aren't shown in menus. This forces their display. QCoreApplication::setAttribute(Qt::AA_DontShowIconsInMenus, false); -#endif QString version = UBVERSION; if(version.endsWith(".")) @@ -148,9 +147,11 @@ UBApplication::UBApplication(const QString &id, int &argc, char **argv) : Single setStyle("fusion"); - QString css = UBFileSystemUtils::readTextFile(UBPlatformUtils::applicationEtcDirectory() + "/"+ qApp->applicationName()+".css"); - if (css.length() > 0) - setStyleSheet(css); + UBThemeManager::instance()->applyUserThemePreference(); + +#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) + connect(QGuiApplication::styleHints(), &QStyleHints::colorSchemeChanged, UBThemeManager::instance(), &UBThemeManager::onColorSchemeChanged); +#endif QApplication::setStartDragDistance(8); // default is 4, and is a bit small for tablets @@ -623,6 +624,7 @@ void UBApplication::updateProtoActionsState() void UBApplication::insertSpaceToToolbarBeforeAction(QToolBar* toolbar, QAction* action, int width) { QWidget* spacer = new QWidget(); + spacer->setStyleSheet("background: transparent;"); if (width >= 0){ QHBoxLayout *layout = new QHBoxLayout(); @@ -713,6 +715,34 @@ bool UBApplication::eventFilter(QObject *obj, QEvent *event) || result; } + else if (event->type() == QEvent::MouseButtonPress) + { + // intercept special mouse buttons for shortcut handler + QMouseEvent *mouseEvent = static_cast(event); + Qt::MouseButton button = mouseEvent->button(); + + if (button != Qt::LeftButton && button != Qt::RightButton) + { + return mPreferencesController->handleMouseEvent(mouseEvent) + || UBShortcutManager::shortcutManager()->handleMouseEvent(mouseEvent) + || result; + } + } + + else if (event->type() == QEvent::TabletPress) + { + // intercept special tablet buttons for shortcut handler + QTabletEvent *tabletEvent = static_cast(event); + Qt::MouseButton button = tabletEvent->button(); + + if (button != Qt::LeftButton) + { + return mPreferencesController->handleTabletEvent(tabletEvent) + || UBShortcutManager::shortcutManager()->handleTabletEvent(tabletEvent) + || result; + } + } + return result; } @@ -787,3 +817,4 @@ bool UBApplication::isFromWeb(QString url) return res; } + diff --git a/src/core/UBApplicationController.cpp b/src/core/UBApplicationController.cpp index 89dad32fb..7fc8bf989 100644 --- a/src/core/UBApplicationController.cpp +++ b/src/core/UBApplicationController.cpp @@ -44,30 +44,21 @@ #include "board/UBBoardPaletteManager.h" #include "board/UBDrawingController.h" - +#include "document/UBDocument.h" #include "document/UBDocumentProxy.h" #include "document/UBDocumentController.h" #include "domain/UBGraphicsWidgetItem.h" -#include "desktop/UBDesktopPalette.h" #include "desktop/UBDesktopAnnotationController.h" #include "web/UBWebController.h" #include "gui/UBScreenMirror.h" #include "gui/UBMainWindow.h" -#include "gui/UBStartupHintsPalette.h" - -#include "domain/UBGraphicsPixmapItem.h" #include "podcast/UBPodcastController.h" -#include "network/UBNetworkAccessManager.h" - -#include "ui_mainWindow.h" - - #ifdef Q_OS_MAC #include @@ -105,7 +96,7 @@ UBApplicationController::UBApplicationController(UBBoardView *pControlView, connect(mUninoteController, SIGNAL(restoreUniboard()), this, SLOT(hideDesktop())); mBlackScene = std::make_shared(nullptr); - mBlackScene->setBackground(true, UBPageBackground::plain); + mBlackScene->setSceneBackground(true, nullptr); if (displayManager->numScreens() >= 2 && displayManager->useMultiScreen()) { @@ -259,6 +250,7 @@ void UBApplicationController::adjustDisplayView() void UBApplicationController::adjustPreviousViews(int pActiveSceneIndex, std::shared_ptr pActiveDocument) { int viewIndex = pActiveSceneIndex; + auto document = UBDocument::getDocument(pActiveDocument); foreach(UBBoardView* previousView, mPreviousViews) { @@ -266,7 +258,7 @@ void UBApplicationController::adjustPreviousViews(int pActiveSceneIndex, std::sh { viewIndex--; - std::shared_ptr scene = UBPersistenceManager::persistenceManager()->loadDocumentScene(pActiveDocument, viewIndex); + std::shared_ptr scene = document->loadScene(viewIndex); if (scene) { @@ -714,7 +706,8 @@ void UBApplicationController::importFile(const QString& pFilePath) bool success = false; - std::shared_ptr document = UBDocumentManager::documentManager()->importFile(fileToOpen, ""); + std::shared_ptr doc = UBDocumentManager::documentManager()->importFile(fileToOpen, ""); + std::shared_ptr document = doc->proxy(); success = (document != 0); diff --git a/src/core/UBDisplayManager.cpp b/src/core/UBDisplayManager.cpp index ca45ad27e..58d425c19 100644 --- a/src/core/UBDisplayManager.cpp +++ b/src/core/UBDisplayManager.cpp @@ -96,6 +96,7 @@ void UBDisplayManager::initScreenIndexes() { qDebug() << "availableScreens" << screens; mAvailableScreens = screens; + createScreenLabels(); emit availableScreenCountChanged(screens.count()); } @@ -106,6 +107,61 @@ void UBDisplayManager::initScreenIndexes() */ } +void UBDisplayManager::createScreenLabels() +{ + if (!mLabelParent) + { + // don't create screen labels before parent is known + return; + } + + qDeleteAll(mScreenLabels); + mScreenLabels.clear(); + + const static QSize labelSize{300, 150}; + const auto screens = availableScreens(); + int screenIndex = 1; + QFont font; + font.setPointSize(48); + + for (QScreen* screen : screens) + { + QString index = QString::number(screenIndex); + + QPushButton* button = new QPushButton{mLabelParent}; + button->setWindowFlag(Qt::FramelessWindowHint, true); + button->setWindowFlag(Qt::WindowStaysOnTopHint, true); + button->setWindowFlag(Qt::BypassWindowManagerHint, true); + button->setWindowFlag(Qt::Window, true); + button->setWindowFlag(Qt::WindowDoesNotAcceptFocus, true); + button->setAttribute(Qt::WA_ShowWithoutActivating, true); + + button->setProperty("screenIndex", index); +#ifdef QT_DEBUG + button->setText(index + "(" + screen->name() + ")"); +#else + button->setText(index); +#endif + button->setFont(font); + button->move(screen->geometry().topLeft()); + button->setMinimumSize(labelSize); + button->setCursor(Qt::PointingHandCursor); + + connect(button, &QPushButton::pressed, this, [this](){ + QPushButton* button = dynamic_cast(sender()); + + if (button) + { + QString screenIndex = button->property("screenIndex").toString(); + emit screenLabelPressed(screenIndex); + } + }); + + mScreenLabels << button; + ++screenIndex; + } +} + void UBDisplayManager::initScreensByRole() { mScreensByRole.clear(); @@ -146,6 +202,9 @@ void UBDisplayManager::assignRoles() QStringList screenList = appScreenList.toStringList(); qDebug() << "assignRoles using screen list" << screenList; + // disable screen labels of screens in list + disableScreenLabels(screenList); + if (!appScreenList.isValid()) { // no entry in configuration files @@ -314,9 +373,51 @@ QList UBDisplayManager::availableScreens() const void UBDisplayManager::adjustScreens() { assignRoles(); + + if (UBPlatformUtils::sessionType() == UBPlatformUtils::SessionType::WAYLAND) + { + auto controlWidget = widget(ScreenRole::Control); + + // hide windows for repositioning + if (controlWidget && hasControl() && controlWidget->isVisible()) + { + for (auto widget : mWidgetsByRole) + { + if (widget) + { + widget->hide(); + } + } + + QTimer::singleShot(100, this, [this](){ + positionScreens(); + }); + + return; + } + } + positionScreens(); } +void UBDisplayManager::setScreenLabelParent(QWidget* labelParent) +{ + mLabelParent = labelParent; + createScreenLabels(); +} + +QStringList UBDisplayManager::availableScreenIndexes() +{ + QStringList screenIndexes; + + for (const auto label : mScreenLabels) + { + screenIndexes << label->property("screenIndex").toString(); + } + + return screenIndexes; +} + void UBDisplayManager::positionScreens() { // static event filter object @@ -473,6 +574,56 @@ void UBDisplayManager::unBlackout() UBApplication::boardController->freezeW3CWidgets(false); } +void UBDisplayManager::showScreenLabels(bool show) +{ + if (mScreenLabels.empty() || mScreenLabels.first().isNull() || mScreenLabels.first()->isVisible() == show) + { + return; + } + + if (show) + { + for (auto label : mScreenLabels) + { + if (label) + { + label->showNormal(); + } + } + } + else + { + for (const auto &label : mScreenLabels) + { + if (label && label->rect().contains(QCursor::pos())) + { + // do not hide labels when triggered by a click on one of them + return; + } + } + + for (auto label : mScreenLabels) + { + if (label) + { + label->hide(); + } + } + } +} + +void UBDisplayManager::disableScreenLabels(QStringList screenList) +{ + for (auto label : mScreenLabels) + { + if (label) + { + const auto screenIndex = label->property("screenIndex").toString(); + label->setDisabled(screenList.contains(screenIndex)); + } + } +} + void UBDisplayManager::addOrRemoveScreen(QScreen *screen) { Q_UNUSED(screen); @@ -521,18 +672,23 @@ qreal UBDisplayManager::logicalDpi(ScreenRole role) const return scr ? scr->logicalDotsPerInch() : 96.; } -QPixmap UBDisplayManager::grab(ScreenRole role, QRect rect) const +void UBDisplayManager::grab(ScreenRole role, std::function callback, QRect rect) const { QScreen* scr = screen(role); if (scr) { - // see https://doc.qt.io/qt-6.2/qtwidgets-desktop-screenshot-example.html - // for using window id 0 - return scr->grabWindow(0, rect.x(), rect.y(), rect.width(), rect.height()); - } + if (!rect.isValid()) + { + rect = QRect(QPoint(0, 0), scr->geometry().size()); + } - return QPixmap(); + UBPlatformUtils::grabScreen(scr, callback, rect); + } + else + { + callback(QPixmap()); + } } QPixmap UBDisplayManager::grabGlobal(QRect rect) const diff --git a/src/core/UBDisplayManager.h b/src/core/UBDisplayManager.h index 6cfaaed6b..ad9ac2d59 100644 --- a/src/core/UBDisplayManager.h +++ b/src/core/UBDisplayManager.h @@ -30,8 +30,10 @@ #ifndef UBDISPLAYMANAGER_H_ #define UBDISPLAYMANAGER_H_ -#include #include +#include +#include +#include class UBBlackoutWidget; class UBBoardView; @@ -105,7 +107,7 @@ class UBDisplayManager : public QObject qreal physicalDpi(ScreenRole role) const; qreal logicalDpi(ScreenRole role) const; - QPixmap grab(ScreenRole role, QRect rect = QRect(0, 0, -1, -1)) const; + void grab(ScreenRole role, std::function callback, QRect rect = {}) const; QPixmap grabGlobal(QRect rect) const; void initScreensByRole(); @@ -113,16 +115,20 @@ class UBDisplayManager : public QObject void positionScreens(); void adjustScreens(); - signals: + void setScreenLabelParent(QWidget* labelParent); + QStringList availableScreenIndexes(); + + signals: void screenRolesAssigned(); void screenLayoutChanged(); void availableScreenCountChanged(int screenCount); + void screenLabelPressed(const QString& screenIndex); - public slots: - + public slots: void blackout(); - void unBlackout(); + void showScreenLabels(bool show); + void disableScreenLabels(QStringList screenList); private slots: @@ -131,6 +137,7 @@ class UBDisplayManager : public QObject private: void initScreenIndexes(); + void createScreenLabels(); QList mBlackoutWidgets; QList mBlackoutUiList; @@ -139,6 +146,9 @@ class UBDisplayManager : public QObject QMap> mScreensByRole; QMap> mWidgetsByRole; + QList> mScreenLabels; + QWidget* mLabelParent{nullptr}; + bool mUseMultiScreen; }; diff --git a/src/core/UBDocumentManager.cpp b/src/core/UBDocumentManager.cpp index 2669d6dd1..a940de21b 100644 --- a/src/core/UBDocumentManager.cpp +++ b/src/core/UBDocumentManager.cpp @@ -27,26 +27,25 @@ #include "UBDocumentManager.h" -#include "document/UBDocument.h" -#include "frameworks/UBStringUtils.h" - #include "adaptors/UBExportFullPDF.h" #include "adaptors/UBExportDocument.h" #include "adaptors/UBExportWeb.h" -#include "adaptors/UBExportCFF.h" #include "adaptors/UBExportDocumentSetAdaptor.h" #include "adaptors/UBImportDocument.h" #include "adaptors/UBImportPDF.h" #include "adaptors/UBImportImage.h" -#include "adaptors/UBImportCFF.h" #include "adaptors/UBImportDocumentSetAdaptor.h" #include "board/UBBoardController.h" #include "domain/UBGraphicsScene.h" -#include "document/UBDocumentProxy.h" +#include "document/UBDocument.h" #include "document/UBDocumentController.h" +#include "document/UBDocumentProxy.h" +#include "document/UBDocumentToc.h" + +#include "frameworks/UBStringUtils.h" #include "gui/UBThumbnailScene.h" @@ -79,7 +78,6 @@ 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); @@ -88,7 +86,6 @@ UBDocumentManager::UBDocumentManager(QObject *parent) mExportAdaptors.append(exportDocumentSet); //mExportAdaptors.append(webPublished); mExportAdaptors.append(exportFullPdf); - //mExportAdaptors.append(cffExporter); // UBExportWeb* exportWeb = new UBExportWeb(this); // mExportAdaptors.append(exportWeb); @@ -101,8 +98,6 @@ UBDocumentManager::UBDocumentManager(QObject *parent) mImportAdaptors.append(pdfImport); UBImportImage* imageImport = new UBImportImage(this); mImportAdaptors.append(imageImport); - UBImportCFF* cffImport = new UBImportCFF(this); - mImportAdaptors.append(cffImport); } @@ -167,7 +162,7 @@ QFileInfoList UBDocumentManager::importUbx(const QString &Incomingfile, const QS return docSetAdaptor->importData(Incomingfile, destination); } -std::shared_ptr UBDocumentManager::importFile(const QFile& pFile, const QString& pGroup) +std::shared_ptr UBDocumentManager::importFile(const QFile& pFile, const QString& pGroup) { QFileInfo fileInfo(pFile); @@ -176,6 +171,7 @@ std::shared_ptr UBDocumentManager::importFile(const QFile& pFil if (adaptor->supportedExtentions().lastIndexOf(fileInfo.suffix().toLower()) != -1) { std::shared_ptr document; + std::shared_ptr doc; UBApplication::setDisabled(true); if (adaptor->isDocumentBased()) @@ -183,7 +179,7 @@ std::shared_ptr UBDocumentManager::importFile(const QFile& pFil UBDocumentBasedImportAdaptor* importAdaptor = (UBDocumentBasedImportAdaptor*)adaptor; document = importAdaptor->importFile(pFile, pGroup); - + doc = UBDocument::getDocument(document); } else { @@ -200,43 +196,19 @@ std::shared_ptr UBDocumentManager::importFile(const QFile& pFil if (document) { - auto doc = UBDocument::getDocument(document); - QUuid uuid = QUuid::createUuid(); - QString filepath = pFile.fileName(); - if (importAdaptor->folderToCopy() != "") - { - bool b = UBPersistenceManager::persistenceManager()->addFileToDocument(document, pFile.fileName(), importAdaptor->folderToCopy() , uuid, filepath); - if (!b) - { - UBApplication::setDisabled(false); - return NULL; - } - } + doc = UBDocument::getDocument(document); - QList pages = importAdaptor->import(uuid, filepath); - int pageIndex = 0; + // create the thumbnail scene before any pages are created + doc->thumbnailScene(false); - UBApplication::showMessage(tr("Creating %1 pages. Please wait...").arg(pages.size()), true); - foreach(UBGraphicsItem* page, pages) - { - #ifdef Q_WS_MACX - //Workaround for issue 912 - QApplication::processEvents(); - #endif - std::shared_ptr scene = doc->createPage(pageIndex); - importAdaptor->placeImportedItemToScene(scene, page); - doc->persistPage(scene, pageIndex); - pageIndex++; - } + importPages(pFile, doc, importAdaptor); - UBPersistenceManager::persistenceManager()->persistDocumentMetadata(document); - doc->thumbnailScene()->createThumbnails(); UBApplication::showMessage(tr("Import successful.")); } } UBApplication::setDisabled(false); - return document; + return doc; } } @@ -248,7 +220,6 @@ int UBDocumentManager::addFilesToDocument(std::shared_ptr docum { int nImportedDocuments = 0; auto doc = UBDocument::getDocument(document); - const auto currentNumberOfPages = document->pageCount(); foreach(const QString& fileName, fileNames) { @@ -278,30 +249,8 @@ int UBDocumentManager::addFilesToDocument(std::shared_ptr docum else { UBPageBasedImportAdaptor* importAdaptor = (UBPageBasedImportAdaptor*)adaptor; + importPages(file, doc, importAdaptor); - QUuid uuid = QUuid::createUuid(); - QString filepath = file.fileName(); - if (importAdaptor->folderToCopy() != "") - { - bool b = UBPersistenceManager::persistenceManager()->addFileToDocument(document, file.fileName(), importAdaptor->folderToCopy() , uuid, filepath); - if (!b) - { - continue; - } - } - - QList pages = importAdaptor->import(uuid, filepath); - int nPage = 0; - foreach(UBGraphicsItem* page, pages) - { - UBApplication::showMessage(tr("Inserting page %1 of %2").arg(++nPage).arg(pages.size()), true); - int pageIndex = document->pageCount(); - std::shared_ptr scene = doc->createPage(pageIndex); - importAdaptor->placeImportedItemToScene(scene, page); - doc->persistPage(scene, pageIndex); - } - - UBPersistenceManager::persistenceManager()->persistDocumentMetadata(document); UBApplication::showMessage(tr("Import of file %1 successful.").arg(file.fileName())); nImportedDocuments++; break; @@ -309,9 +258,9 @@ int UBDocumentManager::addFilesToDocument(std::shared_ptr docum } } - doc->thumbnailScene()->createThumbnails(currentNumberOfPages); UBApplication::setDisabled(false); } + return nImportedDocuments; } @@ -363,3 +312,48 @@ QList UBDocumentManager::supportedExportAdaptors() { return mExportAdaptors; } + +int UBDocumentManager::importPages(const QFile& file, std::shared_ptr doc, UBPageBasedImportAdaptor* importAdaptor) +{ + std::shared_ptr document = doc->proxy(); + QString filepath = file.fileName(); + QUuid uuid; + + if (importAdaptor->folderToCopy() != "") + { + uuid = UBPersistenceManager::persistenceManager()->addFileToDocument(document, file.fileName(), importAdaptor->folderToCopy(), filepath); + + if (uuid.isNull()) + { + return 0; + } + } + + // NOTE when folderToCopy returns empty string, uuid parameter is not used for import + QList pages = importAdaptor->import(uuid, filepath); + const int startIndex = doc->pageCount(); + int pageIndex = startIndex; + + UBApplication::showMessage(tr("Creating %1 pages. Please wait...").arg(pages.size()), true); + + for (UBGraphicsItem* page : pages) + { + std::shared_ptr scene = doc->createPage(pageIndex, false, false, true, false); + importAdaptor->placeImportedItemToScene(scene, page); + doc->persistPage(scene, pageIndex, false, false, false, false); + pageIndex++; + } + + UBPersistenceManager::persistenceManager()->persistDocumentMetadata(document); + + // remove all asset entries to force scanning + for (int index = startIndex; index < pageIndex; ++index) + { + doc->toc()->unsetAssets(index); + } + + doc->toc()->save(); + doc->scanAssets(); + + return pageIndex - startIndex; +} diff --git a/src/core/UBDocumentManager.h b/src/core/UBDocumentManager.h index c4209f4b8..453597531 100644 --- a/src/core/UBDocumentManager.h +++ b/src/core/UBDocumentManager.h @@ -34,7 +34,9 @@ class UBExportAdaptor; class UBImportAdaptor; +class UBDocument; class UBDocumentProxy; +class UBPageBasedImportAdaptor; class UBDocumentManager : public QObject @@ -51,7 +53,7 @@ class UBDocumentManager : public QObject QStringList importFileExtensions(bool notUbx = false); QFileInfoList importUbx(const QString &Incomingfile, const QString &destination); - std::shared_ptr importFile(const QFile& pFile, const QString& pGroup); + std::shared_ptr importFile(const QFile& pFile, const QString& pGroup); int addFilesToDocument(std::shared_ptr pDocument, QStringList fileNames); @@ -63,6 +65,8 @@ class UBDocumentManager : public QObject private: UBDocumentManager(QObject *parent = 0); + int importPages(const QFile& file, std::shared_ptr doc, UBPageBasedImportAdaptor* importAdaptor); + QList mExportAdaptors; QList mImportAdaptors; diff --git a/src/core/UBDownloadManager.cpp b/src/core/UBDownloadManager.cpp index be29da71d..5a8b19361 100644 --- a/src/core/UBDownloadManager.cpp +++ b/src/core/UBDownloadManager.cpp @@ -79,11 +79,9 @@ void UBAsyncLocalFileDownloader::run() if (mDesc.originalSrcUrl.isEmpty()) mDesc.originalSrcUrl = mDesc.srcUrl; - QUuid uuid = QUuid::createUuid(); UBPersistenceManager::persistenceManager()->addFileToDocument(UBApplication::boardController->selectedDocument(), mDesc.srcUrl, destDirectory, - uuid, mTo, NULL); diff --git a/src/core/UBForeignObjectsHandler.cpp b/src/core/UBForeignObjectsHandler.cpp deleted file mode 100644 index 1ad3ee982..000000000 --- a/src/core/UBForeignObjectsHandler.cpp +++ /dev/null @@ -1,651 +0,0 @@ -/* - * Copyright (C) 2015-2022 Département de l'Instruction Publique (DIP-SEM) - * - * Copyright (C) 2013 Open Education Foundation - * - * 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 - * 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, - * 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 OpenBoard. 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(); - } - - static const QRegularExpression rx("\\{.(?!.*\\{).*\\}"); - QRegularExpressionMatch match = rx.match(filePath); - if (!match.hasMatch()) { - return QString(); - } - - return match.captured(); -} - -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(); - } - - static const QRegularExpression braces("[\\{\\}]"); - QString thumbPath = filePath; - thumbPath.replace(braces, "").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 << '\n' - << "Error:" << inFile.errorString() - << "Line:" << errorLine - << "Column:" << errorColumn; - } - inFile.close(); - } else { - qDebug() << "Error reading" << mFoldersXmlStorageName << '\n' - << "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(",", UB::SplitBehavior::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); - int i = ref.indexOf("#page"); - QString dest = ref.replace(i, ref.length()-i, ""); - if (!QFileInfo::exists(dest)) - cureNCopy(dest, false); - if (ref.isEmpty()) { - return; - } - static const QRegularExpression pdfReference("^(.*pdf\\#page\\=).*$"); - ref.replace(pdfReference, 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, bool createNewUuid=true) - { - QString relative = relativePath; - if (createNewUuid) - { - QUuid newUuid = QUuid::createUuid(); - static const QRegularExpression bracedUuid("\\{.*\\}"); - QString newPath = relative.replace(bracedUuid, newUuid.toString()); - - cp_rf(mFromDir + "/" + relativePath, mToDir + "/" + newPath); - - return newPath; - } - else - { - cp_rf(mFromDir + "/" + relativePath, mToDir + "/" + relativePath); - return relativePath; - } - } - -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/UBPersistenceManager.cpp b/src/core/UBPersistenceManager.cpp index c532ae836..e0a151b2e 100644 --- a/src/core/UBPersistenceManager.cpp +++ b/src/core/UBPersistenceManager.cpp @@ -27,6 +27,8 @@ #include "UBPersistenceManager.h" +#include "domain/UBMediaAssetItem.h" +#include "gui/UBBackgroundManager.h" #include "gui/UBMainWindow.h" #include @@ -36,30 +38,22 @@ #include #include -#include "frameworks/UBPlatformUtils.h" -#include "frameworks/UBFileSystemUtils.h" - -#include "core/UBApplication.h" -#include "core/UBSettings.h" -#include "core/UBSetting.h" -#include "core/UBForeignObjectsHandler.h" - -#include "document/UBDocumentProxy.h" - -#include "adaptors/UBExportPDF.h" #include "adaptors/UBSvgSubsetAdaptor.h" #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 "core/UBApplication.h" +#include "core/UBSettings.h" +#include "core/UBSetting.h" + +#include "document/UBDocument.h" #include "document/UBDocumentController.h" +#include "document/UBDocumentProxy.h" +#include "document/UBDocumentToc.h" + +#include "frameworks/UBFileSystemUtils.h" #include "core/memcheck.h" @@ -68,7 +62,6 @@ 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"; @@ -95,7 +88,6 @@ UBPersistenceManager::UBPersistenceManager(QObject *pParent) mDocumentSubDirectories << widgetDirectory; mDocumentSubDirectories << videoDirectory; mDocumentSubDirectories << audioDirectory; - mDocumentSubDirectories << fileDirectory; // Issue 1683 (Evolution) - AOU - 20131206 mDocumentRepositoryPath = UBSettings::userDocumentDirectory(); mFoldersXmlStorageName = mDocumentRepositoryPath + "/" + fFolders; @@ -235,10 +227,16 @@ void UBPersistenceManager::createDocumentProxiesStructure(bool interactive) if (inFile.open(QIODevice::ReadOnly)) { QString domString(inFile.readAll()); - int errorLine = 0; int errorColumn = 0; +#if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)) + QDomDocument::ParseResult result = xmlDom.setContent(domString); + int errorLine = result.errorLine; + int errorColumn = result.errorColumn; +#else + int errorLine, errorColumn; QString errorStr; - - if (xmlDom.setContent(domString, &errorStr, &errorLine, &errorColumn)) { + bool result = xmlDom.setContent(domString, &errorStr, &errorLine, &errorColumn); +#endif + if (result) { loadFolderTreeFromXml("", xmlDom.firstChildElement()); } else { qDebug() << "Error reading content of " << mFoldersXmlStorageName << '\n' @@ -272,14 +270,6 @@ std::shared_ptr UBPersistenceManager::createDocumentProxyStruct docProxy->setMetaData(key, metadatas.value(key)); } - docProxy->setPageCount(sceneCount(docProxy)); - - if (docProxy->pageCount() == 0) - { - qWarning() << "No pages found - " << fullPath; - docProxy->setMetaData(UBSettings::documentName, tr("Broken - %1").arg(contentInfo.baseName())); - } - return docProxy; }; @@ -339,12 +329,12 @@ QDialog::DialogCode UBPersistenceManager::processInteractiveReplacementDialog(st if (UBApplication::documentController->selectedDocument() == replacedProxy) { - UBApplication::documentController->pureSetDocument(pProxy); + UBApplication::documentController->setDocument(pProxy); } if (UBApplication::boardController->selectedDocument() == replacedProxy) { - UBApplication::boardController->pureSetDocument(pProxy); + UBApplication::boardController->setDocument(pProxy); } } else @@ -400,12 +390,12 @@ QDialog::DialogCode UBPersistenceManager::processInteractiveReplacementDialog(st if (UBApplication::documentController->selectedDocument() == replacedProxy) { - UBApplication::documentController->pureSetDocument(pProxy); + UBApplication::documentController->setDocument(pProxy); } if (UBApplication::boardController->selectedDocument() == replacedProxy) { - UBApplication::boardController->pureSetDocument(pProxy); + UBApplication::boardController->setDocument(pProxy); } } else @@ -424,6 +414,25 @@ QDialog::DialogCode UBPersistenceManager::processInteractiveReplacementDialog(st return result; } +/** + * @brief Create a list of all SVG page files ordered by number + * @param documentFolder Document folder + * @return Ordered list of page files + */ +QStringList UBPersistenceManager::pageFiles(const QString& documentFolder) +{ + // create list of SVG files + QDir dir{documentFolder}; + auto svgFiles = dir.entryList({ "page*.svg"}, QDir::Files); + + // sort list by page number + std::sort(svgFiles.begin(), svgFiles.end(), [](QString& left, QString& right){ + return left.mid(4, left.length() - 8).toInt() < right.mid(4, right.length() - 8).toInt(); + }); + + return svgFiles; +} + QString UBPersistenceManager::adjustDocumentVirtualPath(const QString &str) { QStringList pathList = str.split("/", UB::SplitBehavior::SkipEmptyParts); @@ -464,11 +473,6 @@ void UBPersistenceManager::closing() } } -bool UBPersistenceManager::isSceneInCached(std::shared_ptr proxy, int index) const -{ - return mSceneCache.contains(proxy, index); -} - QStringList UBPersistenceManager::allShapes() { QString shapeLibraryPath = UBSettings::settings()->applicationShapeLibraryDirectory(); @@ -574,7 +578,6 @@ std::shared_ptr UBPersistenceManager::createDocument(const QStr if(directory.length() != 0 ) { doc = std::make_shared(directory); - doc->setPageCount(pageCount); } else{ checkIfDocumentRepositoryExists(); @@ -596,18 +599,20 @@ std::shared_ptr UBPersistenceManager::createDocument(const QStr doc->setMetaData(UBSettings::documentUpdatedAt,currentDate); doc->setMetaData(UBSettings::documentDate,currentDate); - if (withEmptyPage) + generatePathIfNeeded(doc); + QDir dir(doc->persistencePath()); + if (!dir.mkpath(doc->persistencePath())) { - createDocumentSceneAt(doc, 0); + return nullptr; // if we can't create the path, abort function. } - else + + UBDocumentToc toc{doc->persistencePath()}; + + if (withEmptyPage) { - generatePathIfNeeded(doc); - QDir dir(doc->persistencePath()); - if (!dir.mkpath(doc->persistencePath())) - { - return nullptr; // if we can't create the path, abort function. - } + const auto pageId = toc.insert(0); + const auto scene = createDocumentSceneAt(doc, pageId); + toc.setUuid(0, scene->uuid()); } bool documentAdded = false; @@ -624,6 +629,7 @@ std::shared_ptr UBPersistenceManager::createDocument(const QStr if (documentAdded) { persistDocumentMetadata(doc); + toc.save(); } else { @@ -684,14 +690,6 @@ std::shared_ptr UBPersistenceManager::createDocumentFromDir(con doc->setMetaData(UBSettings::documentName, pName); } - doc->setUuid(QUuid::createUuid()); - doc->setPageCount(sceneCount(doc)); - - for(int i = 0; i < doc->pageCount(); i++) - { - UBSvgSubsetAdaptor::setSceneUuid(doc, i, QUuid::createUuid()); - } - //work around the bool addDoc = false; if (!promptDialogIfExists) { @@ -733,12 +731,6 @@ std::shared_ptr UBPersistenceManager::duplicateDocument(std::sh UBFileSystemUtils::copyDir(pDocumentProxy->persistencePath(), copy->persistencePath()); - // regenerate scenes UUIDs - for(int i = 0; i < pDocumentProxy->pageCount(); i++) - { - UBSvgSubsetAdaptor::setSceneUuid(pDocumentProxy, i, QUuid::createUuid()); - } - foreach(QString key, pDocumentProxy->metaDatas().keys()) { copy->setMetaData(key, pDocumentProxy->metaDatas().value(key)); @@ -751,8 +743,6 @@ std::shared_ptr UBPersistenceManager::duplicateDocument(std::sh persistDocumentMetadata(copy); - copy->setPageCount(copy->pageCount()); - emit documentCreated(copy); return copy; @@ -760,363 +750,155 @@ std::shared_ptr UBPersistenceManager::duplicateDocument(std::sh } -void UBPersistenceManager::deleteDocumentScenes(std::shared_ptr proxy, const QList& indexes) +void UBPersistenceManager::deleteDocumentScenes(std::shared_ptr proxy, const QList& pageIds) { checkIfDocumentRepositoryExists(); - int pageCount = proxy->pageCount(); - - QList compactedIndexes; - - foreach(int index, indexes) + if (pageIds.size() == 0) { - if (!compactedIndexes.contains(index)) - compactedIndexes.append(index); - } - - if (compactedIndexes.size() == pageCount) - { - deleteDocument(proxy); - return; - } - - if (compactedIndexes.size() == 0) return; - - QString sourceName = proxy->metaData(UBSettings::documentName).toString(); - std::shared_ptr trashDocProxy = createDocument(UBSettings::trashedDocumentGroupNamePrefix/* + sourceGroupName*/, sourceName, false); - - foreach(int index, compactedIndexes) - { - std::shared_ptr scene = loadDocumentScene(proxy, index); - if (scene) - { - //scene is about to move into new document - foreach (QUrl relativeFile, scene->relativeDependencies()) - { - QString source = scene->document()->persistencePath() + "/" + relativeFile.toString(); - QString target = trashDocProxy->persistencePath() + "/" + relativeFile.toString(); - - QFileInfo fi(target); - QDir d = fi.dir(); - - d.mkpath(d.absolutePath()); - QFile::copy(source, target); - } - insertDocumentSceneAt(trashDocProxy, scene, trashDocProxy->pageCount(), true, true); - } } - for (int i = 1; i < indexes.size(); i++) + for (auto pageId : pageIds) { - renamePage(trashDocProxy, i , i - 1); - } - - foreach(int index, compactedIndexes) - { - QString svgFileName = proxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.svg", index); + mWorker->removePendingSaves(proxy, pageId); + QString svgFileName = proxy->persistencePath() + sceneFilenameForId(pageId); QFile::remove(svgFileName); - QString thumbFileName = proxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.thumbnail.jpg", index); - + QString thumbFileName = proxy->persistencePath() + thumbnailFilenameForId(pageId); QFile::remove(thumbFileName); - mSceneCache.removeScene(proxy, index); - - proxy->decPageCount(); - + mSceneCache.removeScene(proxy, pageId); } - std::sort(compactedIndexes.begin(), compactedIndexes.end()); - - int offset = 1; - - for (int i = compactedIndexes.at(0) + 1; i < pageCount; i++) - { - if(compactedIndexes.contains(i)) - { - offset++; - } - else - { - renamePage(proxy, i , i - offset); - - mSceneCache.moveScene(proxy, i, i - offset); - - } - } + // update metadata + QDateTime now = QDateTime::currentDateTime(); + proxy->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(now)); + persistDocumentMetadata(proxy); } -void UBPersistenceManager::duplicateDocumentScene(std::shared_ptr proxy, int index) +QUuid UBPersistenceManager::copyDocumentScene(std::shared_ptr from, int fromPageId, + std::shared_ptr to, int toPageId, QList dependencies) { checkIfDocumentRepositoryExists(); auto scene = UBApplication::boardController->activeScene(); // save modified scene of the same document - if (scene && scene->document() == proxy && scene->isModified()) + if (scene && scene->document() == from && scene->isModified()) { - auto page = UBApplication::boardController->activeSceneIndex(); - persistDocumentScene(proxy, scene, page, false, true); - } - - for (int i = proxy->pageCount(); i > index + 1; i--) - { - renamePage(proxy, i - 1 , i); - - mSceneCache.moveScene(proxy, i - 1, i); + UBApplication::boardController->persistCurrentScene(false, true); } - copyPage(proxy, index , index + 1); + // copy SVG and thumbnail + const QUuid uuid = copyPage(from, fromPageId, to, toPageId); - //TODO: write a proper way to handle object on disk - scene = loadDocumentScene(proxy, index + 1); - - foreach(QGraphicsItem* item, scene->items()) + // copy dependencies, don't overwrite + for (const auto relativeFile : dependencies) { - 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); - - widget->setUuid(newUUid); - - widget->widgetUrl(QUrl::fromLocalFile(widgetDestinationPath)); - - continue; - } - - UBGraphicsPixmapItem* pixmapItem = qgraphicsitem_cast(item); - if(pixmapItem){ - QDir imageDir = proxy->persistencePath() + "/" + UBPersistenceManager::imageDirectory; - QStringList imageFiles = imageDir.entryList({pixmapItem->uuid().toString() + ".*"}); - - if (!imageFiles.isEmpty()) - { - QString source = proxy->persistencePath() + "/" + UBPersistenceManager::imageDirectory + "/" + imageFiles.last(); - 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; - } - - 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; - } - - } - scene->setModified(true); - - proxy->incPageCount(); - - persistDocumentScene(proxy, scene, index + 1, false, true); - - persistDocumentMetadata(proxy); -} - -void UBPersistenceManager::copyDocumentScene(std::shared_ptr from, int fromIndex, std::shared_ptr to, int toIndex) -{ - if (from == to && toIndex <= fromIndex) { - qDebug() << "operation is not supported" << Q_FUNC_INFO; - return; - } - - checkIfDocumentRepositoryExists(); - - for (int i = to->pageCount(); i > toIndex; i--) { - renamePage(to, i - 1, i); - mSceneCache.moveScene(to, i - 1, i); + QString source = from->persistencePath() + "/" + relativeFile; + QString target = to->persistencePath() + "/" + relativeFile; + UBFileSystemUtils::copy(source, target, false); } - UBForeighnObjectsHandler hl; - hl.copyPage(QUrl::fromLocalFile(from->persistencePath()), fromIndex, - QUrl::fromLocalFile(to->persistencePath()), toIndex); - - to->incPageCount(); - - QString thumbTmp(from->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.thumbnail.jpg", fromIndex)); - QString thumbTo(to->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.thumbnail.jpg", toIndex)); - - QFile::remove(thumbTo); - QFile::copy(thumbTmp, thumbTo); + // update metadata + QDateTime now = QDateTime::currentDateTime(); + to->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(now)); + persistDocumentMetadata(to); - Q_ASSERT(QFileInfo(thumbTmp).exists()); - Q_ASSERT(QFileInfo(thumbTo).exists()); - auto pix = std::make_shared(thumbTmp); - UBDocumentController *ctrl = UBApplication::documentController; - ctrl->TreeViewSelectionChanged(ctrl->firstSelectedTreeIndex(), QModelIndex()); + return uuid; } -std::shared_ptr UBPersistenceManager::createDocumentSceneAt(std::shared_ptr proxy, int index, bool useUndoRedoStack) +std::shared_ptr UBPersistenceManager::createDocumentSceneAt(std::shared_ptr proxy, int pageId, bool cached, bool useUndoRedoStack) { - int count = proxy->pageCount(); + std::shared_ptr newScene; - for(int i = count - 1; i >= index; i--) + if (cached) { - renamePage(proxy, i , i + 1); + newScene = mSceneCache.createScene(proxy, pageId, useUndoRedoStack); + } + else + { + newScene = std::make_shared(proxy, useUndoRedoStack); } - mSceneCache.shiftUpScenes(proxy, index, count -1); - - std::shared_ptr newScene = mSceneCache.createScene(proxy, index, useUndoRedoStack); - - newScene->setBackground(UBSettings::settings()->isDarkBackground(), - UBSettings::settings()->UBSettings::pageBackground()); + const auto uuid = UBSettings::settings()->UBSettings::pageBackgroundUuid(); + const auto background = UBApplication::boardController->backgroundManager()->background(uuid); + newScene->setSceneBackground(UBSettings::settings()->isDarkBackground(), background); newScene->setBackgroundGridSize(UBSettings::settings()->crossSize); - proxy->incPageCount(); - - persistDocumentScene(proxy, newScene, index); + if (cached) + { + persistDocumentScene(proxy, newScene, pageId); + } return newScene; } - -void UBPersistenceManager::insertDocumentSceneAt(std::shared_ptr proxy, std::shared_ptr scene, int index, bool persist, bool deleting) +std::shared_ptr UBPersistenceManager::loadDocumentScene(std::shared_ptr proxy, int pageId) { - scene->setDocument(proxy); - - int count = proxy->pageCount(); - - for(int i = count - 1; i >= index; i--) - { - renamePage(proxy, i , i + 1); - } - - mSceneCache.shiftUpScenes(proxy, index, count -1); - - mSceneCache.insert(proxy, index, scene); - - proxy->incPageCount(); - - if (persist) - { - persistDocumentScene(proxy, scene, index); - } + mSceneCache.prepareLoading(proxy, pageId); + return mSceneCache.value(proxy, pageId); } +std::shared_ptr UBPersistenceManager::prepareSceneLoading(std::shared_ptr proxy, int pageId, std::optional xmlContent, bool cached) +{ + return mSceneCache.prepareLoading(proxy, pageId, xmlContent, cached); +} -void UBPersistenceManager::moveSceneToIndex(std::shared_ptr proxy, int source, int target) +std::shared_ptr UBPersistenceManager::getDocumentScene(std::shared_ptr pDocumentProxy, int pageId) { - checkIfDocumentRepositoryExists(); + return mSceneCache.value(pDocumentProxy, pageId); +} - if (source == target) - return; +bool UBPersistenceManager::copyAsset(std::shared_ptr proxy, const QString& fromRelativePath, const QString& toRelativePath) +{ + const auto source = proxy->persistencePath() + "/" + fromRelativePath; + const auto target = proxy->persistencePath() + "/" + toRelativePath; + return UBFileSystemUtils::copy(source, target); +} - auto scene = UBApplication::boardController->activeScene(); +void UBPersistenceManager::cleanupMediaAssets(std::shared_ptr proxy, QSet referencedMediaAssets) +{ + const auto documentPath = proxy->persistencePath() + "/"; - // save modified scene of the same document - if (scene && scene->document() == proxy && scene->isModified()) + for (const auto subfolder : mDocumentSubDirectories) { - auto page = UBApplication::boardController->activeSceneIndex(); - persistDocumentScene(proxy, scene, page, false, true); - } - - QFile svgTmp(proxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.svg", source)); - svgTmp.rename(proxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.tmp", target)); + const auto assets = QDir(documentPath + subfolder).entryInfoList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); - QFile thumbTmp(proxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.thumbnail.jpg", source)); - thumbTmp.rename(proxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.thumbnail.tmp", target)); - - if (source < target) - { - for (int i = source + 1; i <= target; i++) + for (const auto asset : assets) { - renamePage(proxy, i , i - 1); - } - } - else - { - for (int i = source - 1; i >= target; i--) - { - renamePage(proxy, i , i + 1); + const auto mediaAsset = subfolder + "/" + asset.fileName(); + + if (!referencedMediaAssets.contains(mediaAsset)) + { + if (asset.isDir()) + { + UBFileSystemUtils::deleteDir(asset.absoluteFilePath()); + } + else + { + UBFileSystemUtils::deleteFile(asset.absoluteFilePath()); + } + } } } - - QFile svg(proxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.tmp", target)); - svg.rename(proxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.svg", target)); - - QFile thumb(proxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.thumbnail.tmp", target)); - thumb.rename(proxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.thumbnail.jpg", target)); - - mSceneCache.moveScene(proxy, source, target); } - -std::shared_ptr UBPersistenceManager::loadDocumentScene(std::shared_ptr proxy, int sceneIndex, bool cacheNeighboringScenes) +void UBPersistenceManager::waitForAllSaved() const { - mSceneCache.prepareLoading(proxy, sceneIndex); - auto scene = mSceneCache.value(proxy, sceneIndex); - qDebug() << "loadDocumentScene: got result from cache"; - - if (cacheNeighboringScenes) + if (mWorker) { - if(sceneIndex + 1 < proxy->pageCount() && !mSceneCache.contains(proxy, sceneIndex + 1)) - mSceneCache.prepareLoading(proxy,sceneIndex+1); - - if(sceneIndex + 2 < proxy->pageCount() && !mSceneCache.contains(proxy, sceneIndex + 2)) - mSceneCache.prepareLoading(proxy,sceneIndex+2); - - if(sceneIndex - 1 >= 0 && !mSceneCache.contains(proxy, sceneIndex - 1)) - mSceneCache.prepareLoading(proxy,sceneIndex-1); + mWorker->waitForAllSaved(); } - - return scene; -} - -std::shared_ptr UBPersistenceManager::getDocumentScene(std::shared_ptr pDocumentProxy, int sceneIndex) -{ - return mSceneCache.value(pDocumentProxy, sceneIndex); -} - -void UBPersistenceManager::reassignDocProxy(std::shared_ptr newDocument, std::shared_ptr oldDocument) -{ - return mSceneCache.reassignDocProxy(newDocument, oldDocument); } -void UBPersistenceManager::persistDocumentScene(std::shared_ptr pDocumentProxy, std::shared_ptr pScene, const int pSceneIndex, bool isAnAutomaticBackup, bool forceImmediateSaving) +void UBPersistenceManager::persistDocumentScene(std::shared_ptr pDocumentProxy, std::shared_ptr pScene, int pageId, + bool isAnAutomaticBackup, bool forceImmediateSaving, bool addToCache) { checkIfDocumentRepositoryExists(); @@ -1130,28 +912,30 @@ void UBPersistenceManager::persistDocumentScene(std::shared_ptr if(forceImmediateSaving) { - UBSvgSubsetAdaptor::persistScene(pDocumentProxy, pScene, pSceneIndex); + mWorker->removePendingSaves(pDocumentProxy, pageId); + UBSvgSubsetAdaptor::persistScene(pDocumentProxy, pScene, pageId); } else { std::shared_ptr copiedScene = pScene->sceneDeepCopy(); - mWorker->saveScene(pDocumentProxy, copiedScene.get(), pSceneIndex); // keep copiedScene alive until saving is finished mScenesToSave.append(copiedScene); + + mWorker->saveScene(pDocumentProxy, copiedScene.get(), pageId); } - UBThumbnailAdaptor::persistScene(pDocumentProxy, pScene, pSceneIndex); pScene->setModified(false); - mSceneCache.insert(pDocumentProxy, pSceneIndex, pScene); + if (addToCache) + { + mSceneCache.insert(pDocumentProxy, pageId, pScene); + } } std::shared_ptr UBPersistenceManager::persistDocumentMetadata(std::shared_ptr pDocumentProxy, bool forceImmediateSaving) { - //cleanupDocument(pDocumentProxy); - if (forceImmediateSaving) { UBMetadataDcSubsetAdaptor::persist(pDocumentProxy); @@ -1167,67 +951,19 @@ std::shared_ptr UBPersistenceManager::persistDocumentMetadata(s } -void UBPersistenceManager::renamePage(std::shared_ptr pDocumentProxy, const int sourceIndex, const int targetIndex) -{ - UBApplication::showMessage(tr("Renaming pages (%1/%2)").arg(sourceIndex).arg(pDocumentProxy->pageCount())); - QFile svg(pDocumentProxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.svg", sourceIndex)); - svg.rename(pDocumentProxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.svg", targetIndex)); - - QFile thumb(pDocumentProxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.thumbnail.jpg", sourceIndex)); - thumb.rename(pDocumentProxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.thumbnail.jpg", targetIndex)); -} - - -void UBPersistenceManager::copyPage(std::shared_ptr pDocumentProxy, const int sourceIndex, const int targetIndex) +QUuid UBPersistenceManager::copyPage(std::shared_ptr source, const int sourceIndex, std::shared_ptr target, const int targetIndex) { - QFile svg(pDocumentProxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.svg",sourceIndex)); - svg.copy(pDocumentProxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.svg", targetIndex)); - - UBSvgSubsetAdaptor::setSceneUuid(pDocumentProxy, targetIndex, QUuid::createUuid()); - - QFile thumb(pDocumentProxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.thumbnail.jpg", sourceIndex)); - thumb.copy(pDocumentProxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.thumbnail.jpg", targetIndex)); -} - - -int UBPersistenceManager::sceneCount(const std::shared_ptr proxy) -{ - const QString pPath = proxy->persistencePath(); - - int pageIndex = 0; - bool moreToProcess = true; - bool addedMissingZeroPage = false; + QFile thumb(source->persistencePath() + thumbnailFilenameForId(sourceIndex)); + thumb.copy(target->persistencePath() + thumbnailFilenameForId(targetIndex)); - while (moreToProcess) - { - QString fileName = pPath + UBFileSystemUtils::digitFileFormat("/page%1.svg", pageIndex); - - QFile file(fileName); - - if (file.exists()) - { - pageIndex++; - } - else - { - moreToProcess = false; - } - } + const QString sourcePath = source->persistencePath() + sceneFilenameForId(sourceIndex); + const QString targetPath = target->persistencePath() + sceneFilenameForId(targetIndex); - 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; + const QUuid uuid{QUuid::createUuid()}; + UBSvgSubsetAdaptor::replicateScene(sourcePath, targetPath, uuid); + return uuid; } -QStringList UBPersistenceManager::getSceneFileNames(const QString& folder) -{ - QDir dir(folder, "page???.svg", QDir::Name, QDir::Files); - return dir.entryList(); -} QString UBPersistenceManager::generateUniqueDocumentPath(const QString& baseFolder) { @@ -1242,6 +978,15 @@ QString UBPersistenceManager::generateUniqueDocumentPath() return generateUniqueDocumentPath(UBSettings::userDocumentDirectory()); } +QString UBPersistenceManager::sceneFilenameForId(int id) +{ + return UBFileSystemUtils::digitFileFormat("/page%1.svg", id); +} + +QString UBPersistenceManager::thumbnailFilenameForId(int id) +{ + return UBFileSystemUtils::digitFileFormat("/page%1.thumbnail.jpg", id); +} void UBPersistenceManager::generatePathIfNeeded(std::shared_ptr pDocumentProxy) { @@ -1254,84 +999,43 @@ void UBPersistenceManager::generatePathIfNeeded(std::shared_ptr bool UBPersistenceManager::addDirectoryContentToDocument(const QString& documentRootFolder, std::shared_ptr pDocument) { - QStringList sourceScenes = getSceneFileNames(documentRootFolder); - if (sourceScenes.empty()) - return false; - - int targetPageCount = pDocument->pageCount(); + auto proxy = std::make_shared(documentRootFolder); + auto sourceDocument = UBDocument::getDocument(proxy); - for(int sourceIndex = 0 ; sourceIndex < sourceScenes.size(); sourceIndex++) + if (sourceDocument->pageCount() == 0) { - int targetIndex = targetPageCount + sourceIndex; - - QFile svg(documentRootFolder + "/" + sourceScenes[sourceIndex]); - if (!svg.copy(pDocument->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.svg", targetIndex))) - return false; - - UBSvgSubsetAdaptor::setSceneUuid(pDocument, targetIndex, QUuid::createUuid()); - - QFile thumb(documentRootFolder + UBFileSystemUtils::digitFileFormat("/page%1.thumbnail.jpg", sourceIndex)); - // We can ignore error in this case, thumbnail will be genarated - thumb.copy(pDocument->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.thumbnail.jpg", targetIndex)); + return false; } - foreach(QString dir, mDocumentSubDirectories) - { - qDebug() << "copying " << documentRootFolder << "/" << dir << " to " << pDocument->persistencePath() << "/" + dir; + auto targetDocument = UBDocument::getDocument(pDocument); + int targetPageCount = targetDocument->pageCount(); - QDir srcDir(documentRootFolder + "/" + dir); - if (srcDir.exists()) - if (!UBFileSystemUtils::copyDir(documentRootFolder + "/" + dir, pDocument->persistencePath() + "/" + dir)) - return false; + for (int sourceIndex = 0; sourceIndex < sourceDocument->pageCount(); sourceIndex++) + { + int targetIndex = targetPageCount + sourceIndex; + sourceDocument->copyPage(sourceIndex, targetDocument, targetIndex); } - pDocument->setPageCount(pDocument->pageCount()); - //issue NC - NNE - 20131213 : At this point, all is well done. return true; } -bool UBPersistenceManager::isEmpty(std::shared_ptr pDocumentProxy) -{ - if(!pDocumentProxy) - return true; - - if (pDocumentProxy->pageCount() > 1) - return false; - - std::shared_ptr theSoleScene = UBSvgSubsetAdaptor::loadScene(pDocumentProxy, 0); - - bool empty = false; - - if (theSoleScene) - { - empty = theSoleScene->isEmpty(); - } - else - { - empty = true; - } - - return empty; -} - -bool UBPersistenceManager::addFileToDocument(std::shared_ptr pDocumentProxy, - QString path, - const QString& subdir, - QUuid objectUuid, - QString& destinationPath, - QByteArray* data) +QUuid UBPersistenceManager::addFileToDocument(std::shared_ptr pDocumentProxy, + QString path, // full path of source file + const QString& subdir, // target subdir in document + QString& destinationPath, // return value for absolute destination path + QByteArray* data) // optional file data { Q_ASSERT(path.length()); QFileInfo fi(path); - if (!pDocumentProxy || objectUuid.isNull()) - return false; + if (!pDocumentProxy) + return {}; if (data == NULL && !fi.exists()) - return false; + return {}; - qDebug() << fi.suffix(); + QUuid objectUuid = data ? UBMediaAssetItem::createMediaAssetUuid(*data) : UBMediaAssetItem::createMediaAssetUuid(path); QString fileName = subdir + "/" + objectUuid.toString() + "." + fi.suffix(); @@ -1342,12 +1046,12 @@ bool UBPersistenceManager::addFileToDocument(std::shared_ptr pD QDir dir; dir.mkdir(pDocumentProxy->persistencePath() + "/" + subdir); if (!QFile::exists(pDocumentProxy->persistencePath() + "/" + subdir)) - return false; + return {}; if (data == NULL) { QFile source(path); - return source.copy(destinationPath); + return source.copy(destinationPath) ? objectUuid : QUuid{}; } else { @@ -1358,17 +1062,17 @@ bool UBPersistenceManager::addFileToDocument(std::shared_ptr pD qint64 n = newFile.write(*data); newFile.flush(); newFile.close(); - return n == data->size(); + return n == data->size() ? objectUuid : QUuid{}; } else { - return false; + return {}; } } } else { - return false; + return objectUuid; } } @@ -1394,7 +1098,7 @@ bool UBPersistenceManager::addGraphicsWidgetToDocument(std::shared_ptr pDocumentProxy) const -{ - static QRegularExpression uuidPattern("{[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}}"); - - if (!pDocumentProxy->testAndResetCleanupNeeded()) - { - return; - } - - // scan pages and collect possible UUID references - const QString path = pDocumentProxy->persistencePath() + "/"; - const QStringList pages = getSceneFileNames(path); - - if (pages.length() > 0) - { - QSet references; - - for (const QString& page : pages) - { - QFile svgFile(path + page); - - if (svgFile.open(QFile::ReadOnly)) - { - const QString content = svgFile.readAll(); - auto matches = uuidPattern.globalMatch(content); - - while (matches.hasNext()) - { - QRegularExpressionMatch match = matches.next(); - references << match.captured(); - } - } - } - - // scan folders and remove unreferenced files and directories - static const QStringList folders = { ".", "audios", "videos", "objects" }; - - for (const QString& folder : folders) - { - QFileInfoList entries = QDir(path + folder).entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); - - for (const QFileInfo& entry : entries) - { - QRegularExpressionMatch match = uuidPattern.match(entry.fileName()); - - if (match.hasMatch() && !references.contains(match.captured())) - { - const QString filename = folder + "/" + entry.fileName(); - const QString absoluteFilePath = entry.absoluteFilePath(); - - // unreferenced file or directory - if (entry.isDir()) - { - qWarning() << "Deleting unreferenced directory" << filename; - UBFileSystemUtils::deleteDir(absoluteFilePath); - } - else - { - qWarning() << "Deleting unreferenced file" << filename; - UBFileSystemUtils::deleteFile(absoluteFilePath); - } - } - } - } - } - else - { - //in a network-storage context, can happen on a newly created document, depending on the client and the cache mode you're using - qWarning() << "could not find any pages for the document " << pDocumentProxy->persistencePath(); - } -} - bool UBPersistenceManager::mayHaveVideo(std::shared_ptr pDocumentProxy) { QDir videoDir(pDocumentProxy->persistencePath() + "/" + UBPersistenceManager::videoDirectory); diff --git a/src/core/UBPersistenceManager.h b/src/core/UBPersistenceManager.h index 2ceee64c0..d3a7817f9 100644 --- a/src/core/UBPersistenceManager.h +++ b/src/core/UBPersistenceManager.h @@ -59,7 +59,6 @@ class UBPersistenceManager : public QObject static const QString videoDirectory; static const QString audioDirectory; static const QString widgetDirectory; - static const QString fileDirectory; // Issue 1683 (Evolution) - AOU - 20131206 static const QString myDocumentsName; static const QString modelsName; @@ -98,23 +97,23 @@ class UBPersistenceManager : public QObject virtual void deleteDocument(std::shared_ptr pDocumentProxy); - virtual void deleteDocumentScenes(std::shared_ptr pDocumentProxy, const QList& indexes); + virtual void deleteDocumentScenes(std::shared_ptr pDocumentProxy, const QList& pageIds); - virtual void duplicateDocumentScene(std::shared_ptr pDocumentProxy, int index); + virtual QUuid copyDocumentScene(std::shared_ptrfrom, int fromPageId, std::shared_ptrto, int toPageId, QList dependencies); - virtual void copyDocumentScene(std::shared_ptrfrom, int fromIndex, std::shared_ptrto, int toIndex); + virtual void persistDocumentScene(std::shared_ptr pDocumentProxy, std::shared_ptr pScene, int pageId, + bool isAnAutomaticBackup = false, bool forceImmediateSaving = false, bool addToCache = true); - virtual void persistDocumentScene(std::shared_ptr pDocumentProxy, std::shared_ptr pScene, const int pSceneIndex, bool isAnAutomaticBackup = false, bool forceImmediateSaving = false); + virtual std::shared_ptr createDocumentSceneAt(std::shared_ptr pDocumentProxy, int pageId, bool cached = true, bool useUndoRedoStack = true); - virtual std::shared_ptr createDocumentSceneAt(std::shared_ptr pDocumentProxy, int index, bool useUndoRedoStack = true); + virtual std::shared_ptr loadDocumentScene(std::shared_ptr pDocumentProxy, int pageId); + std::shared_ptr prepareSceneLoading(std::shared_ptr proxy, int pageId, std::optional xmlContent = {}, bool cached = true); + std::shared_ptr getDocumentScene(std::shared_ptr pDocumentProxy, int pageId); - virtual void insertDocumentSceneAt(std::shared_ptr pDocumentProxy, std::shared_ptr scene, int index, bool persist = true, bool deleting = false); + bool copyAsset(std::shared_ptr proxy, const QString& fromRelativePath, const QString& toRelativePath); + void cleanupMediaAssets(std::shared_ptr proxy, QSet referencedMediaAssets); - virtual void moveSceneToIndex(std::shared_ptr pDocumentProxy, int source, int target); - - virtual std::shared_ptr loadDocumentScene(std::shared_ptr pDocumentProxy, int sceneIndex, bool cacheNeighboringScenes = true); - std::shared_ptr getDocumentScene(std::shared_ptr pDocumentProxy, int sceneIndex); - void reassignDocProxy(std::shared_ptr newDocument, std::shared_ptr oldDocument); + void waitForAllSaved() const; // QList > documentProxies; UBDocumentTreeNode *mDocumentTreeStructure; @@ -128,6 +127,8 @@ class UBPersistenceManager : public QObject QString generateUniqueDocumentPath(); QString generateUniqueDocumentPath(const QString& baseFolder); + static QString sceneFilenameForId(int id); + static QString thumbnailFilenameForId(int id); bool addDirectoryContentToDocument(const QString& documentRootFolder, std::shared_ptr pDocument); @@ -136,15 +137,15 @@ class UBPersistenceManager : public QObject static std::shared_ptr createDocumentProxyStructure(const QFileInfo &contentInfo); QDialog::DialogCode processInteractiveReplacementDialog(std::shared_ptr pProxy, bool multipleFiles = false); + QStringList pageFiles(const QString& documentFolder); + QStringList documentSubDirectories() { return mDocumentSubDirectories; } - virtual bool isEmpty(std::shared_ptr pDocumentProxy); - bool addGraphicsWidgetToDocument(std::shared_ptr mDocumentProxy, QString path, QUuid objectUuid, QString& destinationPath); - bool addFileToDocument(std::shared_ptr pDocumentProxy, QString path, const QString& subdir, QUuid objectUuid, QString& destinationPath, QByteArray* data = NULL); + QUuid addFileToDocument(std::shared_ptr pDocumentProxy, QString path, const QString& subdir, QString& destinationPath, QByteArray* data = NULL); bool mayHaveVideo(std::shared_ptr pDocumentProxy); bool mayHaveAudio(std::shared_ptr pDocumentProxy); @@ -155,32 +156,25 @@ class UBPersistenceManager : public QObject QString adjustDocumentVirtualPath(const QString &str); void closing(); - bool isSceneInCached(std::shared_ptrproxy, int index) const; signals: void documentCreated(std::shared_ptr pDocumentProxy); void documentMetadataChanged(std::shared_ptr pDocumentProxy); // The following signals are emitted in UBDocument - void documentSceneDuplicated(std::shared_ptr pDocumentProxy, int pIndex); - void documentSceneMoved(std::shared_ptr pDocumentProxy, int fromIndex, int toIndex); - void documentSceneDeleted(std::shared_ptr pDocumentProxy, int pIndex); + void documentSceneDuplicated(UBDocument* document, int pIndex); + void documentSceneMoved(UBDocument* document, int fromIndex, int toIndex); + void documentSceneDeleted(UBDocument* document, int pIndex); private: - static int sceneCount(const std::shared_ptr pDocumentProxy); - static QStringList getSceneFileNames(const QString& folder); - void renamePage(std::shared_ptr pDocumentProxy, - const int sourceIndex, const int targetIndex); - void copyPage(std::shared_ptr pDocumentProxy, - const int sourceIndex, const int targetIndex); + QUuid copyPage(std::shared_ptr source, const int sourceIndex, + std::shared_ptr target, const int targetIndex); void generatePathIfNeeded(std::shared_ptr pDocumentProxy); void checkIfDocumentRepositoryExists(); void saveFoldersTreeToXml(QXmlStreamWriter &writer, const QModelIndex &parentIndex); void loadFolderTreeFromXml(const QString &path, const QDomElement &element); - void cleanupDocument(std::shared_ptr pDocumentProxy) const; - QString xmlFolderStructureFilename; UBSceneCache mSceneCache; diff --git a/src/core/UBPersistenceWorker.cpp b/src/core/UBPersistenceWorker.cpp index 93476b269..103757489 100644 --- a/src/core/UBPersistenceWorker.cpp +++ b/src/core/UBPersistenceWorker.cpp @@ -37,9 +37,9 @@ UBPersistenceWorker::UBPersistenceWorker(QObject *parent) : { } -void UBPersistenceWorker::saveScene(std::shared_ptr proxy, UBGraphicsScene *scene, const int pageIndex) +void UBPersistenceWorker::saveScene(std::shared_ptr proxy, UBGraphicsScene *scene, const int pageId) { - PersistenceInformation entry = {WriteScene, proxy, scene, pageIndex}; + PersistenceInformation entry = {WriteScene, proxy, scene, pageId}; QMutexLocker locker(&mMutex); saves.append(entry); @@ -54,9 +54,37 @@ void UBPersistenceWorker::saveMetadata(std::shared_ptr proxy) mSemaphore.release(); } +void UBPersistenceWorker::removePendingSaves(std::shared_ptr proxy, const int pageId) +{ + QMutexLocker savingLocker(&mSaving); + QMutexLocker locker(&mMutex); + + for (auto& info : saves) + { + if (info.action == WriteScene && info.proxy == proxy && info.pageId == pageId) + { + // deactivate entry, release scene + qDebug() << "removed pending save for page id" << pageId; + info.action = Noop; + emit scenePersisted(info.scene); + } + } +} + +void UBPersistenceWorker::waitForAllSaved() +{ + QMutexLocker locker(&mSaving); + + if (!saves.isEmpty()) + { + mNoMoreSaves.wait(&mSaving); + } +} + void UBPersistenceWorker::applicationWillClose() { qDebug() << "application Will close signal received"; + waitForAllSaved(); mReceivedApplicationClosing = true; mSemaphore.release(); } @@ -65,22 +93,37 @@ void UBPersistenceWorker::process() { qDebug() << "process starts"; mSemaphore.acquire(); - do{ - PersistenceInformation info; + + while (!mReceivedApplicationClosing) + { { - QMutexLocker locker(&mMutex); - info = saves.takeFirst(); - } - if(info.action == WriteScene){ - UBSvgSubsetAdaptor::persistScene(info.proxy, info.scene->shared_from_this(), info.sceneIndex); - emit scenePersisted(info.scene); - } - else if (info.action == WriteMetadata) { - UBMetadataDcSubsetAdaptor::persist(info.proxy); - emit metadataPersisted(info.proxy); + QMutexLocker saving(&mSaving); + PersistenceInformation info; + + { + QMutexLocker locker(&mMutex); + info = saves.takeFirst(); + } + + if (info.action == WriteScene) + { + UBSvgSubsetAdaptor::persistScene(info.proxy, info.scene->shared_from_this(), info.pageId); + emit scenePersisted(info.scene); + } + else if (info.action == WriteMetadata) + { + UBMetadataDcSubsetAdaptor::persist(info.proxy); + emit metadataPersisted(info.proxy); + } + + if (saves.isEmpty()) + { + mNoMoreSaves.notify_all(); + } } + mSemaphore.acquire(); - }while(!mReceivedApplicationClosing); + } qDebug() << "process will stop"; emit finished(); } diff --git a/src/core/UBPersistenceWorker.h b/src/core/UBPersistenceWorker.h index 93113b61e..a14d24f70 100644 --- a/src/core/UBPersistenceWorker.h +++ b/src/core/UBPersistenceWorker.h @@ -31,11 +31,14 @@ #include #include +#include + #include "document/UBDocumentProxy.h" #include "domain/UBGraphicsScene.h" typedef enum{ - WriteScene = 0, + Noop, + WriteScene, WriteMetadata }ActionType; @@ -43,7 +46,7 @@ typedef struct{ ActionType action; std::shared_ptr proxy; UBGraphicsScene* scene; - int sceneIndex; + int pageId; }PersistenceInformation; class UBPersistenceWorker : public QObject @@ -52,9 +55,12 @@ class UBPersistenceWorker : public QObject public: explicit UBPersistenceWorker(QObject *parent = 0); - void saveScene(std::shared_ptr proxy, UBGraphicsScene* scene, const int pageIndex); + void saveScene(std::shared_ptr proxy, UBGraphicsScene* scene, const int pageId); void saveMetadata(std::shared_ptr proxy); + void removePendingSaves(std::shared_ptr proxy, const int pageId); + void waitForAllSaved(); + signals: void finished(); void error(QString string); @@ -69,6 +75,8 @@ public slots: bool mReceivedApplicationClosing; QSemaphore mSemaphore; QMutex mMutex; + QMutex mSaving; + QWaitCondition mNoMoreSaves; QList saves; }; diff --git a/src/core/UBPreferencesController.cpp b/src/core/UBPreferencesController.cpp index ed0e393d8..76c5fa479 100644 --- a/src/core/UBPreferencesController.cpp +++ b/src/core/UBPreferencesController.cpp @@ -31,12 +31,17 @@ #include "UBSettings.h" #include "UBApplication.h" +#include +#include #include "gui/UBCircleFrame.h" #include "core/UBSetting.h" #include "core/UBApplicationController.h" #include "core/UBDisplayManager.h" +#include "core/UBThemeManager.h" +#include "core/UBShortcutManager.h" +#include "frameworks/UBPlatformUtils.h" #include "frameworks/UBStringUtils.h" @@ -50,7 +55,6 @@ #include "core/memcheck.h" - qreal UBPreferencesController::sSliderRatio = 10.0; qreal UBPreferencesController::sMinPenWidth = 0.5; qreal UBPreferencesController::sMaxPenWidth = 50.0; @@ -72,7 +76,6 @@ void UBPreferencesDialog::closeEvent(QCloseEvent* e) } - UBPreferencesController::UBPreferencesController(QWidget *parent) : QObject(parent) , mPreferencesWindow(0) @@ -88,6 +91,11 @@ UBPreferencesController::UBPreferencesController(QWidget *parent) adjustScreensPreferences(); connect(UBApplication::displayManager, &UBDisplayManager::availableScreenCountChanged, this, &UBPreferencesController::adjustScreensPreferences); + +#if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)) + mPreferencesUI->keySequence->setMaximumSequenceLength(1); +#endif + wire(); } @@ -103,6 +111,56 @@ UBPreferencesController::~UBPreferencesController() delete mMarkerProperties; } +bool UBPreferencesController::handleMouseEvent(QMouseEvent *event) +{ + if (!mPreferencesUI->recordButton->isChecked() + || mPreferencesUI->mainTabWidget->currentWidget() != mPreferencesUI->shortcutTab) + { + return false; + } + + Qt::MouseButton button = event->button(); + + if (currentIndex.isValid()) + { + QString buttonName = UBShortcutManager::buttonName(button); + mPreferencesUI->mouseButton->setText(buttonName); + bool ok = UBShortcutManager::shortcutManager()->checkData(currentIndex.siblingAtColumn(3), buttonName); + applyShortcutFilter(ok ? mPreferencesUI->filter->text() : buttonName, ok ? -1 : 3); + mPreferencesUI->recordButton->setEnabled(ok); + mPreferencesUI->report->setText(ok ? "" : tr("Mouse button already in use")); + event->accept(); + return true; + } + + return false; +} + +bool UBPreferencesController::handleTabletEvent(QTabletEvent *event) +{ + if (!mPreferencesUI->recordButton->isChecked() + || mPreferencesUI->mainTabWidget->currentWidget() != mPreferencesUI->shortcutTab) + { + return false; + } + + Qt::MouseButton button = event->button(); + + if (currentIndex.isValid()) + { + QString buttonName = UBShortcutManager::buttonName(button); + mPreferencesUI->stylusButton->setText(buttonName); + bool ok = UBShortcutManager::shortcutManager()->checkData(currentIndex.siblingAtColumn(4), buttonName); + applyShortcutFilter(ok ? mPreferencesUI->filter->text() : buttonName, ok ? -1 : 4); + mPreferencesUI->recordButton->setEnabled(ok); + mPreferencesUI->report->setText(ok ? "" : tr("Stylus button already in use")); + event->accept(); + return true; + } + + return false; +} + void UBPreferencesController::adjustScreensPreferences() { bool enabled = UBApplication::displayManager->numScreens() > 1; @@ -137,6 +195,69 @@ void UBPreferencesController::adjustScreensPreferences() UBSettings::settings()->appScreenList->set(value); mPreferencesUI->screenList->loadScreenList(value.toStringList()); } + + if (mPreferencesUI->screenLayoutPreview) + { + mPreferencesUI->screenLayoutPreview->refreshScreens(); + } +} + +void UBPreferencesController::applyShortcutFilter(const QString &filter, int filterCol) +{ + // go throug rows in reverse direction + QAbstractItemModel* model = mPreferencesUI->shortcutTableView->model(); + QModelIndex index = model->index(0, 0); + bool groupVisible = false; + int minCol = filterCol < 0 ? 0 : filterCol; + int maxCol = filterCol < 0 ? model->columnCount() : filterCol + 1; + + for (int row = model->rowCount() - 1; row >= 0; --row) + { + QModelIndex rowIndex = index.siblingAtRow(row); + bool match = false; + bool header = model->data(rowIndex, UBShortcutManager::GroupHeaderRole).toBool(); + + if (header) + { + match = groupVisible; + groupVisible = false; + } + else if (currentIndex.isValid() && currentIndex.row() == row) + { + match = true; + groupVisible = true; + } + else + { + for (int col = minCol; col < maxCol; ++col) + { + QModelIndex colIndex = rowIndex.siblingAtColumn(col); + const auto colData{model->data(colIndex).toString()}; + const auto exactMatch{filterCol >= 0}; + + bool hasMatch{false}; + + if (exactMatch) + { + const auto tokens = colData.split(", "); + hasMatch = tokens.contains(filter); + } + else + { + hasMatch = colData.contains(filter, Qt::CaseInsensitive); + } + + if (hasMatch) + { + match = true; + groupVisible = true; + break; + } + } + } + + mPreferencesUI->shortcutTableView->setRowHidden(row, !match); + } } void UBPreferencesController::show() @@ -158,7 +279,7 @@ void UBPreferencesController::wire() connect(mPreferencesUI->defaultSettingsButton, SIGNAL(released()), this, SLOT(defaultSettings())); connect(mPreferencesUI->screenList, &UBScreenListLineEdit::screenListChanged, this, [this,settings](const QStringList screenList){ - settings->appScreenList->set(screenList); + mScreenList = screenList; if (!mScreenConfigurationPath.isEmpty()) { @@ -186,11 +307,16 @@ void UBPreferencesController::wire() connect(mPreferencesUI->emptyTrashDaysValue, SIGNAL(valueChanged(int)), settings->emptyTrashDaysValue, SLOT(setInt(int))); - connect(mPreferencesUI->keyboardPaletteKeyButtonSize, qOverload(&QComboBox::currentIndexChanged), settings->boardKeyboardPaletteKeyBtnSize, [=](int index) { + connect(mPreferencesUI->keyboardPaletteKeyButtonSize, qOverload(&QComboBox::currentIndexChanged), settings->boardKeyboardPaletteKeyBtnSize, [this, settings](int index) { settings->boardKeyboardPaletteKeyBtnSize->setString(mPreferencesUI->keyboardPaletteKeyButtonSize->itemText(index)); }); connect(mPreferencesUI->startModeComboBox, SIGNAL(currentIndexChanged(int)), settings->appStartMode, SLOT(setInt(int))); + connect(mPreferencesUI->themeComboBox, qOverload(&QComboBox::currentIndexChanged), this, [=](int index) { + settings->appThemeMode->setInt(index); + UBThemeManager::instance()->applyUserThemePreference(); + }); + connect(mPreferencesUI->useExternalBrowserCheckBox, SIGNAL(clicked(bool)), settings->webUseExternalBrowser, SLOT(setBool(bool))); connect(mPreferencesUI->displayBrowserPageCheckBox, SIGNAL(clicked(bool)), settings->webShowPageImmediatelyOnMirroredScreen, SLOT(setBool(bool))); @@ -253,16 +379,7 @@ void UBPreferencesController::wire() connect(mPreferencesUI->darkBackgroundOpacitySlider, SIGNAL(valueChanged(int)), this, SLOT(darkBackgroundCrossOpacityValueChanged(int))); // pen - QList penLightBackgroundColors = settings->boardPenLightBackgroundColors->colors(); - QList penDarkBackgroundColors = settings->boardPenDarkBackgroundColors->colors(); - QList penLightBackgroundSelectedColors = settings->boardPenLightBackgroundSelectedColors->colors(); - QList penDarkBackgroundSelectedColors = settings->boardPenDarkBackgroundSelectedColors->colors(); - - mPenProperties = new UBBrushPropertiesFrame(mPreferencesUI->penFrame, - penLightBackgroundColors, penDarkBackgroundColors, penLightBackgroundSelectedColors, - penDarkBackgroundSelectedColors, this); - - mPenProperties->opacityFrame->hide(); + mPenProperties = new UBBrushPropertiesFrame(mPreferencesUI->penFrame); connect(mPenProperties->fineSlider, SIGNAL(valueChanged(int)), this, SLOT(widthSliderChanged(int))); connect(mPenProperties->mediumSlider, SIGNAL(valueChanged(int)), this, SLOT(widthSliderChanged(int))); @@ -272,14 +389,7 @@ void UBPreferencesController::wire() connect(mPenProperties->circleSpinBox, SIGNAL(valueChanged(int)), this, SLOT(penPreviewFromSizeChanged(int))); // marker - QList markerLightBackgroundColors = settings->boardMarkerLightBackgroundColors->colors(); - QList markerDarkBackgroundColors = settings->boardMarkerDarkBackgroundColors->colors(); - QList markerLightBackgroundSelectedColors = settings->boardMarkerLightBackgroundSelectedColors->colors(); - QList markerDarkBackgroundSelectedColors = settings->boardMarkerDarkBackgroundSelectedColors->colors(); - - mMarkerProperties = new UBBrushPropertiesFrame(mPreferencesUI->markerFrame, markerLightBackgroundColors, - markerDarkBackgroundColors, markerLightBackgroundSelectedColors, - markerDarkBackgroundSelectedColors, this); + mMarkerProperties = new UBBrushPropertiesFrame(mPreferencesUI->markerFrame); mMarkerProperties->pressureSensitiveCheckBox->setText(tr("Marker is pressure sensitive")); @@ -289,10 +399,51 @@ void UBPreferencesController::wire() connect(mMarkerProperties->mediumSlider, SIGNAL(valueChanged(int)), this, SLOT(widthSliderChanged(int))); connect(mMarkerProperties->strongSlider, SIGNAL(valueChanged(int)), this, SLOT(widthSliderChanged(int))); connect(mMarkerProperties->pressureSensitiveCheckBox, SIGNAL(clicked(bool)), settings, SLOT(setMarkerPressureSensitive(bool))); - connect(mMarkerProperties->opacitySlider, SIGNAL(valueChanged(int)), this, SLOT(opacitySliderChanged(int))); // about tab connect(mPreferencesUI->checkSoftwareUpdateAtLaunchCheckBox, SIGNAL(clicked(bool)), settings->appEnableAutomaticSoftwareUpdates, SLOT(setBool(bool))); + + // shortcut tab + connect(mPreferencesUI->shortcutTableView, &QTableView::clicked, this, &UBPreferencesController::actionSelected); + connect(mPreferencesUI->shortcutTableView, &QTableView::doubleClicked, this, [this](){ + mPreferencesUI->recordButton->setChecked(true); + mPreferencesUI->keySequence->setFocus(); + }); + connect(mPreferencesUI->filter, &QLineEdit::textChanged, this, [this](const QString& text){applyShortcutFilter(text);}); + connect(mPreferencesUI->recordButton, &QPushButton::clicked, this, &UBPreferencesController::recordingClicked); + connect(mPreferencesUI->recordButton, &QPushButton::toggled, mPreferencesUI->shortcutScrollArea, &QScrollArea::setDisabled); + connect(mPreferencesUI->abortButton, &QPushButton::clicked, this, &UBPreferencesController::abortClicked); + connect(mPreferencesUI->resetButton, &QPushButton::clicked, this, &UBPreferencesController::resetClicked); + connect(mPreferencesUI->noCtrl, &QCheckBox::toggled, UBShortcutManager::shortcutManager(), &UBShortcutManager::ignoreCtrl); + connect(mPreferencesUI->mainTabWidget, &QTabWidget::currentChanged, [this](int tab){ + auto shortcutTab = mPreferencesUI->mainTabWidget->indexOf(mPreferencesUI->shortcutTab); + + if (tab != shortcutTab) { + abortClicked(); + } + }); + connect(mPreferencesUI->keySequence, &QKeySequenceEdit::keySequenceChanged, this, [this](const QKeySequence& keySequence){ + // check validity of shortcut + if (currentIndex.isValid()) + { + const QString keyString{keySequence.toString()}; + bool ok = UBShortcutManager::shortcutManager()->checkData(currentIndex.siblingAtColumn(2), keyString); + applyShortcutFilter(ok ? mPreferencesUI->filter->text() : keyString, ok ? -1 : 2); + mPreferencesUI->recordButton->setEnabled(ok); + mPreferencesUI->report->setText(ok ? "" : tr("Key sequence already in use")); + mPreferencesUI->noCtrl->setEnabled(!UBShortcutManager::shortcutManager()->hasCtrlConflicts(keySequence)); + } + }); + connect(UBApplication::app(), &QApplication::focusChanged, this, [this](QWidget*, QWidget* now){ + // activate recording when input field gets focus + if (now == mPreferencesUI->keySequence || now == mPreferencesUI->mouseButton || now == mPreferencesUI->stylusButton) + { + if (mPreferencesUI->recordButton->isEnabled() && !mPreferencesUI->recordButton->isChecked()) + { + mPreferencesUI->recordButton->click(); + } + } + }); } void UBPreferencesController::init() @@ -320,6 +471,7 @@ void UBPreferencesController::init() mPreferencesUI->emptyTrashDaysValue->setValue(settings->emptyTrashDaysValue->get().toInt()); mPreferencesUI->startModeComboBox->setCurrentIndex(settings->appStartMode->get().toInt()); + mPreferencesUI->themeComboBox->setCurrentIndex(settings->appThemeMode->get().toInt()); mPreferencesUI->useExternalBrowserCheckBox->setChecked(settings->webUseExternalBrowser->get().toBool()); mPreferencesUI->displayBrowserPageCheckBox->setChecked(settings->webShowPageImmediatelyOnMirroredScreen->get().toBool()); @@ -349,8 +501,13 @@ void UBPreferencesController::init() mMarkerProperties->strongSlider->setValue(settings->boardMarkerStrongWidth->get().toDouble() * sSliderRatio); mMarkerProperties->pressureSensitiveCheckBox->setChecked(settings->boardMarkerPressureSensitive->get().toBool()); - mMarkerProperties->opacitySlider->setValue(settings->boardMarkerAlpha->get().toDouble() * 100); - + // shortcut tab + mPreferencesUI->shortcutTableView->setModel(UBShortcutManager::shortcutManager()); + mPreferencesUI->shortcutTableView->horizontalHeader()->setModel(UBShortcutManager::shortcutManager()); + mPreferencesUI->shortcutTableView->horizontalHeader()->resizeSection(0, 150); + mPreferencesUI->shortcutTableView->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); + mPreferencesUI->noCtrl->setChecked(settings->value("Shortcut/IgnoreCtrl").toBool()); + mPreferencesUI->noCtrl->setEnabled(!UBShortcutManager::shortcutManager()->hasCtrlConflicts()); } void UBPreferencesController::close() @@ -362,6 +519,12 @@ void UBPreferencesController::close() UBSettings::settings()->setProxyUsername(mPreferencesUI->proxyUsername->text()); UBSettings::settings()->setProxyPassword(mPreferencesUI->proxyPassword->text()); + // screen settings + if (mScreenList != UBSettings::settings()->appScreenList->get().toStringList()) + { + UBSettings::settings()->appScreenList->set(mScreenList); + } + mPreferencesWindow->accept(); } @@ -405,18 +568,6 @@ void UBPreferencesController::defaultSettings() mPenProperties->pressureSensitiveCheckBox->setChecked(settings->boardPenPressureSensitive->reset().toBool()); mPenProperties->circleCheckBox->setChecked(settings->showPenPreviewCircle->reset().toBool()); mPenProperties->circleSpinBox->setValue(settings->penPreviewFromSize->reset().toInt()); - - settings->boardPenLightBackgroundSelectedColors->reset(); - QList lightBackgroundSelectedColors = settings->boardPenLightBackgroundSelectedColors->colors(); - - settings->boardPenDarkBackgroundSelectedColors->reset(); - QList darkBackgroundSelectedColors = settings->boardPenDarkBackgroundSelectedColors->colors(); - - for (int i = 0 ; i < settings->colorPaletteSize ; i++) - { - mPenProperties->lightBackgroundColorPickers[i]->setSelectedColorIndex(lightBackgroundSelectedColors.indexOf(settings->penColors(false).at(i))); - mPenProperties->darkBackgroundColorPickers[i]->setSelectedColorIndex(darkBackgroundSelectedColors.indexOf(settings->penColors(true).at(i))); - } } else if (mPreferencesUI->mainTabWidget->currentWidget() == mPreferencesUI->markerTab) { @@ -424,20 +575,6 @@ void UBPreferencesController::defaultSettings() mMarkerProperties->mediumSlider->setValue(settings->boardMarkerMediumWidth->reset().toDouble() * sSliderRatio); mMarkerProperties->strongSlider->setValue(settings->boardMarkerStrongWidth->reset().toDouble() * sSliderRatio); mMarkerProperties->pressureSensitiveCheckBox->setChecked(settings->boardMarkerPressureSensitive->reset().toBool()); - - mMarkerProperties->opacitySlider->setValue(settings->boardMarkerAlpha->reset().toDouble() * 100); - - settings->boardMarkerLightBackgroundSelectedColors->reset(); - QList lightBackgroundSelectedColors = settings->boardMarkerLightBackgroundSelectedColors->colors(); - - settings->boardMarkerDarkBackgroundSelectedColors->reset(); - QList darkBackgroundSelectedColors = settings->boardMarkerDarkBackgroundSelectedColors->colors(); - - for (int i = 0 ; i < settings->colorPaletteSize ; i++) - { - mMarkerProperties->lightBackgroundColorPickers[i]->setSelectedColorIndex(lightBackgroundSelectedColors.indexOf(settings->markerColors(false).at(i))); - mMarkerProperties->darkBackgroundColorPickers[i]->setSelectedColorIndex(darkBackgroundSelectedColors.indexOf(settings->markerColors(true).at(i))); - } } else if (mPreferencesUI->mainTabWidget->currentWidget() == mPreferencesUI->aboutTab) { @@ -445,7 +582,8 @@ void UBPreferencesController::defaultSettings() mPreferencesUI->checkSoftwareUpdateAtLaunchCheckBox->setChecked(defaultValue); } - else if(mPreferencesUI->mainTabWidget->currentWidget() == mPreferencesUI->networkTab){ + else if(mPreferencesUI->mainTabWidget->currentWidget() == mPreferencesUI->networkTab) + { bool defaultValue = settings->webUseExternalBrowser->reset().toBool(); mPreferencesUI->useExternalBrowserCheckBox->setChecked(defaultValue); defaultValue = settings->webShowPageImmediatelyOnMirroredScreen->reset().toBool(); @@ -480,6 +618,23 @@ void UBPreferencesController::defaultSettings() lightBackgroundCrossOpacityValueChanged(lightBackgroundOpacity); } + else if(mPreferencesUI->mainTabWidget->currentWidget() == mPreferencesUI->shortcutTab) + { + if (mPreferencesUI->recordButton->isChecked()) + { + abortClicked(); + } + + UBShortcutManager* sm = UBShortcutManager::shortcutManager(); + + for (int row = 0; row < sm->rowCount(); ++row) + { + QModelIndex rowIndex = sm->index(row, 0); + sm->resetData(rowIndex); + } + + resetClicked(); + } } void UBPreferencesController::darkBackgroundCrossOpacityValueChanged(int value) @@ -588,54 +743,6 @@ void UBPreferencesController::widthSliderChanged(int value) } -void UBPreferencesController::opacitySliderChanged(int value) -{ - qreal opacity = ((qreal)value) / 100; - - QObject *slider = sender(); - - if (slider == mMarkerProperties->opacitySlider) - { - UBDrawingController::drawingController()->setMarkerAlpha(opacity); - } -} - - -void UBPreferencesController::colorSelected(const QColor& color) -{ - UBColorPicker *colorPicker = qobject_cast(sender()); - - int index = mPenProperties->lightBackgroundColorPickers.indexOf(colorPicker); - - if (index >= 0) - { - UBDrawingController::drawingController()->setPenColor(false, color, index); - return; - } - - index = mPenProperties->darkBackgroundColorPickers.indexOf(colorPicker); - - if (index >= 0) - { - UBDrawingController::drawingController()->setPenColor(true, color, index); - } - - index = mMarkerProperties->lightBackgroundColorPickers.indexOf(colorPicker); - - if (index >= 0) - { - UBDrawingController::drawingController()->setMarkerColor(false, color, index); - } - - index = mMarkerProperties->darkBackgroundColorPickers.indexOf(colorPicker); - - if (index >= 0) - { - UBDrawingController::drawingController()->setMarkerColor(true, color, index); - } - -} - void UBPreferencesController::setCrossColorOnDarkBackground(const QColor& color) { UBSettings::settings()->boardCrossColorDarkBackground->set(color.name(QColor::HexArgb)); @@ -690,79 +797,83 @@ void UBPreferencesController::systemOSKCheckBoxToggled(bool checked) mPreferencesUI->keyboardPaletteKeyButtonSize_Label->setVisible(!checked); } -UBBrushPropertiesFrame::UBBrushPropertiesFrame(QFrame* owner, const QList& lightBackgroundColors, - const QList& darkBackgroundColors, const QList& lightBackgroundSelectedColors, - const QList& darkBackgroundSelectedColors, UBPreferencesController* controller) +void UBPreferencesController::actionSelected(const QModelIndex& index) { - setupUi(owner); - - QPalette lightBackgroundPalette = QApplication::palette(); - lightBackgroundPalette.setColor(QPalette::Window, Qt::white); - - lightBackgroundFrame->setAutoFillBackground(true); - lightBackgroundFrame->setPalette(lightBackgroundPalette); - - QPalette darkBackgroundPalette = QApplication::palette(); - darkBackgroundPalette.setColor(QPalette::Window, Qt::black); - darkBackgroundPalette.setColor(QPalette::ButtonText, Qt::white); - darkBackgroundPalette.setColor(QPalette::WindowText, Qt::white); - - darkBackgroundFrame->setAutoFillBackground(true); - darkBackgroundFrame->setPalette(darkBackgroundPalette); - darkBackgroundLabel->setPalette(darkBackgroundPalette); - - QList firstLightBackgroundColor; - firstLightBackgroundColor.append(lightBackgroundColors[0]); - - lightBackgroundColorPicker0->setColors(firstLightBackgroundColor); - lightBackgroundColorPicker0->setSelectedColorIndex(0); - lightBackgroundColorPickers.append(lightBackgroundColorPicker0); - - for (int i = 1 ; i < UBSettings::settings()->colorPaletteSize ; i++) + if (mPreferencesUI->recordButton->isChecked()) { - UBColorPicker *picker = new UBColorPicker(lightBackgroundFrame); - picker->setObjectName(QString("penLightBackgroundColor%1").arg(i)); - picker->setMinimumSize(QSize(32, 32)); - picker->setFrameShape(QFrame::StyledPanel); - picker->setFrameShadow(QFrame::Raised); - - lightBackgroundLayout->addWidget(picker); - - picker->setColors(lightBackgroundColors); - - picker->setSelectedColorIndex(lightBackgroundColors.indexOf(lightBackgroundSelectedColors.at(i))); + return; + } - lightBackgroundColorPickers.append(picker); + currentIndex = index; + UBShortcutManager* sm = UBShortcutManager::shortcutManager(); + const QKeySequence keySequence{sm->data(index.siblingAtColumn(2), UBShortcutManager::PrimaryShortcutRole).toString()}; + mPreferencesUI->keySequence->setKeySequence(keySequence); + mPreferencesUI->mouseButton->setText(sm->data(index.siblingAtColumn(3)).toString()); + mPreferencesUI->stylusButton->setText(sm->data(index.siblingAtColumn(4)).toString()); - QObject::connect(picker, SIGNAL(colorSelected(const QColor&)), controller, SLOT(colorSelected(const QColor&))); + bool isAction = sm->data(index, UBShortcutManager::ActionRole).toBool(); + mPreferencesUI->recordButton->setEnabled(true); + mPreferencesUI->shortcutsGroupBox->setEnabled(isAction); +} +void UBPreferencesController::recordingClicked(bool checked) +{ + if (!checked && currentIndex.isValid()) + { + // recording finished + UBShortcutManager* sm = UBShortcutManager::shortcutManager(); + sm->setData(currentIndex.siblingAtColumn(2), mPreferencesUI->keySequence->keySequence().toString()); + sm->setData(currentIndex.siblingAtColumn(3), sm->buttonIndex(mPreferencesUI->mouseButton->text())); + sm->setData(currentIndex.siblingAtColumn(4), sm->buttonIndex(mPreferencesUI->stylusButton->text())); + } + else if (checked) + { + mPreferencesUI->keySequence->setFocus(); } - QList firstDarkBackgroundColor; - firstDarkBackgroundColor.append(darkBackgroundColors[0]); + mPreferencesUI->shortcutTableView->setSelectionMode(checked ? QTableView::NoSelection : QTableView::SingleSelection); +// mPreferencesUI->recordButton->setText(checked ? tr("Accept", "preferencesDialog") : tr("Record", "preferencesDialog")); +} - darkBackgroundColorPicker0->setColors(firstDarkBackgroundColor); - darkBackgroundColorPicker0->setSelectedColorIndex(0); - darkBackgroundColorPickers.append(darkBackgroundColorPicker0); +void UBPreferencesController::abortClicked() +{ + applyShortcutFilter(mPreferencesUI->filter->text()); + + mPreferencesUI->recordButton->setEnabled(true); + mPreferencesUI->recordButton->setChecked(false); +// mPreferencesUI->recordButton->setText(tr("Record", "preferencesDialog")); + mPreferencesUI->shortcutTableView->setSelectionMode(QTableView::SingleSelection); + mPreferencesUI->report->setText(""); + mPreferencesUI->noCtrl->setEnabled(!UBShortcutManager::shortcutManager()->hasCtrlConflicts()); +} - for (int i = 1 ; i < UBSettings::settings()->colorPaletteSize ; i++) +void UBPreferencesController::resetClicked() +{ + if (mPreferencesUI->recordButton->isChecked()) { - UBColorPicker *picker = new UBColorPicker(darkBackgroundFrame); - picker->setObjectName(QString("penDarkBackgroundColor%1").arg(i)); - picker->setMinimumSize(QSize(32, 32)); - picker->setFrameShape(QFrame::StyledPanel); - picker->setFrameShadow(QFrame::Raised); + abortClicked(); + } - darkBackgroundLayout->addWidget(picker); + if (currentIndex.isValid()) + { + UBShortcutManager* sm = UBShortcutManager::shortcutManager(); - picker->setColors(darkBackgroundColors); - picker->setSelectedColorIndex(darkBackgroundColors.indexOf(darkBackgroundSelectedColors.at(i))); + sm->resetData(currentIndex); + applyShortcutFilter(mPreferencesUI->filter->text()); - darkBackgroundColorPickers.append(picker); + const QKeySequence keySequence{sm->data(currentIndex.siblingAtColumn(2)).toString()}; + mPreferencesUI->keySequence->setKeySequence(keySequence); + mPreferencesUI->mouseButton->setText(sm->data(currentIndex.siblingAtColumn(3)).toString()); + mPreferencesUI->stylusButton->setText(sm->data(currentIndex.siblingAtColumn(4)).toString()); + } - QObject::connect(picker, SIGNAL(colorSelected(const QColor&)), controller, SLOT(colorSelected(const QColor&))); + mPreferencesUI->report->setText(""); + mPreferencesUI->noCtrl->setEnabled(!UBShortcutManager::shortcutManager()->hasCtrlConflicts()); +} - } +UBBrushPropertiesFrame::UBBrushPropertiesFrame(QFrame* owner) +{ + setupUi(owner); } UBScreenListLineEdit::UBScreenListLineEdit(QWidget *parent) @@ -770,6 +881,10 @@ UBScreenListLineEdit::UBScreenListLineEdit(QWidget *parent) , mValidator(nullptr) { connect(this, &QLineEdit::textChanged, this, &UBScreenListLineEdit::onTextChanged); + UBApplication::displayManager->setScreenLabelParent(this); + + connect(UBApplication::displayManager, &UBDisplayManager::screenLabelPressed, this, &UBScreenListLineEdit::addScreen); + connect(this, &UBScreenListLineEdit::screenListChanged, UBApplication::displayManager, &UBDisplayManager::disableScreenLabels); } void UBScreenListLineEdit::setDefault() @@ -785,116 +900,63 @@ void UBScreenListLineEdit::loadScreenList(const QStringList &screenList) void UBScreenListLineEdit::focusInEvent(QFocusEvent *focusEvent) { QLineEdit::focusInEvent(focusEvent); - - if (mScreenLabels.empty()) + if (UBPlatformUtils::sessionType() != UBPlatformUtils::WAYLAND) { - QStringList screenList = UBStringUtils::trimmed(text().split(',')); - - QList screens = UBApplication::displayManager->availableScreens(); - QStringList availableScreenIndexes; - int screenIndex = 1; - QFont font; - font.setPointSize(48); - - for (QScreen* screen : screens) - { - QString index = QString::number(screenIndex); - availableScreenIndexes << index; - - QPushButton* button = new QPushButton(this); - button->setWindowFlag(Qt::FramelessWindowHint, true); - button->setWindowFlag(Qt::WindowStaysOnTopHint, true); - button->setWindowFlag(Qt::X11BypassWindowManagerHint, true); - button->setWindowFlag(Qt::Window, true); - button->setWindowFlag(Qt::WindowDoesNotAcceptFocus, true); - button->setAttribute(Qt::WA_ShowWithoutActivating, true); - button->setProperty("screenIndex", index); -#ifdef QT_DEBUG - button->setText(index + "(" + screen->name() + ")"); -#else - button->setText(index); -#endif - button->setFont(font); - button->move(screen->geometry().topLeft()); - button->setMinimumSize(300, 150); - button->setDisabled(screenList.contains(index)); - button->setCursor(Qt::PointingHandCursor); - button->show(); - - connect(button, &QPushButton::pressed, this, &UBScreenListLineEdit::addScreen); + UBApplication::displayManager->showScreenLabels(true); + } - mScreenLabels << button; - ++screenIndex; - } + if (!mValidator) + { + mValidator = new UBStringListValidator(this); + setValidator(mValidator); + } - if (!mValidator) - { - mValidator = new UBStringListValidator(this); - setValidator(mValidator); - } + mValidator->setValidationStringList(UBApplication::displayManager->availableScreenIndexes()); - mValidator->setValidationStringList(availableScreenIndexes); - } } void UBScreenListLineEdit::focusOutEvent(QFocusEvent *focusEvent) { QLineEdit::focusOutEvent(focusEvent); - qDeleteAll(mScreenLabels); - mScreenLabels.clear(); -} -void UBScreenListLineEdit::addScreen() -{ - QPushButton* button = dynamic_cast(sender()); - - if (button) + if (focusEvent->reason() != Qt::ActiveWindowFocusReason && + UBPlatformUtils::sessionType() != UBPlatformUtils::WAYLAND) { - QString list = text(); - mValidator->fixup(list); - QString screenIndex = button->property("screenIndex").toString(); - - if (list.isEmpty()) - { - setText(screenIndex); - } - else - { - setText(list + "," + screenIndex); - } - - button->setEnabled(false); + UBApplication::displayManager->showScreenLabels(false); } } -void UBScreenListLineEdit::onTextChanged(const QString &input) +void UBScreenListLineEdit::addScreen(const QString& screenIndex) { - const QStringList screenList = UBStringUtils::trimmed(input.split(',')); + qDebug() << "addScreen" << screenIndex; - for (QPushButton* button : std::as_const(mScreenLabels)) + QString list = text(); + mValidator->fixup(list); + + if (list.isEmpty()) { - button->setDisabled(screenList.contains(button->property("screenIndex").toString())); + setText(screenIndex); } - - if (input.isEmpty() || input.right(1) == ',') + else { - // create and attach a new QCompleter - QStringList model; - - for (QPushButton* button : std::as_const(mScreenLabels)) - { - if (button->isEnabled()) - { - model << input + button->property("screenIndex").toString(); - } - } + setText(list + "," + screenIndex); } +} + +void UBScreenListLineEdit::onTextChanged(const QString &input) +{ + const QStringList screenList = UBStringUtils::trimmed(input.split(',')); // user indication of acceptable input if (hasAcceptableInput()) { setStyleSheet(""); emit screenListChanged(screenList); + + QTimer::singleShot(100, this, [this](){ + // keep focus + activateWindow(); + }); } else { @@ -949,3 +1011,85 @@ void UBStringListValidator::setValidationStringList(const QStringList &list) { mList = list; } + +UBScreenLayoutPreview::UBScreenLayoutPreview(QWidget* parent) + : QWidget(parent) +{ + setMinimumSize(QSize(480, 260)); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + if (UBApplication::displayManager) + { + connect(UBApplication::displayManager, &UBDisplayManager::availableScreenCountChanged, + this, &UBScreenLayoutPreview::refreshScreens); + connect(UBApplication::displayManager, &UBDisplayManager::screenLayoutChanged, + this, &UBScreenLayoutPreview::refreshScreens); + } + + refreshScreens(); +} + +void UBScreenLayoutPreview::refreshScreens() +{ + mScreens = UBApplication::displayManager ? UBApplication::displayManager->availableScreens() : QList(); + update(); +} + +void UBScreenLayoutPreview::paintEvent(QPaintEvent* event) +{ + Q_UNUSED(event); + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing, true); + + if (mScreens.isEmpty()) + { + painter.drawText(rect(), Qt::AlignCenter, tr("No displays detected")); + return; + } + + const int margin = 10; + QRectF availableRect = QRectF(rect()).adjusted(margin, margin, -margin, -margin); + + QRectF bounding; + for (QScreen* screen : mScreens) + { + bounding = bounding.isNull() ? QRectF(screen->geometry()) : bounding.united(QRectF(screen->geometry())); + } + + if (bounding.width() <= 0 || bounding.height() <= 0) + { + painter.drawText(rect(), Qt::AlignCenter, tr("Invalid display layout")); + return; + } + + const qreal scale = std::min(availableRect.width() / bounding.width(), + availableRect.height() / bounding.height()); + + const QPointF offset = availableRect.topLeft() - bounding.topLeft() * scale; + + const QColor screenColor = palette().alternateBase().color(); + const QColor primaryColor = palette().mid().color(); + const QColor borderColor = palette().mid().color().darker(120); + const QFont font = painter.font(); + + int index = 1; + for (QScreen* screen : mScreens) + { + QRectF g(screen->geometry()); + QRectF mapped(g.topLeft() * scale + offset, g.size() * scale); + + QColor fill = (screen == QGuiApplication::primaryScreen()) ? primaryColor : screenColor; + painter.setBrush(fill); + painter.setPen(QPen(borderColor, 1.0)); + painter.drawRoundedRect(mapped, 6, 6); + + painter.setPen(palette().windowText().color()); + QFont f = font; + f.setBold(true); + painter.setFont(f); + painter.drawText(mapped, Qt::AlignCenter, QString::number(index)); + + ++index; + } +} diff --git a/src/core/UBPreferencesController.h b/src/core/UBPreferencesController.h index d89b4c181..0e8116578 100644 --- a/src/core/UBPreferencesController.h +++ b/src/core/UBPreferencesController.h @@ -70,6 +70,8 @@ class UBPreferencesController : public QObject UBPreferencesController(QWidget *parent); virtual ~UBPreferencesController(); + bool handleMouseEvent(QMouseEvent *event); + bool handleTabletEvent(QTabletEvent *event); public slots: @@ -87,6 +89,7 @@ class UBPreferencesController : public QObject UBColorPicker* mDarkBackgroundGridColorPicker; UBColorPicker* mLightBackgroundGridColorPicker; QString mScreenConfigurationPath; + QStringList mScreenList; protected slots: @@ -96,39 +99,40 @@ class UBPreferencesController : public QObject void darkBackgroundCrossOpacityValueChanged(int value); void lightBackgroundCrossOpacityValueChanged(int value); void widthSliderChanged(int value); - void opacitySliderChanged(int value); - void colorSelected(const QColor&); void setCrossColorOnDarkBackground(const QColor& color); void setCrossColorOnLightBackground(const QColor& color); void toolbarPositionChanged(bool checked); void toolbarOrientationVertical(bool checked); void toolbarOrientationHorizontal(bool checked); void systemOSKCheckBoxToggled(bool checked); + void actionSelected(const QModelIndex& index); + void recordingClicked(bool checked); + void abortClicked(); + void resetClicked(); private slots: void adjustScreensPreferences(); + void applyShortcutFilter(const QString& filter, int filterCol = -1); + private: static qreal sSliderRatio; static qreal sMinPenWidth; static qreal sMaxPenWidth; + QModelIndex currentIndex; }; class UBBrushPropertiesFrame : public Ui::brushProperties { - - public: - UBBrushPropertiesFrame(QFrame* owner, const QList& lightBackgroundColors,const QList& darkBackgroundColors, const QList& lightBackgroundSelectedColors,const QList& darkBackgroundSelectedColors, UBPreferencesController* controller); - - virtual ~UBBrushPropertiesFrame(){} - - QList lightBackgroundColorPickers; - QList darkBackgroundColorPickers; +public: + explicit UBBrushPropertiesFrame(QFrame* owner); + virtual ~UBBrushPropertiesFrame(){} }; // forward class UBStringListValidator; +class UBScreenLayoutPreview; class UBScreenListLineEdit : public QLineEdit { @@ -149,11 +153,10 @@ class UBScreenListLineEdit : public QLineEdit void screenListChanged(QStringList screenList); private slots: - void addScreen(); + void addScreen(const QString& screenIndex); void onTextChanged(const QString& input); private: - QList mScreenLabels; UBStringListValidator* mValidator; }; @@ -174,4 +177,22 @@ class UBStringListValidator : public QValidator QStringList mList; }; +class UBScreenLayoutPreview : public QWidget +{ + Q_OBJECT; + +public: + explicit UBScreenLayoutPreview(QWidget* parent = nullptr); + ~UBScreenLayoutPreview() override = default; + +public slots: + void refreshScreens(); + +protected: + void paintEvent(QPaintEvent* event) override; + +private: + QList mScreens; +}; + #endif /* UBPREFERENCESCONTROLLER_H_ */ diff --git a/src/core/UBSceneCache.cpp b/src/core/UBSceneCache.cpp index fb5896d3a..2d9407fbc 100644 --- a/src/core/UBSceneCache.cpp +++ b/src/core/UBSceneCache.cpp @@ -36,6 +36,7 @@ #include "core/UBSettings.h" #include "core/UBSetting.h" +#include "document/UBDocument.h" #include "document/UBDocumentProxy.h" #include "core/memcheck.h" @@ -52,49 +53,43 @@ UBSceneCache::~UBSceneCache() } -std::shared_ptr UBSceneCache::createScene(std::shared_ptr proxy, int pageIndex, bool useUndoRedoStack) +std::shared_ptr UBSceneCache::createScene(std::shared_ptr proxy, int pageId, bool useUndoRedoStack) { auto newScene = std::make_shared(proxy, useUndoRedoStack); - insert(proxy, pageIndex, newScene); + insert(proxy, pageId, newScene); return newScene; } -std::shared_ptr UBSceneCache::prepareLoading(std::shared_ptr proxy, int pageIndex) +std::shared_ptr UBSceneCache::prepareLoading(std::shared_ptr proxy, int pageId, std::optional xmlContent, bool cached) { - if (mSceneCache.contains({proxy, pageIndex})) + if (mSceneCache.contains({proxy, pageId})) { - return value(proxy, pageIndex); + return nullptr; } // no entry in cache; create a cache entry to load scene - qDebug() << "Preparing to load scene" << pageIndex; - auto cacheEntry = std::make_shared(proxy, pageIndex); + qDebug() << "Preparing to load scene" << pageId; + auto cacheEntry = std::make_shared(proxy, pageId, xmlContent); + + if (cached) + { + insertEntry({proxy, pageId}, cacheEntry); + } - insertEntry({proxy, pageIndex}, cacheEntry); cacheEntry->startLoading(); - return nullptr; + + // return the cache entry as opaque handle to keep the entry alive for uncached loading + return cacheEntry; } -void UBSceneCache::insert (std::shared_ptr proxy, int pageIndex, std::shared_ptr scene) +void UBSceneCache::insert (std::shared_ptr proxy, int pageId, std::shared_ptr scene) { - // remove all entries pointing to this scene - const auto keylist = mSceneCache.keys(); + removeSceneFromCache(scene); - for (const auto& key : keylist) - { - auto entry = mSceneCache.value(key); - - if (entry->isSceneAvailable() && entry->scene() == scene) - { - mSceneCache.remove(key); - mCachedKeyFIFO.removeAll(key); - } - } - - UBSceneCacheID key{proxy, pageIndex}; + UBSceneCacheID key{proxy, pageId}; insertEntry(key, std::make_shared(scene)); // restore view state @@ -105,15 +100,15 @@ void UBSceneCache::insert (std::shared_ptr proxy, int pageIndex } -bool UBSceneCache::contains(std::shared_ptr proxy, int pageIndex) const +bool UBSceneCache::contains(std::shared_ptr proxy, int pageId) const { - return mSceneCache.contains({proxy, pageIndex}); + return mSceneCache.contains({proxy, pageId}); } -std::shared_ptr UBSceneCache::value(std::shared_ptr proxy, int pageIndex) +std::shared_ptr UBSceneCache::value(std::shared_ptr proxy, int pageId) { - UBSceneCacheID key{proxy, pageIndex}; + UBSceneCacheID key{proxy, pageId}; if (mSceneCache.contains(key)) { @@ -131,9 +126,9 @@ std::shared_ptr UBSceneCache::value(std::shared_ptr proxy, int pageIndex) +void UBSceneCache::removeScene(std::shared_ptr proxy, int pageId) { - UBSceneCacheID key{proxy, pageIndex}; + UBSceneCacheID key{proxy, pageId}; if (!mSceneCache.contains(key)) { @@ -144,7 +139,7 @@ void UBSceneCache::removeScene(std::shared_ptr proxy, int pageI if (!entry->isSceneAvailable() || !entry->scene()->isActive()) { - int count = mSceneCache.remove(key); + mSceneCache.remove(key); mCachedKeyFIFO.removeAll(key); if (entry->isSceneAvailable()) @@ -157,121 +152,47 @@ void UBSceneCache::removeScene(std::shared_ptr proxy, int pageI void UBSceneCache::removeAllScenes(std::shared_ptr proxy) { - for(int i = 0 ; i < proxy->pageCount(); i++) - { - removeScene(proxy, i); - } -} - - -void UBSceneCache::moveScene(std::shared_ptr proxy, int sourceIndex, int targetIndex) -{ - UBSceneCacheID keySource(proxy, sourceIndex); + // get list of all page ids for proxy, avoid modifying the map here + auto lowerBound = mSceneCache.lowerBound({proxy, 0}); + auto upperBound = mSceneCache.upperBound({proxy, INT_MAX}); - std::shared_ptr entry; - bool hasEntry = mSceneCache.contains(keySource); + QList pageIds; - if (hasEntry) + for (auto iter = lowerBound; iter != upperBound; ++iter) { - entry = mSceneCache.value(keySource); - mCachedKeyFIFO.removeAll(keySource); + pageIds << iter.key().mPageId; } - if (sourceIndex < targetIndex) + // delete scenes by id + for(auto pageId : pageIds) { - for (int i = sourceIndex + 1; i <= targetIndex; i++) - { - internalMoveScene(proxy, i, i - 1); - } + removeScene(proxy, pageId); } - else - { - for (int i = sourceIndex - 1; i >= targetIndex; i--) - { - internalMoveScene(proxy, i, i + 1); - } - } - - UBSceneCacheID keyTarget(proxy, targetIndex); - - if (hasEntry) - { - insertEntry(keyTarget, entry); - mCachedKeyFIFO.enqueue(keyTarget); - } - else if (mSceneCache.contains(keyTarget)) - { - entry = mSceneCache.take(keyTarget); - mCachedKeyFIFO.removeAll(keyTarget); - } - } -void UBSceneCache::reassignDocProxy(std::shared_ptr newDocument, std::shared_ptr oldDocument) +void UBSceneCache::removeSceneFromCache(std::shared_ptr scene) { - if (!newDocument || !oldDocument) { - return; - } - if (newDocument->pageCount() != oldDocument->pageCount()) { - return; - } - if (!QFileInfo(oldDocument->persistencePath()).exists()) { + if (scene->isActive()) + { return; } - for (int i = 0; i < oldDocument->pageCount(); i++) { - - UBSceneCacheID sourceKey(oldDocument, i); - auto entry = mSceneCache.value(sourceKey); - - if (entry->isSceneAvailable()) - { - entry->scene()->setDocument(newDocument); - } - mCachedKeyFIFO.removeAll(sourceKey); - int count = mSceneCache.remove(sourceKey); - - insertEntry({newDocument, i}, entry); - } -} - - -void UBSceneCache::shiftUpScenes(std::shared_ptr proxy, int startIncIndex, int endIncIndex) -{ - for(int i = endIncIndex; i >= startIncIndex; i--) - { - UBApplication::showMessage(QObject::tr("Moving cached scenes (%1/%2)").arg(i).arg(endIncIndex)); - internalMoveScene(proxy, i, i + 1); - } -} - - -void UBSceneCache::internalMoveScene(std::shared_ptr proxy, int sourceIndex, int targetIndex) -{ - UBSceneCacheID sourceKey(proxy, sourceIndex); + // remove all entries pointing to this scene + const auto keylist = mSceneCache.keys(); - if (mSceneCache.contains(sourceKey)) + for (const auto& key : keylist) { - auto scene = mSceneCache.take(sourceKey); - mCachedKeyFIFO.removeAll(sourceKey); - - UBSceneCacheID targetKey(proxy, targetIndex); - mSceneCache.insert(targetKey, scene); - mCachedKeyFIFO.enqueue(targetKey); + auto entry = mSceneCache.value(key); - } - else - { - UBSceneCacheID targetKey(proxy, targetIndex); - if (mSceneCache.contains(targetKey)) + if (entry->isSceneAvailable() && entry->scene() == scene) { - mSceneCache.take(targetKey); - - mCachedKeyFIFO.removeAll(targetKey); + mSceneCache.remove(key); + mCachedKeyFIFO.removeAll(key); } } } + void UBSceneCache::insertEntry(UBSceneCacheID key, std::shared_ptr entry) { mSceneCache.insert(key, entry); @@ -291,18 +212,18 @@ void UBSceneCache::insertEntry(UBSceneCacheID key, std::shared_ptrisSceneAvailable() && !entry->scene()->isActive()) || !entry->isSceneAvailable()) { - qDebug() << "removing page" << key.pageIndex << "of" << key.documentProxy->documentFolderName(); - removeScene(key.documentProxy, key.pageIndex); + qDebug() << "removing page" << key.mPageId << "of" << key.mDocumentProxy->documentFolderName(); + removeScene(key.mDocumentProxy, key.mPageId); entry = nullptr; - qDebug() << "removed page" << key.pageIndex << "of" << key.documentProxy->documentFolderName(); + qDebug() << "removed page" << key.mPageId << "of" << key.mDocumentProxy->documentFolderName(); break; } } } -UBSceneCache::SceneCacheEntry::SceneCacheEntry(std::shared_ptr proxy, int pageIndex) +UBSceneCache::SceneCacheEntry::SceneCacheEntry(std::shared_ptr proxy, int pageId, std::optional xmlContent) { - mContext = UBSvgSubsetAdaptor::prepareLoadingScene(proxy, pageIndex); + mContext = UBSvgSubsetAdaptor::prepareLoadingScene(proxy, pageId, xmlContent); } UBSceneCache::SceneCacheEntry::SceneCacheEntry(std::shared_ptr scene) @@ -326,6 +247,7 @@ void UBSceneCache::SceneCacheEntry::startLoading() { mTimer->stop(); delete mTimer; + mTimer = nullptr; return; } @@ -336,10 +258,13 @@ void UBSceneCache::SceneCacheEntry::startLoading() if (mContext->isFinished()) { mScene = mContext->scene(); + auto document = UBDocument::getDocument(mContext->proxy()); mContext = nullptr; mTimer->stop(); delete mTimer; mTimer = nullptr; + + document->sceneLoaded(mScene.get(), shared_from_this()); } } }); @@ -370,6 +295,8 @@ std::shared_ptr UBSceneCache::SceneCacheEntry::scene() } mScene = mContext->scene(); + auto document = UBDocument::getDocument(mContext->proxy()); + document->sceneLoaded(mScene.get(), shared_from_this()); mContext = nullptr; } diff --git a/src/core/UBSceneCache.h b/src/core/UBSceneCache.h index 52e38adec..9e4342a1c 100644 --- a/src/core/UBSceneCache.h +++ b/src/core/UBSceneCache.h @@ -32,8 +32,6 @@ #include -#include - #include "adaptors/UBSvgSubsetAdaptor.h" #include "domain/UBGraphicsScene.h" @@ -46,31 +44,33 @@ class UBSceneCacheID public: UBSceneCacheID() - : documentProxy(0) - , pageIndex(-1) + : mDocumentProxy(0) + , mPageId(-1) { // NOOP } - UBSceneCacheID(std::shared_ptr pDocumentProxy, int pPageIndex) + UBSceneCacheID(std::shared_ptr pDocumentProxy, int pageId) { - documentProxy = pDocumentProxy; - pageIndex = pPageIndex; + mDocumentProxy = pDocumentProxy; + mPageId = pageId; } - std::shared_ptr documentProxy; - int pageIndex; + std::shared_ptr mDocumentProxy; + int mPageId; }; -inline bool operator==(const UBSceneCacheID &id1, const UBSceneCacheID &id2) +inline bool operator<(const UBSceneCacheID &id1, const UBSceneCacheID &id2) { - return id1.documentProxy == id2.documentProxy - && id1.pageIndex == id2.pageIndex; + return id1.mDocumentProxy < id2.mDocumentProxy || + (id1.mDocumentProxy == id2.mDocumentProxy + && id1.mPageId < id2.mPageId); } -inline uint qHash(const UBSceneCacheID &id) +inline bool operator==(const UBSceneCacheID &id1, const UBSceneCacheID &id2) { - return qHash(id.pageIndex); + return id1.mDocumentProxy == id2.mDocumentProxy + && id1.mPageId == id2.mPageId; } @@ -80,32 +80,28 @@ class UBSceneCache UBSceneCache(); virtual ~UBSceneCache(); - std::shared_ptr createScene(std::shared_ptr proxy, int pageIndex, bool useUndoRedoStack); + std::shared_ptr createScene(std::shared_ptr proxy, int pageId, bool useUndoRedoStack); - std::shared_ptr prepareLoading(std::shared_ptr proxy, int pageIndex); + std::shared_ptr prepareLoading(std::shared_ptr proxy, int pageId, std::optional xmlContent = {}, bool cached = true); - void insert (std::shared_ptr proxy, int pageIndex, std::shared_ptr scene); + void insert (std::shared_ptr proxy, int pageId, std::shared_ptr scene); - bool contains(std::shared_ptr proxy, int pageIndex) const; + bool contains(std::shared_ptr proxy, int pageId) const; - std::shared_ptr value(std::shared_ptr proxy, int pageIndex); + std::shared_ptr value(std::shared_ptr proxy, int pageId); - void removeScene(std::shared_ptr proxy, int pageIndex); + void removeScene(std::shared_ptr proxy, int pageId); void removeAllScenes(std::shared_ptr proxy); - void moveScene(std::shared_ptr proxy, int sourceIndex, int targetIndex); - - void reassignDocProxy(std::shared_ptr newDocument, std::shared_ptr oldDocument); - - void shiftUpScenes(std::shared_ptr proxy, int startIncIndex, int endIncIndex); + void removeSceneFromCache(std::shared_ptr scene); private: - class SceneCacheEntry + class SceneCacheEntry : public std::enable_shared_from_this { public: - SceneCacheEntry(std::shared_ptr proxy, int pageIndex); + SceneCacheEntry(std::shared_ptr proxy, int pageId, std::optional xmlContent = {}); SceneCacheEntry(std::shared_ptr scene); ~SceneCacheEntry(); void startLoading(); @@ -118,18 +114,13 @@ class UBSceneCache QTimer* mTimer = nullptr; }; -// typedef QFuture> FutureScene; -// typedef std::variant, FutureScene> CacheEntry; - - void internalMoveScene(std::shared_ptr proxy, int sourceIndex, int targetIndex); - void insertEntry(UBSceneCacheID key, std::shared_ptr entry); - QHash> mSceneCache; + QMap> mSceneCache; QQueue mCachedKeyFIFO; - QHash mViewStates; + QMap mViewStates; }; diff --git a/src/core/UBSetting.cpp b/src/core/UBSetting.cpp index 78f08052d..7ba14a738 100644 --- a/src/core/UBSetting.cpp +++ b/src/core/UBSetting.cpp @@ -156,6 +156,29 @@ QList UBColorListSetting::colors() const return mColors; } +QList UBColorListSetting::defaultColors() const +{ + QList colors; + + for (const auto& serializedColor : defaultValue().toStringList()) + { +#if (QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)) + QColor color = QColor::fromString(serializedColor); +#else + QColor color; + color.setNamedColor(serializedColor); +#endif + if (mAlpha >= 0) + { + color.setAlphaF(mAlpha); + } + + colors.append(color); + } + + return colors; +} + void UBColorListSetting::setColor(int pIndex, const QColor& color) { QStringList list = get().toStringList(); @@ -171,6 +194,28 @@ void UBColorListSetting::setColor(int pIndex, const QColor& color) set(list); } +void UBColorListSetting::setColors(const QList& colors) +{ + mColors.clear(); + + QStringList serialized; + serialized.reserve(colors.size()); + + for (const QColor& inputColor : colors) + { + QColor color = inputColor; + if (mAlpha >= 0) + { + color.setAlphaF(mAlpha); + } + + serialized << color.name(QColor::HexArgb); + mColors << color; + } + + set(serialized); +} + void UBColorListSetting::setAlpha(qreal pAlpha) { mAlpha = pAlpha; diff --git a/src/core/UBSetting.h b/src/core/UBSetting.h index 94f135029..2618da9e9 100644 --- a/src/core/UBSetting.h +++ b/src/core/UBSetting.h @@ -104,9 +104,13 @@ class UBColorListSetting : public UBSetting virtual QVariant reset(); QList colors() const; + QList defaultColors() const; void setColor(int pIndex, const QColor& color); + // Replace the entire color list and persist it. + void setColors(const QList& colors); + void setAlpha(qreal pAlpha); protected: diff --git a/src/core/UBSettings.cpp b/src/core/UBSettings.cpp index 645063e53..0d6cd120c 100644 --- a/src/core/UBSettings.cpp +++ b/src/core/UBSettings.cpp @@ -50,8 +50,10 @@ int UBSettings::crossSize = 24; int UBSettings::defaultCrossSize = 24; int UBSettings::minCrossSize = 12; int UBSettings::maxCrossSize = 96; //TODO: user-settable? -bool UBSettings::intermediateLines = false; -int UBSettings::colorPaletteSize = 5; +const int UBSettings::minColorPaletteSize = 5; +const int UBSettings::maxColorPaletteSize = 10; +const int UBSettings::defaultColorPaletteSize = 8; +int UBSettings::colorPaletteSize = UBSettings::defaultColorPaletteSize; int UBSettings::objectFrameWidth = 20; int UBSettings::boardMargin = 10; @@ -74,7 +76,7 @@ const int UBSettings::sDefaultFontPixelSize = 36; const char *UBSettings::sDefaultFontFamily = "Arial"; const char *UBSettings::sDefaultFontStyleName = "Regular"; -QString UBSettings::currentFileVersion = "4.8.0"; +QString UBSettings::currentFileVersion = "4.9.0"; QBrush UBSettings::eraserBrushDarkBackground = QBrush(QColor(127, 127, 127, 80)); QBrush UBSettings::eraserBrushLightBackground = QBrush(QColor(127, 127, 127, 80)); @@ -98,9 +100,11 @@ QColor UBSettings::documentSizeMarkColorDarkBackground = QColor(44, 44, 44, 200) QColor UBSettings::documentSizeMarkColorLightBackground = QColor(241, 241, 241); QColor UBSettings::paletteColor = QColor(127, 127, 127, 127); -QColor UBSettings::opaquePaletteColor = QColor(66, 66, 66, 200); +QColor UBSettings::opaquePaletteColor = QColor(66, 66, 66, 255); -QColor UBSettings::documentViewLightColor = QColor(241, 241, 241); +QColor UBSettings::documentThumbnailsViewColor = QColor(214, 222, 235); +QColor UBSettings::boardThumbnailsViewColor = QColor(237, 242, 247); +QColor UBSettings::pageNavigationWidgetColor = QColor(Qt::white); QPointer UBSettings::sAppSettings = 0; @@ -137,6 +141,30 @@ int UBSettings::objectInControlViewMargin = 100; QString UBSettings::appPingMessage = "__uniboard_ping"; +QList UBSettings::normalizedColors(const QList& current, + const QList& defaults, + int targetSize, + bool& changed) +{ + QList result = current; + + if (result.size() < targetSize) + { + for (int i = result.size(); i < targetSize; ++i) + { + result.append(defaults.value(i % defaults.size(), QColor(Qt::black))); + } + changed = true; + } + else if (result.size() > targetSize) + { + result = result.mid(0, targetSize); + changed = true; + } + + return result; +} + UBSettings* UBSettings::settings() { if (!sSingleton) { @@ -247,6 +275,7 @@ void UBSettings::init() appHideCheckForSoftwareUpdate = new UBSetting(this, "App", "HideCheckForSoftwareUpdate", false); appToolBarOrientationVertical = new UBSetting(this, "App", "ToolBarOrientationVertical", false); appPreferredLanguage = new UBSetting(this,"App","PreferredLanguage", ""); + appThemeMode = new UBSetting(this, "App", "ThemeMode", 0); // removed in version 1.7.0 mUserSettings->remove("App/HideSwapDisplayScreens"); @@ -302,6 +331,13 @@ void UBSettings::init() boardCrossColorDarkBackground = new UBSetting(this, "Board", "CrossColorDarkBackground", "#C8C0C0C0"); boardCrossColorLightBackground = new UBSetting(this, "Board", "CrossColorLightBackground", "#A5E1FF"); + boardColorPaletteSize = new UBSetting(this, "Board", "ColorPaletteSize", defaultColorPaletteSize); + colorPaletteSize = qBound(minColorPaletteSize, boardColorPaletteSize->get().toInt(), maxColorPaletteSize); + if (colorPaletteSize != boardColorPaletteSize->get().toInt()) + { + boardColorPaletteSize->set(colorPaletteSize); + } + QStringList gridLightBackgroundColors; gridLightBackgroundColors << "#000000" << "#FF0000" << "#004080" << "#008000" << "#FFDD00" << "#C87400" << "#800040" << "#008080" << "#A5E1FF"; boardGridLightBackgroundColors = new UBColorListSetting(this, "Board", "GridLightBackgroundColors", gridLightBackgroundColors, -1.0); @@ -315,17 +351,17 @@ void UBSettings::init() boardPenLightBackgroundColors = new UBColorListSetting(this, "Board", "PenLightBackgroundColors", penLightBackgroundColors, 1.0); QStringList penDarkBackgroundColors; - penDarkBackgroundColors << "#FFFFFF" << "#FF3400" <<"#66C0FF" << "#81FF5C" << "#FFFF00" << "#B68360" << "#FF497E" << "#8D69FF" << "#000000"; + penDarkBackgroundColors << "#FFFFFF" << "#FF3400" <<"#66C0FF" << "#81FF5C" << "#FFFF00" << "#B68360" << "#FF497E" << "#8D69FF" << "#C8C0C0" << "#000000"; boardPenDarkBackgroundColors = new UBColorListSetting(this, "Board", "PenDarkBackgroundColors", penDarkBackgroundColors, 1.0); boardMarkerAlpha = new UBSetting(this, "Board", "MarkerAlpha", 0.5); QStringList markerLightBackgroundColors; - markerLightBackgroundColors << "#E3FF00" << "#FF0000" <<"#004080" << "#008000" << "#C87400" << "#800040" << "#008080" << "#000000"; + markerLightBackgroundColors << "#E3FF00" << "#FF0000" <<"#004080" << "#008000" << "#C87400" << "#800040" << "#008080" << "#000000" << "#FFA500" << "#8000FF"; boardMarkerLightBackgroundColors = new UBColorListSetting(this, "Board", "MarkerLightBackgroundColors", markerLightBackgroundColors, boardMarkerAlpha->get().toDouble()); QStringList markerDarkBackgroundColors; - markerDarkBackgroundColors << "#FFFF00" << "#FF4400" <<"#66C0FF" << "#81FF5C" << "#B68360" << "#FF497E" << "#8D69FF" << "#FFFFFF"; + markerDarkBackgroundColors << "#FFFF00" << "#FF4400" <<"#66C0FF" << "#81FF5C" << "#B68360" << "#FF497E" << "#8D69FF" << "#FFFFFF" << "#00FFFF" << "#FFD700"; boardMarkerDarkBackgroundColors = new UBColorListSetting(this, "Board", "MarkerDarkBackgroundColors", markerDarkBackgroundColors, boardMarkerAlpha->get().toDouble()); QStringList penLightBackgroundSelectedColors; @@ -334,7 +370,7 @@ void UBSettings::init() QStringList markerLightBackgroundSelectedColors; QStringList markerDarkBackgroundSelectedColors; - for (int i = 0; i < colorPaletteSize; i++) + for (int i = 0; i < maxColorPaletteSize; i++) { penLightBackgroundSelectedColors << penLightBackgroundColors[i]; penDarkBackgroundSelectedColors << penDarkBackgroundColors[i]; @@ -642,20 +678,25 @@ QColor UBSettings::currentPenColor() QColor UBSettings::penColor(bool onDarkBackground) { QList colors = penColors(onDarkBackground); - return colors.at(penColorIndex()); + int index = qBound(0, penColorIndex(), colors.size() - 1); + return colors.value(index, colors.isEmpty() ? QColor(Qt::black) : colors.first()); } QList UBSettings::penColors(bool onDarkBackground) { - if (onDarkBackground) - { - return boardPenDarkBackgroundSelectedColors->colors(); - } - else + UBColorListSetting* selected = onDarkBackground ? boardPenDarkBackgroundSelectedColors : boardPenLightBackgroundSelectedColors; + UBColorListSetting* defaultsSetting = onDarkBackground ? boardPenDarkBackgroundColors : boardPenLightBackgroundColors; + + bool changed = false; + QList normalized = normalizedColors(selected->colors(), defaultsSetting->colors(), maxColorPaletteSize, changed); + + if (changed) { - return boardPenLightBackgroundSelectedColors->colors(); + selected->setColors(normalized); } + + return normalized.mid(0, colorPaletteSize); } @@ -724,20 +765,79 @@ QColor UBSettings::currentMarkerColor() QColor UBSettings::markerColor(bool onDarkBackground) { QList colors = markerColors(onDarkBackground); - return colors.at(markerColorIndex()); + int index = qBound(0, markerColorIndex(), colors.size() - 1); + return colors.value(index, colors.isEmpty() ? QColor(Qt::black) : colors.first()); } QList UBSettings::markerColors(bool onDarkBackground) { - if (onDarkBackground) + UBColorListSetting* selected = onDarkBackground ? boardMarkerDarkBackgroundSelectedColors : boardMarkerLightBackgroundSelectedColors; + UBColorListSetting* defaultsSetting = onDarkBackground ? boardMarkerDarkBackgroundColors : boardMarkerLightBackgroundColors; + + bool changed = false; + QList normalized = normalizedColors(selected->colors(), defaultsSetting->colors(), maxColorPaletteSize, changed); + + if (changed) { - return boardMarkerDarkBackgroundSelectedColors->colors(); + selected->setColors(normalized); } - else + + return normalized.mid(0, colorPaletteSize); +} + + +QList UBSettings::defaultPenColors(bool onDarkBackground) const +{ + UBColorListSetting* setting = onDarkBackground ? boardPenDarkBackgroundColors : boardPenLightBackgroundColors; + return setting->defaultColors(); +} + + +QList UBSettings::defaultMarkerColors(bool onDarkBackground) const +{ + UBColorListSetting* setting = onDarkBackground ? boardMarkerDarkBackgroundColors : boardMarkerLightBackgroundColors; + return setting->defaultColors(); +} + + +void UBSettings::setColorPaletteSize(int size) +{ + int clamped = qBound(minColorPaletteSize, size, maxColorPaletteSize); + + if (clamped == colorPaletteSize) + return; + + colorPaletteSize = clamped; + boardColorPaletteSize->set(colorPaletteSize); + + auto normalizeSelected = [this](UBColorListSetting* selected, UBColorListSetting* defaultsSetting) { - return boardMarkerLightBackgroundSelectedColors->colors(); + bool changed = false; + QList normalized = normalizedColors(selected->colors(), defaultsSetting->colors(), maxColorPaletteSize, changed); + if (changed) + { + selected->setColors(normalized); + } + }; + + normalizeSelected(boardPenLightBackgroundSelectedColors, boardPenLightBackgroundColors); + normalizeSelected(boardPenDarkBackgroundSelectedColors, boardPenDarkBackgroundColors); + normalizeSelected(boardMarkerLightBackgroundSelectedColors, boardMarkerLightBackgroundColors); + normalizeSelected(boardMarkerDarkBackgroundSelectedColors, boardMarkerDarkBackgroundColors); + + int maxIndex = qMax(0, colorPaletteSize - 1); + if (penColorIndex() > maxIndex) + { + setPenColorIndex(maxIndex); } + + if (markerColorIndex() > maxIndex) + { + setMarkerColorIndex(maxIndex); + } + + emit colorContextChanged(); } //----------------------------------------// @@ -814,21 +914,10 @@ bool UBSettings::isDarkBackground() } -UBPageBackground UBSettings::pageBackground() +QUuid UBSettings::pageBackgroundUuid() { - QString val = value("Board/PageBackground", 0).toString(); - - if (val == "crossed") - return UBPageBackground::crossed; - else if (val == "ruled") - return UBPageBackground::ruled; - else - return UBPageBackground::plain; -} - -bool UBSettings::isSeyesRuledBackground() -{ - return value("Board/SeyesRuledBackground", false).toBool(); + QUuid uuid = value("Board/PageBackground", 0).toUuid(); + return uuid; } void UBSettings::setDarkBackground(bool isDarkBackground) @@ -838,23 +927,9 @@ void UBSettings::setDarkBackground(bool isDarkBackground) } -void UBSettings::setPageBackground(UBPageBackground background) -{ - QString val; - - if (background == UBPageBackground::crossed) - val = "crossed"; - else if (background == UBPageBackground::ruled) - val = "ruled"; - else - val = "plain"; - - setValue("Board/PageBackground", val); -} - -void UBSettings::setSeyesRuledBackground(bool isSeyesRuledBackground) +void UBSettings::setPageBackgroundUuid(const QUuid& background) { - setValue("Board/SeyesRuledBackground", isSeyesRuledBackground); + setValue("Board/PageBackground", background.toString()); } void UBSettings::setPenPressureSensitive(bool sensitive) @@ -1164,16 +1239,6 @@ QString UBSettings::applicationImageLibraryDirectory() } } -QString UBSettings::userAnimationDirectory() -{ - static QString animationDirectory = ""; - if(animationDirectory.isEmpty()){ - animationDirectory = userDataDirectory() + "/animationUserDirectory"; - checkDirectory(animationDirectory); - } - return animationDirectory; -} - QString UBSettings::userInteractiveDirectory() { static QString interactiveDirectory = ""; @@ -1250,20 +1315,6 @@ QString UBSettings::applicationVideosLibraryDirectory() } } -QString UBSettings::applicationAnimationsLibraryDirectory() -{ - QString defaultRelativePath = QString("./library/animations"); - - QString configPath = value("Library/AnimationsDirectory", QVariant(defaultRelativePath)).toString(); - - if (configPath.startsWith(".")) { - return UBPlatformUtils::applicationResourcesDirectory() + configPath.right(configPath.size() - 1); - } - else { - return configPath; - } -} - QString UBSettings::applicationStartupHintsDirectory() { QString defaultRelativePath = QString("./startupHints"); @@ -1502,7 +1553,7 @@ void UBSettings::checkNewSettings() << boardMarkerLightBackgroundSelectedColors; foreach (UBColorListSetting* setting, colorSettings) { - if (setting->colors().size() < 5) + if (setting->colors().size() < minColorPaletteSize) setting->reset(); } @@ -1532,6 +1583,15 @@ void UBSettings::checkNewSettings() setting->set(currentColors); } + int storedPaletteSize = boardColorPaletteSize->get().toInt(); + if (storedPaletteSize < minColorPaletteSize || storedPaletteSize > maxColorPaletteSize) + { + storedPaletteSize = defaultColorPaletteSize; + boardColorPaletteSize->set(storedPaletteSize); + } + + setColorPaletteSize(storedPaletteSize); + // A typo was corrected in version 1.3 removeSetting("Board/useSystemOnScreenKeybard"); diff --git a/src/core/UBSettings.h b/src/core/UBSettings.h index 53a608833..f87e8e7cf 100644 --- a/src/core/UBSettings.h +++ b/src/core/UBSettings.h @@ -52,6 +52,10 @@ class UBSettings : public QObject UBSettings(QObject *parent = 0); virtual ~UBSettings(); void cleanNonPersistentSettings(); + static QList normalizedColors(const QList& current, + const QList& defaults, + int targetSize, + bool& changed); public: @@ -69,6 +73,7 @@ class UBSettings : public QObject QColor currentPenColor(); QColor penColor(bool onDarkBackground); QList penColors(bool onDarkBackground); + QList defaultPenColors(bool onDarkBackground) const; // Marker related int markerWidthIndex(); @@ -77,6 +82,12 @@ class UBSettings : public QObject QColor currentMarkerColor(); QColor markerColor(bool onDarkBackground); QList markerColors(bool onDarkBackground); + QList defaultMarkerColors(bool onDarkBackground) const; + + // Palette size + void setColorPaletteSize(int size); + int colorPaletteMin() const { return minColorPaletteSize; } + int colorPaletteMax() const { return maxColorPaletteSize; } // Eraser related int eraserWidthIndex(); @@ -87,11 +98,9 @@ class UBSettings : public QObject // Background related bool isDarkBackground(); - UBPageBackground pageBackground(); - bool isSeyesRuledBackground(); + QUuid pageBackgroundUuid(); void setDarkBackground(bool isDarkBackground); - void setPageBackground(UBPageBackground background); - void setSeyesRuledBackground(bool isSeyesRuledBackground); + void setPageBackgroundUuid(const QUuid& background); // Stylus palette related bool isStylusPaletteVisible(); @@ -140,7 +149,6 @@ class UBSettings : public QObject static QString userVideoDirectory(); static QString userAudioDirectory(); static QString userSearchDirectory(); - static QString userAnimationDirectory(); static QString userInteractiveDirectory(); static QString userInteractiveFavoritesDirectory(); static QString userPodcastRecordingDirectory(); @@ -156,7 +164,6 @@ class UBSettings : public QObject QString applicationCustomFontDirectory(); QString applicationAudiosLibraryDirectory(); QString applicationVideosLibraryDirectory(); - QString applicationAnimationsLibraryDirectory(); QString applicationStartupHintsDirectory(); QNetworkProxy* httpProxy(); @@ -167,7 +174,9 @@ class UBSettings : public QObject static QColor paletteColor; static QColor opaquePaletteColor; - static QColor documentViewLightColor; + static QColor documentThumbnailsViewColor; + static QColor boardThumbnailsViewColor; + static QColor pageNavigationWidgetColor; static QBrush eraserBrushDarkBackground; static QBrush eraserBrushLightBackground; @@ -195,9 +204,11 @@ class UBSettings : public QObject static int defaultCrossSize; static int minCrossSize; static int maxCrossSize; - static bool intermediateLines; static int colorPaletteSize; + static const int minColorPaletteSize; + static const int maxColorPaletteSize; + static const int defaultColorPaletteSize; static int objectFrameWidth; static QString documentGroupName; @@ -263,6 +274,7 @@ class UBSettings : public QObject UBSetting* appHideCheckForSoftwareUpdate; UBSetting* appToolBarOrientationVertical; UBSetting* appPreferredLanguage; + UBSetting* appThemeMode; UBSetting* appRunInWindow; UBSetting* appIsInSoftwareUpdateProcess; @@ -303,6 +315,7 @@ class UBSettings : public QObject UBSetting* boardCrossColorDarkBackground; UBSetting* boardCrossColorLightBackground; + UBSetting* boardColorPaletteSize; UBColorListSetting* boardGridLightBackgroundColors; UBColorListSetting* boardGridDarkBackgroundColors; diff --git a/src/core/UBShortcutManager.cpp b/src/core/UBShortcutManager.cpp index dfe8c8239..0f54fd7dd 100644 --- a/src/core/UBShortcutManager.cpp +++ b/src/core/UBShortcutManager.cpp @@ -39,11 +39,26 @@ static const char* descriptionProperty("description"); static const char* mouseButtonProperty("mouseButton"); static const char* tabletButtonProperty("tabletButton"); +// helper function to flip keyboard modifier depending on Qt version +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + QKeyCombination flipModifier(const QKeyCombination& keyCombination, Qt::KeyboardModifier modifier) + { + Qt::KeyboardModifiers mod = keyCombination.keyboardModifiers(); + mod.setFlag(modifier, !mod.testFlag(modifier)); + return QKeyCombination(mod, keyCombination.key()); + } +#else + int flipModifier(int keyCombination, Qt::KeyboardModifier modifier) + { + return keyCombination ^ modifier; + } +#endif + UBShortcutManager* UBShortcutManager::sShortcutManager = nullptr; UBShortcutManager::UBShortcutManager() : mIgnoreCtrl(false) { - actionsOfGroup(QObject::tr("Common")); + actionsOfGroup(tr("Common")); } UBShortcutManager *UBShortcutManager::shortcutManager() @@ -133,6 +148,11 @@ void UBShortcutManager::addMainActions(UBMainWindow *mainWindow) mainWindow->actionQuit }, mainWindow); + // Don't allow to modify these shortcuts + mainWindow->actionCut->setProperty("builtIn", true); + mainWindow->actionCopy->setProperty("builtIn", true); + mainWindow->actionPaste->setProperty("builtIn", true); + addActions(tr("Board"), { mainWindow->actionUndo, mainWindow->actionRedo, @@ -183,19 +203,18 @@ void UBShortcutManager::addMainActions(UBMainWindow *mainWindow) mainWindow->actionColor1, mainWindow->actionColor2, mainWindow->actionColor3, - mainWindow->actionColor4 + mainWindow->actionColor4, + mainWindow->actionColor5, + mainWindow->actionColor6, + mainWindow->actionColor7, + mainWindow->actionColor8, + mainWindow->actionColor9 }, mainWindow); addActions(tr("Background"), { mainWindow->actionBackgrounds, - mainWindow->actionPlainLightBackground, - mainWindow->actionCrossedLightBackground, - mainWindow->actionRuledLightBackground, - mainWindow->actionPlainDarkBackground, - mainWindow->actionCrossedDarkBackground, - mainWindow->actionRuledDarkBackground, mainWindow->actionDefaultGridSize, - mainWindow->actionDrawIntermediateGridLines + mainWindow->actionLightDarkMode }, mainWindow); addActions(tr("Podcast"), { @@ -574,7 +593,9 @@ bool UBShortcutManager::checkData(const QModelIndex &index, const QVariant &valu for (int row = 0; row < rowCount(); ++row) { - if (data(index.siblingAtRow(row)).toString().split(", ").contains(value.toString())) + if (row != index.row() && + !value.toString().isEmpty() && + data(index.siblingAtRow(row)).toString().split(", ").contains(value.toString())) { // duplicate value return false; @@ -608,7 +629,7 @@ bool UBShortcutManager::hasCtrlConflicts(const QKeySequence &additionalShortcut) else { shortcuts << action->shortcut(); - ctrlShortcuts << QKeySequence(action->shortcut()[0] ^ Qt::CTRL); + ctrlShortcuts << QKeySequence(flipModifier(action->shortcut()[0], Qt::ControlModifier)); } } @@ -616,7 +637,7 @@ bool UBShortcutManager::hasCtrlConflicts(const QKeySequence &additionalShortcut) if (!additionalShortcut.isEmpty()) { shortcuts << additionalShortcut; - ctrlShortcuts << QKeySequence(additionalShortcut[0] ^ Qt::CTRL); + ctrlShortcuts << QKeySequence(flipModifier(additionalShortcut[0], Qt::ControlModifier)); } return shortcuts.intersects(ctrlShortcuts); @@ -688,8 +709,14 @@ void UBShortcutManager::ignoreCtrl(bool ignore) { QList shortcuts = action->shortcuts(); - if (ignore && !shortcuts.empty() && shortcuts[0][0] & Qt::CTRL) { - QKeySequence noCtrl(shortcuts[0][0] ^ Qt::CTRL); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + bool hasCtrl = !shortcuts.empty() && shortcuts[0][0].keyboardModifiers().testFlag(Qt::ControlModifier); +#else + bool hasCtrl = !shortcuts.empty() && shortcuts[0][0] & Qt::ControlModifier; +#endif + + if (ignore && hasCtrl) { + QKeySequence noCtrl(flipModifier(shortcuts[0][0], Qt::ControlModifier)); shortcuts << noCtrl; action->setShortcuts(shortcuts); } @@ -829,7 +856,11 @@ bool UBActionGroupHistory::keyReleased(QKeyEvent *event) if (keySequence.count() > 0) { +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + int actionKey = action->shortcut()[0].key(); +#else int actionKey = action->shortcut()[0] & ~Qt::KeyboardModifierMask; +#endif if (key == actionKey) { diff --git a/src/core/UBThemeManager.cpp b/src/core/UBThemeManager.cpp new file mode 100644 index 000000000..bdcad20e7 --- /dev/null +++ b/src/core/UBThemeManager.cpp @@ -0,0 +1,397 @@ +/* + * Copyright (C) 2015-2025 Département de l'Instruction Publique (DIP-SEM) + * + * Copyright (C) 2013 Open Education Foundation + * + * 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 + * 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, + * 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 OpenBoard. If not, see . + */ + +#include "UBThemeManager.h" +#include "UBApplication.h" +#include "UBSettings.h" +#include "UBApplicationController.h" +#include "frameworks/UBFileSystemUtils.h" +#include "frameworks/UBPlatformUtils.h" +#include "board/UBBoardController.h" +#include "board/UBBoardPaletteManager.h" +#include "gui/UBMainWindow.h" +#include "gui/UBBoardThumbnailsView.h" +#include "gui/UBFloatingPalette.h" +#include "gui/UBPageNavigationWidget.h" +#include "gui/UBStartupHintsPalette.h" +#include "gui/UBDocumentThumbnailsView.h" +#include "desktop/UBDesktopAnnotationController.h" +#include "desktop/UBDesktopPalette.h" + +#include +#include +#include +#include +#include + +#ifdef Q_OS_WIN +#include +#endif + +UBThemeManager* UBThemeManager::sInstance = nullptr; + +UBThemeManager::UBThemeManager() + : QObject(nullptr) + , mIsDarkMode(false) +{ +} + +UBThemeManager::~UBThemeManager() +{ +} + +UBThemeManager* UBThemeManager::instance() +{ + if (!sInstance) + { + sInstance = new UBThemeManager(); + } + return sInstance; +} + +void UBThemeManager::applyUserThemePreference() +{ + int themeMode = UBSettings::settings()->appThemeMode->get().toInt(); + + bool isDarkMode = false; + + if (themeMode == 0) // Auto - detect from system + { +#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) + // Qt 6.5+: Use the new colorScheme API + Qt::ColorScheme colorScheme = QGuiApplication::styleHints()->colorScheme(); + isDarkMode = (colorScheme == Qt::ColorScheme::Dark); + + qDebug() << "Theme detection using Qt 6.5+ API:" << (isDarkMode ? "Dark" : "Light"); +#else + // Fallback for Qt 6.0-6.4: platform-specific detection + #ifdef Q_OS_WIN + // Windows: Check registry for dark mode + QSettings windowsSettings("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", QSettings::NativeFormat); + isDarkMode = (windowsSettings.value("AppsUseLightTheme", 1).toInt() == 0); + qDebug() << "Theme detection (Windows registry):" << (isDarkMode ? "Dark" : "Light"); + #elif defined(Q_OS_MACOS) + // macOS: Check system appearance using palette lightness + isDarkMode = (QApplication::palette().color(QPalette::Window).lightness() < 128); + qDebug() << "Theme detection (macOS palette):" << (isDarkMode ? "Dark" : "Light"); + #elif defined(Q_OS_LINUX) + // Linux: Check if current palette suggests dark theme + isDarkMode = (QApplication::palette().color(QPalette::Window).lightness() < 128); + qDebug() << "Theme detection (Linux palette):" << (isDarkMode ? "Dark" : "Light"); + #endif +#endif + } + else if (themeMode == 1) // Light + { + isDarkMode = false; + qDebug() << "Theme: User preference - Light"; + } + else if (themeMode == 2) // Dark + { + isDarkMode = true; + qDebug() << "Theme: User preference - Dark"; + } + + applyTheme(isDarkMode); +} + +void UBThemeManager::applyTheme(bool isDark) +{ + bool hasThemeChanged = (mIsDarkMode != isDark); + mIsDarkMode = isDark; + + if (mIsDarkMode) + { + setupDarkPalette(); + } + else + { + setupLightPalette(); + } + + loadStylesheets(); + + updateWidgets(); + + if (hasThemeChanged) + { + emit themeChanged(mIsDarkMode); + } +} + +void UBThemeManager::setupDarkPalette() +{ + QPalette darkPalette; + + // Base colors + darkPalette.setColor(QPalette::Window, UBTheme::Dark::Window); + darkPalette.setColor(QPalette::WindowText, UBTheme::Dark::WindowText); + darkPalette.setColor(QPalette::Base, UBTheme::Dark::Base); + darkPalette.setColor(QPalette::AlternateBase, UBTheme::Dark::AlternateBase); + darkPalette.setColor(QPalette::ToolTipBase, UBTheme::Dark::ToolTipBase); + darkPalette.setColor(QPalette::ToolTipText, UBTheme::Dark::ToolTipText); + darkPalette.setColor(QPalette::Text, UBTheme::Dark::Text); + darkPalette.setColor(QPalette::Button, UBTheme::Dark::Button); + darkPalette.setColor(QPalette::ButtonText, UBTheme::Dark::ButtonText); + darkPalette.setColor(QPalette::BrightText, UBTheme::Dark::BrightText); + darkPalette.setColor(QPalette::Link, UBTheme::Dark::Link); + darkPalette.setColor(QPalette::Highlight, UBTheme::Dark::Highlight); + darkPalette.setColor(QPalette::HighlightedText, UBTheme::Dark::HighlightedText); + darkPalette.setColor(QPalette::Light, UBTheme::Dark::Light); + darkPalette.setColor(QPalette::Midlight, UBTheme::Dark::Midlight); + darkPalette.setColor(QPalette::Dark, UBTheme::Dark::Dark); + darkPalette.setColor(QPalette::Mid, UBTheme::Dark::Mid); + darkPalette.setColor(QPalette::Shadow, UBTheme::Dark::Shadow); + + // Disabled state colors + darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, UBTheme::Dark::DisabledText); + darkPalette.setColor(QPalette::Disabled, QPalette::Text, UBTheme::Dark::DisabledText); + darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, UBTheme::Dark::DisabledText); + darkPalette.setColor(QPalette::Disabled, QPalette::Base, UBTheme::Dark::DisabledBase); + darkPalette.setColor(QPalette::Disabled, QPalette::Button, UBTheme::Dark::DisabledButton); + + QApplication::setPalette(darkPalette); + + // Update OpenBoard-specific palette colors + UBSettings::paletteColor = UBTheme::Dark::PaletteColor; + UBSettings::opaquePaletteColor = UBTheme::Dark::OpaquePaletteColor; + UBSettings::documentThumbnailsViewColor = darkPalette.color(QPalette::Window); + UBSettings::boardThumbnailsViewColor = darkPalette.color(QPalette::Window); + UBSettings::pageNavigationWidgetColor = darkPalette.color(QPalette::Base); + UBSettings::treeViewBackgroundColor = darkPalette.color(QPalette::Highlight); +} + +void UBThemeManager::setupLightPalette() +{ + QPalette lightPalette; + + // Base colors + lightPalette.setColor(QPalette::Window, UBTheme::Light::Window); + lightPalette.setColor(QPalette::WindowText, UBTheme::Light::WindowText); + lightPalette.setColor(QPalette::Base, UBTheme::Light::Base); + lightPalette.setColor(QPalette::AlternateBase, UBTheme::Light::AlternateBase); + lightPalette.setColor(QPalette::ToolTipBase, UBTheme::Light::ToolTipBase); + lightPalette.setColor(QPalette::ToolTipText, UBTheme::Light::ToolTipText); + lightPalette.setColor(QPalette::Text, UBTheme::Light::Text); + lightPalette.setColor(QPalette::Button, UBTheme::Light::Button); + lightPalette.setColor(QPalette::ButtonText, UBTheme::Light::ButtonText); + lightPalette.setColor(QPalette::BrightText, UBTheme::Light::BrightText); + lightPalette.setColor(QPalette::Link, UBTheme::Light::Link); + lightPalette.setColor(QPalette::Highlight, UBTheme::Light::Highlight); + lightPalette.setColor(QPalette::HighlightedText, UBTheme::Light::HighlightedText); + lightPalette.setColor(QPalette::Light, UBTheme::Light::Light); + lightPalette.setColor(QPalette::Midlight, UBTheme::Light::Midlight); + lightPalette.setColor(QPalette::Dark, UBTheme::Light::Dark); + lightPalette.setColor(QPalette::Mid, UBTheme::Light::Mid); + lightPalette.setColor(QPalette::Shadow, UBTheme::Light::Shadow); + + // Disabled state colors + lightPalette.setColor(QPalette::Disabled, QPalette::WindowText, UBTheme::Light::DisabledText); + lightPalette.setColor(QPalette::Disabled, QPalette::Text, UBTheme::Light::DisabledText); + lightPalette.setColor(QPalette::Disabled, QPalette::ButtonText, UBTheme::Light::DisabledText); + lightPalette.setColor(QPalette::Disabled, QPalette::Base, UBTheme::Light::DisabledBase); + lightPalette.setColor(QPalette::Disabled, QPalette::Button, UBTheme::Light::DisabledButton); + + QApplication::setPalette(lightPalette); + + // Update OpenBoard-specific palette colors + UBSettings::paletteColor = UBTheme::Light::PaletteColor; + UBSettings::opaquePaletteColor = UBTheme::Light::OpaquePaletteColor; + UBSettings::documentThumbnailsViewColor = QColor(214, 222, 235); + UBSettings::boardThumbnailsViewColor = UBTheme::Light::PaletteColor; + UBSettings::pageNavigationWidgetColor = UBTheme::Light::PaletteColor; + UBSettings::treeViewBackgroundColor = lightPalette.color(QPalette::Highlight); +} + +void UBThemeManager::loadStylesheets() +{ + QString fullStylesheet; + + // Layer 1: Embedded base stylesheet shared by light and dark themes + QString embeddedStyle = UBFileSystemUtils::readTextFile(":/style.qss"); + if (!embeddedStyle.isEmpty()) + { + fullStylesheet += embeddedStyle; + } + + // Layer 2: Theme-specific stylesheet (dark or light) + if (mIsDarkMode) + { + QString darkThemeCss = UBFileSystemUtils::readTextFile(":/darkTheme.qss"); + if (darkThemeCss.isEmpty()) { + // Fallback to file system if resource not found (allows user customization) + darkThemeCss = UBFileSystemUtils::readTextFile( + UBPlatformUtils::applicationResourcesDirectory() + "/darkTheme.qss"); + + if (darkThemeCss.isEmpty()) { + qWarning() << "Failed to load dark theme stylesheet from resources and filesystem"; + } + } + if (!darkThemeCss.isEmpty()) { + if (!fullStylesheet.isEmpty()) { + fullStylesheet += "\n"; + } + fullStylesheet += darkThemeCss; + } + } else { + QString lightThemeCss = UBFileSystemUtils::readTextFile(":/lightTheme.qss"); + if (lightThemeCss.isEmpty()) { + // Fallback to file system if resource not found (allows user customization) + lightThemeCss = UBFileSystemUtils::readTextFile( + UBPlatformUtils::applicationResourcesDirectory() + "/lightTheme.qss"); + + if (lightThemeCss.isEmpty()) { + qWarning() << "Failed to load light theme stylesheet from resources and filesystem"; + } + } + if (!lightThemeCss.isEmpty()) { + if (!fullStylesheet.isEmpty()) { + fullStylesheet += "\n"; + } + fullStylesheet += lightThemeCss; + } + } + + // Layer 3: Standard CSS (user customizable, uses palette() references) + QString css = UBFileSystemUtils::readTextFile( + UBPlatformUtils::applicationEtcDirectory() + "/" + qApp->applicationName() + ".css"); + if (!css.isEmpty()) { + if (!fullStylesheet.isEmpty()) { + fullStylesheet += "\n"; + } + fullStylesheet += css; + } + + // Apply the combined stylesheet once + qApp->setStyleSheet(fullStylesheet); +} + +void UBThemeManager::updateWidgets() +{ + // Update all widgets after a theme change + if (!UBApplication::mainWindow) + { + qDebug() << "Main window not initialized, skipping widget updates"; + return; + } + + // Update all floating palettes + auto floatingPalettes = UBApplication::mainWindow->findChildren(); + for (auto floatingPalette: std::as_const(floatingPalettes)) + { + floatingPalette->setBackgroundBrush(QBrush(UBSettings::paletteColor)); + } + + // Update left and right dock palettes + if (UBApplication::boardController && UBApplication::boardController->paletteManager()) + { + auto paletteManager = UBApplication::boardController->paletteManager(); + + if (paletteManager->leftPalette()) + { + paletteManager->leftPalette()->setBackgroundBrush(QBrush(UBSettings::paletteColor)); + } + + if (paletteManager->rightPalette()) + { + paletteManager->rightPalette()->setBackgroundBrush(QBrush(UBSettings::paletteColor)); + } + } + + // Update desktop left palette and property palettes (pen, marker and eraser sub palettes) + if (UBApplication::applicationController && + UBApplication::applicationController->uninotesController()) + { + auto desktopController = UBApplication::applicationController->uninotesController(); + + if (desktopController->desktopPalette()) + { + desktopController->desktopPalette()->setBackgroundBrush(QBrush(UBSettings::opaquePaletteColor)); + } + + } + + // Update startup hints palette background + // (HTML content updates via themeChanged signal → JavaScript listener) + if (UBApplication::boardController && + UBApplication::boardController->paletteManager() && + UBApplication::boardController->paletteManager()->tipsPalette()) + { + auto startupHint = UBApplication::boardController->paletteManager()->tipsPalette(); + + startupHint->setBackgroundBrush(QBrush(UBSettings::paletteColor)); + startupHint->setPalette(QApplication::palette()); + startupHint->update(); + } + + auto updateThumbnailSceneTextColor = [](QGraphicsScene* thumbnailScene) + { + if (!thumbnailScene) + { + return; + } + + for (auto item : thumbnailScene->items()) + { + if (auto textItem = dynamic_cast(item)) + { + textItem->setDefaultTextColor(QApplication::palette().color(QPalette::WindowText)); + } + } + }; + + auto documentThumbnailViews = UBApplication::mainWindow->findChildren(); + for (auto documentThumbnailView : std::as_const(documentThumbnailViews)) + { + documentThumbnailView->QGraphicsView::setBackgroundBrush(QBrush(UBSettings::documentThumbnailsViewColor)); + updateThumbnailSceneTextColor(documentThumbnailView->scene()); + } + + auto boardThumbnailViews = UBApplication::mainWindow->findChildren(); + for (auto boardThumbnailView : std::as_const(boardThumbnailViews)) + { + updateThumbnailSceneTextColor(boardThumbnailView->scene()); + } + + // Force all widgets to update their appearance + // This ensures that all widgets pick up the new palette and stylesheet + QList allWidgets = UBApplication::mainWindow->findChildren(); + for (QWidget* widget : allWidgets) + { + widget->update(); + } + + // Also update the main window itself + UBApplication::mainWindow->update(); + + qDebug() << "Widget updates completed"; +} + +void UBThemeManager::onColorSchemeChanged(Qt::ColorScheme colorScheme) +{ + qDebug() << "System color scheme changed to:" << (colorScheme == Qt::ColorScheme::Dark ? "Dark" : "Light"); + applyTheme(colorScheme == Qt::ColorScheme::Dark); +} diff --git a/src/core/UBThemeManager.h b/src/core/UBThemeManager.h new file mode 100644 index 000000000..66b17257b --- /dev/null +++ b/src/core/UBThemeManager.h @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2015-2022 Département de l'Instruction Publique (DIP-SEM) + * + * Copyright (C) 2013 Open Education Foundation + * + * 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 + * 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, + * 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 OpenBoard. If not, see . + */ + +#ifndef UBTHEMEMANAGER_H +#define UBTHEMEMANAGER_H + +#include +#include +#include + +/** + * @brief Manages application theme (dark/light mode) detection and application + * + * This class is responsible for: + * - Detecting system theme preference (Windows, macOS, Linux) + * - Applying appropriate color palettes + * - Loading and managing theme stylesheets + * - Notifying components of theme changes + */ +class UBThemeManager : public QObject +{ + Q_OBJECT + +public: + /** + * @brief Get the singleton instance + */ + static UBThemeManager* instance(); + + /** + * @brief Apply theme based on user preference setting + * + * Respects UBSettings::appThemeMode: 0 = Auto, 1 = Light, 2 = Dark + */ + void applyUserThemePreference(); + + /** + * @brief Apply a specific theme (dark or light) + * @param isDark true for dark mode, false for light mode + */ + void applyTheme(bool isDark); + + /** + * @brief Check if dark mode is currently active + * @return true if dark mode is enabled + */ + bool isDarkMode() const { return mIsDarkMode; } + + /** + * @brief Update all theme-dependent widgets after theme change + * + * This method updates runtime widget instances including: + * - Floating palettes + * - Dock palettes + * - Desktop property palettes + * - Startup hints palette + */ + void updateWidgets(); + +public slots: + /** + * @brief Handle system color scheme changes (Qt 6.5+) + * @param colorScheme The new color scheme from the OS + */ + void onColorSchemeChanged(Qt::ColorScheme colorScheme); + +signals: + /** + * @brief Emitted when the theme changes + * @param isDark true if switched to dark mode + */ + void themeChanged(bool isDark); + +private: + UBThemeManager(); + ~UBThemeManager(); + + UBThemeManager(const UBThemeManager&) = delete; + UBThemeManager& operator=(const UBThemeManager&) = delete; + + /** + * @brief Setup the dark theme palette + */ + void setupDarkPalette(); + + /** + * @brief Setup the light theme palette + */ + void setupLightPalette(); + + /** + * @brief Load and concatenate all stylesheets + */ + void loadStylesheets(); + + static UBThemeManager* sInstance; + bool mIsDarkMode; +}; + +/** + * @brief Theme color constants + */ +namespace UBTheme { + namespace Dark { + const QColor Window(53, 53, 53); + const QColor WindowText(Qt::white); + const QColor Base(42, 42, 42); + const QColor AlternateBase(66, 66, 66); + const QColor ToolTipBase(53, 53, 53); + const QColor ToolTipText(Qt::white); + const QColor Text(Qt::white); + const QColor Button(53, 53, 53); + const QColor ButtonText(Qt::white); + const QColor BrightText(Qt::red); + const QColor Link(42, 130, 218); + const QColor Highlight(42, 130, 218); + const QColor HighlightedText(Qt::black); + const QColor Light(70, 70, 70); + const QColor Midlight(60, 60, 60); + const QColor Dark(35, 35, 35); + const QColor Mid(50, 50, 50); + const QColor Shadow(20, 20, 20); + const QColor DisabledText(127, 127, 127); + const QColor DisabledBase(49, 49, 49); + const QColor DisabledButton(53, 53, 53); + + // OpenBoard-specific palette colors + const QColor PaletteColor(70, 70, 70, 220); + const QColor OpaquePaletteColor(50, 50, 50, 255); + } + + namespace Light { + const QColor Window(237, 242, 247); + const QColor WindowText(31, 41, 55); + const QColor Base(Qt::white); + const QColor AlternateBase(247, 249, 252); + const QColor ToolTipBase(Qt::white); + const QColor ToolTipText(31, 41, 55); + const QColor Text(31, 41, 55); + const QColor Button(247, 249, 252); + const QColor ButtonText(31, 41, 55); + const QColor BrightText(Qt::red); + const QColor Link(61, 143, 209); + const QColor Highlight(61, 143, 209); + const QColor HighlightedText(Qt::white); + const QColor Light(Qt::white); + const QColor Midlight(242, 246, 250); + const QColor Dark(179, 191, 205); + const QColor Mid(205, 214, 224); + const QColor Shadow(143, 154, 168); + const QColor DisabledText(148, 163, 184); + const QColor DisabledBase(242, 242, 242); + const QColor DisabledButton(239, 242, 247); + + // OpenBoard-specific palette colors + const QColor PaletteColor(236, 241, 247, 235); + const QColor OpaquePaletteColor(236, 241, 247, 255); + } +} + +#endif // UBTHEMEMANAGER_H diff --git a/src/core/UBWidgetInspector.cpp b/src/core/UBWidgetInspector.cpp new file mode 100644 index 000000000..0abc32a80 --- /dev/null +++ b/src/core/UBWidgetInspector.cpp @@ -0,0 +1,335 @@ +#include "UBWidgetInspector.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +UBWidgetInspector::UBWidgetInspector(QObject *parent) + : QObject(parent) + , mInspectorDialog(nullptr) + , mInspectorView(nullptr) +{ +} + +bool UBWidgetInspector::eventFilter(QObject *obj, QEvent *event) +{ + if (!event || QCoreApplication::closingDown()) + return QObject::eventFilter(obj, event); + + switch (event->type()) { + case QEvent::KeyPress: { + const auto *keyEvent = static_cast(event); + if (keyEvent->key() != Qt::Key_F12 || keyEvent->isAutoRepeat()) + return QObject::eventFilter(obj, event); + + inspectWidgetUnderCursor(); + return true; + } + default: + return QObject::eventFilter(obj, event); + } +} + +void UBWidgetInspector::inspectWidgetUnderCursor() +{ + QWidget *widget = QApplication::widgetAt(QCursor::pos()); + showInspector(buildInfo(widget)); +} + +QString UBWidgetInspector::buildInfo(const QWidget *widget) const +{ + if (!widget) { + return QStringLiteral("No widget under cursor."); + } + + const QString name = widget->objectName().isEmpty() + ? QStringLiteral("") + : widget->objectName(); + const QString styleName = + (widget->style() && widget->style()->metaObject()) + ? QString::fromUtf8(widget->style()->metaObject()->className()) + : QStringLiteral(""); + const QList hierarchy = buildWidgetHierarchy(widget); + const QStringList selectorTokens = buildSelectorTokens(hierarchy); + const QList sources = buildStyleSheetSources(hierarchy); + + QStringList reportSections; + reportSections << QStringLiteral("Target\n%1 obj=\"%2\"\nstyle=\"%3\"") + .arg(QString::fromUtf8(widget->metaObject()->className()), + name, + styleName); + reportSections << buildHierarchySection(hierarchy); + reportSections << QStringLiteral("Precedence\n1. widgetQss on the target widget\n2. parentQss from the nearest parent outward\n3. appQss from QApplication\nWithin one source, more specific selectors win; ties are resolved by later rules."); + reportSections << buildRelevantStylesSection(sources, selectorTokens); + + return reportSections.join(QStringLiteral("\n\n")); +} + +void UBWidgetInspector::showInspector(const QString &info) +{ + ensureInspectorDialog(); + + mInspectorView->setPlainText(info); + mInspectorView->moveCursor(QTextCursor::Start); + + if (!mInspectorDialog->isVisible()) + mInspectorDialog->show(); + + positionInspector(); + mInspectorDialog->raise(); + mInspectorDialog->activateWindow(); +} + +void UBWidgetInspector::ensureInspectorDialog() +{ + if (mInspectorDialog && mInspectorView) + return; + + auto *dialog = new QDialog(); + dialog->setWindowTitle(QStringLiteral("Widget Inspector")); + dialog->setWindowFlag(Qt::Tool, true); + dialog->resize(760, 520); + + auto *layout = new QVBoxLayout(dialog); + layout->setContentsMargins(8, 8, 8, 8); + + auto *view = new QPlainTextEdit(dialog); + view->setReadOnly(true); + view->setLineWrapMode(QPlainTextEdit::NoWrap); + layout->addWidget(view); + + mInspectorDialog = dialog; + mInspectorView = view; + + connect(dialog, &QObject::destroyed, this, [this]() { + mInspectorDialog.clear(); + mInspectorView.clear(); + }); +} + +void UBWidgetInspector::positionInspector() +{ + if (!mInspectorDialog) + return; + + QPoint position = QCursor::pos() + QPoint(24, 24); + if (QScreen *screen = QGuiApplication::screenAt(QCursor::pos())) { + const QRect availableGeometry = screen->availableGeometry(); + const QSize dialogSize = mInspectorDialog->size(); + + if (position.x() + dialogSize.width() > availableGeometry.right()) + position.rx() = qMax(availableGeometry.left(), availableGeometry.right() - dialogSize.width() + 1); + + if (position.y() + dialogSize.height() > availableGeometry.bottom()) + position.ry() = qMax(availableGeometry.top(), availableGeometry.bottom() - dialogSize.height() + 1); + } + + mInspectorDialog->move(position); +} + +QString UBWidgetInspector::describeWidget(const QWidget *widget) +{ + if (!widget) + return QStringLiteral(""); + + const QString objectName = widget->objectName().isEmpty() + ? QStringLiteral("") + : widget->objectName(); + return QStringLiteral("%1 obj=\"%2\"") + .arg(QString::fromUtf8(widget->metaObject()->className()), objectName); +} + +QList UBWidgetInspector::buildWidgetHierarchy(const QWidget *widget) +{ + QList hierarchy; + for (const QWidget *current = widget; current; current = current->parentWidget()) + hierarchy << current; + return hierarchy; +} + +QString UBWidgetInspector::buildHierarchySection(const QList &hierarchy) +{ + QStringList lines; + lines << QStringLiteral("Hierarchy"); + + for (int index = 0; index < hierarchy.size(); ++index) { + const QWidget *widget = hierarchy.at(index); + const QString prefix = (index == 0) ? QStringLiteral("0. target") : QStringLiteral("%1. parent").arg(index); + lines << QStringLiteral("%1 %2").arg(prefix, describeWidget(widget)); + } + + return lines.join(QStringLiteral("\n")); +} + +QStringList UBWidgetInspector::buildSelectorTokens(const QList &hierarchy) +{ + QStringList tokens; + + for (const QWidget *widget : hierarchy) { + if (!widget) + continue; + + if (!widget->objectName().isEmpty()) + tokens << QStringLiteral("#%1").arg(widget->objectName()); + + for (const QMetaObject *metaObject = widget->metaObject(); metaObject; metaObject = metaObject->superClass()) { + const QString className = QString::fromUtf8(metaObject->className()); + if (className.isEmpty()) + continue; + + tokens << className; + + if (className == QStringLiteral("QWidget")) + break; + } + } + + tokens.removeDuplicates(); + return tokens; +} + +QList UBWidgetInspector::buildStyleSheetSources(const QList &hierarchy) +{ + QList sources; + + if (!hierarchy.isEmpty()) { + sources << StyleSheetSource{QStringLiteral("widgetQss %1").arg(describeWidget(hierarchy.first())), + hierarchy.first()->styleSheet()}; + } + + for (int index = 1; index < hierarchy.size(); ++index) { + sources << StyleSheetSource{QStringLiteral("parentQss %1").arg(describeWidget(hierarchy.at(index))), + hierarchy.at(index)->styleSheet()}; + } + + sources << StyleSheetSource{QStringLiteral("appQss"), qApp ? qApp->styleSheet() : QString()}; + return sources; +} + +QString UBWidgetInspector::buildRelevantStylesSection(const QList &sources, const QStringList &selectorTokens) +{ + QStringList sections; + sections << QStringLiteral("Relevant Style Rules"); + + for (const StyleSheetSource &source : sources) { + const QStringList relevantRules = filterRelevantRules(source.styleSheet, selectorTokens); + if (relevantRules.isEmpty()) + continue; + + sections << QStringLiteral("%1\n%2").arg(source.label, relevantRules.join(QStringLiteral("\n\n"))); + } + + if (sections.size() == 1) + sections << QStringLiteral("No selector block referencing the target hierarchy was found."); + + return sections.join(QStringLiteral("\n\n")); +} + +QStringList UBWidgetInspector::filterRelevantRules(const QString &styleSheet, const QStringList &selectorTokens) +{ + QStringList relevantRules; + + for (const StyleRule &rule : parseStyleRules(styleSheet)) { + const QStringList matchedTokens = matchedTokensForSelectors(rule.selectors, selectorTokens); + if (matchedTokens.isEmpty()) + continue; + + relevantRules << QStringLiteral("[matches: %1]\n%2 {\n%3\n}") + .arg(matchedTokens.join(QStringLiteral(", ")), + rule.selectors.trimmed(), + indentMultiline(rule.body.trimmed(), QStringLiteral(" "))); + } + + return relevantRules; +} + +QList UBWidgetInspector::parseStyleRules(const QString &styleSheet) +{ + const QString source = stripComments(styleSheet); + QList rules; + int position = 0; + + while (position < source.size()) { + const int openBrace = source.indexOf(QChar('{'), position); + if (openBrace < 0) + break; + + int depth = 1; + int cursor = openBrace + 1; + while (cursor < source.size() && depth > 0) { + if (source.at(cursor) == QChar('{')) + ++depth; + else if (source.at(cursor) == QChar('}')) + --depth; + ++cursor; + } + + if (depth != 0) + break; + + const QString selectors = source.mid(position, openBrace - position).trimmed(); + const QString body = source.mid(openBrace + 1, cursor - openBrace - 2).trimmed(); + if (!selectors.isEmpty() && !body.isEmpty()) + rules << StyleRule{selectors, body}; + + position = cursor; + } + + return rules; +} + +QString UBWidgetInspector::stripComments(const QString &styleSheet) +{ + QString stripped = styleSheet; + static const QRegularExpression commentPattern(QStringLiteral("/\\*.*?\\*/"), + QRegularExpression::DotMatchesEverythingOption); + stripped.remove(commentPattern); + return stripped; +} + +QStringList UBWidgetInspector::matchedTokensForSelectors(const QString &selectors, const QStringList &selectorTokens) +{ + QStringList matchedTokens; + const QStringList selectorParts = selectors.split(QChar(','), Qt::SkipEmptyParts); + + for (const QString &selectorPart : selectorParts) { + const QString selector = selectorPart.trimmed(); + if (selector.isEmpty()) + continue; + + for (const QString &token : selectorTokens) { + if (selector.contains(token)) + matchedTokens << token; + } + } + + matchedTokens.removeDuplicates(); + return matchedTokens; +} + +QString UBWidgetInspector::indentMultiline(const QString &text, const QString &indent) +{ + QStringList lines = normalizeStyleSheet(text).split(QChar('\n')); + for (QString &line : lines) + line.prepend(indent); + return lines.join(QStringLiteral("\n")); +} + +QString UBWidgetInspector::normalizeStyleSheet(const QString &styleSheet) +{ + QString normalized = styleSheet.trimmed(); + normalized.replace(QStringLiteral("\r\n"), QStringLiteral("\n")); + normalized.replace(QChar('\r'), QChar('\n')); + return normalized; +} diff --git a/src/core/UBWidgetInspector.h b/src/core/UBWidgetInspector.h new file mode 100644 index 000000000..551938130 --- /dev/null +++ b/src/core/UBWidgetInspector.h @@ -0,0 +1,60 @@ +#ifndef UBWIDGETINSPECTOR_H_ +#define UBWIDGETINSPECTOR_H_ + +#include +#include +#include +#include + +class QDialog; +class QEvent; +class QPlainTextEdit; +class QWidget; + +class UBWidgetInspector : public QObject +{ + Q_OBJECT + +public: + explicit UBWidgetInspector(QObject *parent = nullptr); + +protected: + bool eventFilter(QObject *obj, QEvent *event) override; + +private: + struct StyleSheetSource + { + QString label; + QString styleSheet; + }; + + struct StyleRule + { + QString selectors; + QString body; + }; + + void inspectWidgetUnderCursor(); + QString buildInfo(const QWidget *widget) const; + void showInspector(const QString &info); + void ensureInspectorDialog(); + void positionInspector(); + + static QString describeWidget(const QWidget *widget); + static QList buildWidgetHierarchy(const QWidget *widget); + static QString buildHierarchySection(const QList &hierarchy); + static QStringList buildSelectorTokens(const QList &hierarchy); + static QList buildStyleSheetSources(const QList &hierarchy); + static QString buildRelevantStylesSection(const QList &sources, const QStringList &selectorTokens); + static QStringList filterRelevantRules(const QString &styleSheet, const QStringList &selectorTokens); + static QList parseStyleRules(const QString &styleSheet); + static QString stripComments(const QString &styleSheet); + static QStringList matchedTokensForSelectors(const QString &selectors, const QStringList &selectorTokens); + static QString indentMultiline(const QString &text, const QString &indent); + static QString normalizeStyleSheet(const QString &styleSheet); + + QPointer mInspectorDialog; + QPointer mInspectorView; +}; + +#endif /* UBWIDGETINSPECTOR_H_ */ diff --git a/src/core/core.pri b/src/core/core.pri index 293e143e6..09ad83dee 100644 --- a/src/core/core.pri +++ b/src/core/core.pri @@ -2,6 +2,7 @@ HEADERS += src/core/UB.h \ src/core/UBShortcutManager.h \ src/core/UBApplication.h \ + src/core/UBThemeManager.h \ src/core/UBSettings.h \ src/core/UBSetting.h \ src/core/UBPersistenceManager.h \ @@ -15,12 +16,13 @@ HEADERS += src/core/UB.h \ src/core/UBDownloadManager.h \ src/core/UBDownloadThread.h \ src/core/UBTextTools.h \ - src/core/UBPersistenceWorker.h \ - $$PWD/UBForeignObjectsHandler.h + src/core/UBWidgetInspector.h \ + src/core/UBPersistenceWorker.h SOURCES += src/core/main.cpp \ src/core/UBShortcutManager.cpp \ src/core/UBApplication.cpp \ + src/core/UBThemeManager.cpp \ src/core/UBSettings.cpp \ src/core/UBSetting.cpp \ src/core/UBPersistenceManager.cpp \ @@ -34,5 +36,5 @@ SOURCES += src/core/main.cpp \ src/core/UBDownloadManager.cpp \ src/core/UBDownloadThread.cpp \ src/core/UBTextTools.cpp \ - src/core/UBPersistenceWorker.cpp \ - $$PWD/UBForeignObjectsHandler.cpp + src/core/UBWidgetInspector.cpp \ + src/core/UBPersistenceWorker.cpp diff --git a/src/core/main.cpp b/src/core/main.cpp index 03db78ed9..985ea0512 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -28,12 +28,14 @@ #include - #include "frameworks/UBPlatformUtils.h" #include "frameworks/UBFileSystemUtils.h" #include "UBApplication.h" #include "UBSettings.h" +#ifdef QT_DEBUG +#include "UBWidgetInspector.h" +#endif /* Uncomment this for memory leaks detection */ /* @@ -134,6 +136,10 @@ int main(int argc, char *argv[]) UBApplication app("OpenBoard", argc, argv); +#ifdef QT_DEBUG + app.installEventFilter(new UBWidgetInspector(&app)); +#endif + QStringList args = app.arguments(); QString dumpPath = UBSettings::userDataDirectory() + "/log"; diff --git a/src/desktop/UBCustomCaptureWindow.cpp b/src/desktop/UBCustomCaptureWindow.cpp index 79fab9301..0193b70d1 100644 --- a/src/desktop/UBCustomCaptureWindow.cpp +++ b/src/desktop/UBCustomCaptureWindow.cpp @@ -79,16 +79,8 @@ int UBCustomCaptureWindow::execute(const QPixmap &pScreenPixmap) { mWholeScreenPixmap = pScreenPixmap; - // necessary so that changing geometry really affects the widget - showNormal(); - const auto geo = UBApplication::displayManager->screenGeometry(ScreenRole::Desktop); - setGeometry(geo); - - // for some Qt versions it is also necessary to do explicit move and resize - move(geo.topLeft()); - resize(geo.size()); - - this->show(); + setGeometry(UBApplication::displayManager->screenGeometry(ScreenRole::Desktop)); + showFullScreen(); setWindowOpacity(1.0); return exec(); diff --git a/src/desktop/UBDesktopAnnotationController.cpp b/src/desktop/UBDesktopAnnotationController.cpp index 6d4e1da48..14948b3a7 100644 --- a/src/desktop/UBDesktopAnnotationController.cpp +++ b/src/desktop/UBDesktopAnnotationController.cpp @@ -29,6 +29,9 @@ #include "UBDesktopAnnotationController.h" +#include +#include + #include "frameworks/UBPlatformUtils.h" #include "core/UBApplication.h" @@ -79,6 +82,7 @@ UBDesktopAnnotationController::UBDesktopAnnotationController(QObject *parent, UB { mTransparentDrawingView = new UBBoardView(UBApplication::boardController, static_cast(0), false, true); // deleted in UBDesktopAnnotationController::destructor + mTransparentDrawingView->setWindowTitle("DesktopView"); mTransparentDrawingView->setAttribute(Qt::WA_TranslucentBackground, true); #ifdef Q_OS_OSX // didn't find the equivalent in Qt6 @@ -95,7 +99,9 @@ UBDesktopAnnotationController::UBDesktopAnnotationController(QObject *parent, UB mTransparentDrawingView->setAcceptDrops(true); - QString backgroundStyle = "QWidget {background-color: rgba(127, 127, 127, 0)}"; + QString backgroundStyle = + "QGraphicsView { background-color: rgba(127, 127, 127, 0); }" + "QWidget#qt_scrollarea_viewport { background-color: rgba(127, 127, 127, 0); }"; mTransparentDrawingView->setStyleSheet(backgroundStyle); mTransparentDrawingScene = std::make_shared(nullptr, false); @@ -145,10 +151,7 @@ UBDesktopAnnotationController::UBDesktopAnnotationController(QObject *parent, UB mDesktopMarkerPalette = new UBDesktopMarkerPalette(mTransparentDrawingView, rightPalette, "desktop"); mDesktopEraserPalette = new UBDesktopEraserPalette(mTransparentDrawingView, rightPalette, "desktop"); - mDesktopPalette->setBackgroundBrush(UBSettings::settings()->opaquePaletteColor); - mDesktopPenPalette->setBackgroundBrush(UBSettings::settings()->opaquePaletteColor); - mDesktopMarkerPalette->setBackgroundBrush(UBSettings::settings()->opaquePaletteColor); - mDesktopEraserPalette->setBackgroundBrush(UBSettings::settings()->opaquePaletteColor); + mDesktopPalette->setBackgroundBrush(QBrush(UBSettings::settings()->opaquePaletteColor)); // Hack : the size of the property palettes is computed the first time the palette is visible @@ -186,11 +189,31 @@ UBDesktopAnnotationController::~UBDesktopAnnotationController() delete mTransparentDrawingView; } +QPixmap UBDesktopAnnotationController::clipboardScreenshot() +{ + QClipboard* clipboard = QGuiApplication::clipboard(); + + if (!clipboard) + { + return {}; + } + + const QImage image = clipboard->image(); + + if (!image.isNull()) + { + return QPixmap::fromImage(image); + } + + const QPixmap pixmap = clipboard->pixmap(); + return pixmap.isNull() ? QPixmap{} : pixmap; +} + void UBDesktopAnnotationController::updateColors(){ if(UBApplication::boardController->activeScene()->isDarkBackground()){ - mTransparentDrawingScene->setBackground(true, UBPageBackground::plain); + mTransparentDrawingScene->setSceneBackground(true, nullptr); }else{ - mTransparentDrawingScene->setBackground(false, UBPageBackground::plain); + mTransparentDrawingScene->setSceneBackground(false, nullptr); } } @@ -350,6 +373,10 @@ void UBDesktopAnnotationController::showWindow() #ifdef UB_REQUIRES_MASK_UPDATE updateMask(true); #endif // UB_REQUIRES_MASK_UPDATE + +#ifdef Q_OS_LINUX + UBPlatformUtils::keepOnTop(); +#endif } @@ -442,25 +469,35 @@ void UBDesktopAnnotationController::customCapture() updateBackground(); mDesktopPalette->disappearForCapture(); - UBCustomCaptureWindow customCaptureWindow(mDesktopPalette); - // need to show the window before execute it to avoid some glitch on windows. - #ifndef Q_OS_WIN // Working only without this call on win32 desktop mode - UBPlatformUtils::showFullScreen(&customCaptureWindow); - #endif + getScreenPixmap([this](QPixmap pixmap){ + auto finishCapture = [this](){ + mDesktopPalette->appear(); + mCustomCaptureClicked = false; + mIsFullyTransparent = false; + updateBackground(); + }; - if (customCaptureWindow.execute(getScreenPixmap()) == QDialog::Accepted) - { - QPixmap selectedPixmap = customCaptureWindow.getSelectedPixmap(); - emit imageCaptured(selectedPixmap, false); - } + QPixmap screenshot = pixmap.isNull() ? clipboardScreenshot() : pixmap; + // On Wayland with xdg-desktop-portal, the user already chose the area. + if (UBPlatformUtils::sessionType() == UBPlatformUtils::WAYLAND && !screenshot.isNull()) + { + emit imageCaptured(screenshot, false); + finishCapture(); + return; + } - mDesktopPalette->appear(); + UBCustomCaptureWindow customCaptureWindow(mDesktopPalette); - mCustomCaptureClicked = false; - mIsFullyTransparent = false; - updateBackground(); + if (customCaptureWindow.execute(screenshot) == QDialog::Accepted) + { + QPixmap selectedPixmap = customCaptureWindow.getSelectedPixmap(); + emit imageCaptured(selectedPixmap, false); + } + + finishCapture(); + }); } } @@ -473,21 +510,31 @@ void UBDesktopAnnotationController::screenCapture() mDesktopPalette->disappearForCapture(); - QPixmap originalPixmap = getScreenPixmap(); - - mDesktopPalette->appear(); + getScreenPixmap([this](QPixmap pixmap){ + auto finishCapture = [this](){ + mDesktopPalette->appear(); + mIsFullyTransparent = false; + updateBackground(); + }; - emit imageCaptured(originalPixmap, false); + QPixmap screenshot = pixmap.isNull() ? clipboardScreenshot() : pixmap; - mIsFullyTransparent = false; + if (UBPlatformUtils::sessionType() == UBPlatformUtils::WAYLAND && !screenshot.isNull()) + { + emit imageCaptured(screenshot, false); + finishCapture(); + return; + } - updateBackground(); + emit imageCaptured(screenshot, false); + finishCapture(); + }); } -QPixmap UBDesktopAnnotationController::getScreenPixmap() +void UBDesktopAnnotationController::getScreenPixmap(std::function callback) { - return UBApplication::displayManager->grab(ScreenRole::Control); + UBApplication::displayManager->grab(ScreenRole::Control, callback); } diff --git a/src/desktop/UBDesktopAnnotationController.h b/src/desktop/UBDesktopAnnotationController.h index 5a3e07130..b217a663a 100644 --- a/src/desktop/UBDesktopAnnotationController.h +++ b/src/desktop/UBDesktopAnnotationController.h @@ -99,7 +99,7 @@ class UBDesktopAnnotationController : public QObject void restoreUniboard(); protected: - QPixmap getScreenPixmap(); + void getScreenPixmap(std::function callback); UBBoardView* mTransparentDrawingView; std::shared_ptr mTransparentDrawingScene; @@ -132,6 +132,7 @@ class UBDesktopAnnotationController : public QObject void setAssociatedPalettePosition(UBActionPalette* palette, const QString& actionName); void togglePropertyPalette(UBActionPalette* palette); void updateMask(bool bTransparent); + static QPixmap clipboardScreenshot(); UBDesktopPalette *mDesktopPalette; //UBKeyboardPalette *mKeyboardPalette; diff --git a/src/desktop/UBDesktopPropertyPalette.cpp b/src/desktop/UBDesktopPropertyPalette.cpp index 3b520b170..f647f3e7a 100644 --- a/src/desktop/UBDesktopPropertyPalette.cpp +++ b/src/desktop/UBDesktopPropertyPalette.cpp @@ -42,7 +42,8 @@ UBDesktopPropertyPalette::UBDesktopPropertyPalette(QWidget *parent, UBRightPalet :UBPropertyPalette(Qt::Horizontal, parent) ,rightPalette(_rightPalette) { - setStyleSheet("QWidget { background : qlineargradient(x1:0, y1:0.49, x2:0, y2:0.5, stop:0 #c3c3c3, stop:1 #b4b4b4);}"); + setObjectName("DesktopPropertyPalette"); + setAttribute(Qt::WA_StyledBackground, true); } int UBDesktopPropertyPalette::getParentRightOffset() @@ -62,9 +63,15 @@ UBDesktopPenPalette::UBDesktopPenPalette(QWidget *parent, UBRightPalette* rightP colorActions.append(UBApplication::mainWindow->actionColor2); colorActions.append(UBApplication::mainWindow->actionColor3); colorActions.append(UBApplication::mainWindow->actionColor4); + colorActions.append(UBApplication::mainWindow->actionColor5); + colorActions.append(UBApplication::mainWindow->actionColor6); + colorActions.append(UBApplication::mainWindow->actionColor7); + colorActions.append(UBApplication::mainWindow->actionColor8); + colorActions.append(UBApplication::mainWindow->actionColor9); + colorActions.append(UBApplication::mainWindow->actionColorPreferences); UBToolbarButtonGroup *colorChoice = - new UBToolbarButtonGroup(UBApplication::mainWindow->boardToolBar, colorActions, objectNamePrefix); + new UBToolbarButtonGroup(UBApplication::mainWindow->boardToolBar, colorActions, objectNamePrefix, UBSettings::settings()->colorPaletteSize); colorChoice->displayText(false); @@ -159,8 +166,14 @@ UBDesktopMarkerPalette::UBDesktopMarkerPalette(QWidget *parent, UBRightPalette* colorActions.append(UBApplication::mainWindow->actionColor2); colorActions.append(UBApplication::mainWindow->actionColor3); colorActions.append(UBApplication::mainWindow->actionColor4); - - UBToolbarButtonGroup *colorChoice = new UBToolbarButtonGroup(UBApplication::mainWindow->boardToolBar, colorActions, objectNamePrefix); + colorActions.append(UBApplication::mainWindow->actionColor5); + colorActions.append(UBApplication::mainWindow->actionColor6); + colorActions.append(UBApplication::mainWindow->actionColor7); + colorActions.append(UBApplication::mainWindow->actionColor8); + colorActions.append(UBApplication::mainWindow->actionColor9); + colorActions.append(UBApplication::mainWindow->actionColorPreferences); + + UBToolbarButtonGroup *colorChoice = new UBToolbarButtonGroup(UBApplication::mainWindow->boardToolBar, colorActions, objectNamePrefix, UBSettings::settings()->colorPaletteSize); colorChoice->displayText(false); //connect(colorChoice, SIGNAL(activated(int)), this, SLOT(UBApplication::boardController->setColorIndex(int))); @@ -187,4 +200,3 @@ UBDesktopMarkerPalette::UBDesktopMarkerPalette(QWidget *parent, UBRightPalette* layout()->addWidget(lineWidthChoice); } - diff --git a/src/document/CMakeLists.txt b/src/document/CMakeLists.txt index 73b1982fd..a6e1f4c36 100644 --- a/src/document/CMakeLists.txt +++ b/src/document/CMakeLists.txt @@ -7,6 +7,10 @@ target_sources(${PROJECT_NAME} PRIVATE UBDocumentController.h UBDocumentProxy.cpp UBDocumentProxy.h + UBDocumentToc.cpp + UBDocumentToc.h UBSortFilterProxyModel.cpp UBSortFilterProxyModel.h + UBTocSerializer.cpp + UBTocSerializer.h ) diff --git a/src/document/UBDocument.cpp b/src/document/UBDocument.cpp index 7d669ec35..7057ae4d0 100644 --- a/src/document/UBDocument.cpp +++ b/src/document/UBDocument.cpp @@ -22,11 +22,16 @@ #include "UBDocument.h" +#include "adaptors/UBMetadataDcSubsetAdaptor.h" #include "adaptors/UBThumbnailAdaptor.h" #include "core/UBApplication.h" #include "core/UBPersistenceManager.h" #include "document/UBDocumentController.h" +#include "document/UBDocumentToc.h" +#include "domain/UBMediaAssetItem.h" +#include "frameworks/UBBackgroundLoader.h" #include "gui/UBMainWindow.h" +#include "gui/UBThumbnail.h" #include "gui/UBThumbnailScene.h" QList> UBDocument::sDocuments; @@ -34,14 +39,33 @@ QList> UBDocument::sDocuments; UBDocument::UBDocument(std::shared_ptr proxy) : mProxy(proxy) - , mThumbnailScene(new UBThumbnailScene(this)) { - mThumbnailScene->createThumbnails(); } UBDocument::~UBDocument() { - delete mThumbnailScene; + if (mSceneAssetLoader) + { + mSceneAssetLoader->abort(); + delete mSceneAssetLoader; + } + + if (QFile::exists(mProxy->persistencePath())) + { + if (mSceneHeaderLoader) + { + assureHeaderLoaderFinished(false); + delete mSceneHeaderLoader; + } + + mToc->save(); + deleteUnreferencedAssets(); + } + else if (mSceneHeaderLoader) + { + mSceneHeaderLoader->abort(); + delete mSceneHeaderLoader; + } } std::shared_ptr UBDocument::proxy() const @@ -49,113 +73,458 @@ std::shared_ptr UBDocument::proxy() const return mProxy; } -void UBDocument::deletePages(QList indexes) +bool UBDocument::deletePages(QList indexes, bool approve) { if (indexes.isEmpty()) { - return; + return true; + } + + if (approve) + { + bool accepted{false}; + + if (indexes.size() > 1) + { + accepted = UBApplication::mainWindow->yesNoQuestion( + UBDocumentController::tr("Moving %1 pages of the document \"%2\" to trash") + .arg(QString::number(indexes.size()), mProxy->name()), + UBDocumentController::tr("You are about to move %1 pages of the document \"%2\" to trash. Are you sure ?") + .arg(QString::number(indexes.size()), mProxy->name()), + QPixmap(":/images/trash-document-page.png")); + } + else + { + accepted = UBApplication::mainWindow->yesNoQuestion( + UBDocumentController::tr("Remove page %1").arg(indexes.at(0) + 1), + UBDocumentController::tr("You are about to remove page %1 of the document \"%2\". Are you sure ?") + .arg(indexes.at(0) + 1) + .arg(mProxy->name()), + QPixmap(":/images/trash-document-page.png")); + } + + if (!accepted) + { + return false; + } } - bool accepted{false}; + // sort list, remove duplicates + std::sort(indexes.begin(), indexes.end()); + indexes.erase(std::unique(indexes.begin(), indexes.end()), indexes.end()); - if (indexes.size() > 1) + // check bounds + if (indexes.first() < 0 || indexes.last() >= pageCount()) { - accepted = UBApplication::mainWindow->yesNoQuestion( - UBDocumentController::tr("Moving %1 pages of the document \"%2\" to trash") - .arg(QString::number(indexes.size()), mProxy->name()), - UBDocumentController::tr("You are about to move %1 pages of the document \"%2\" to trash. Are you sure ?") - .arg(QString::number(indexes.size()), mProxy->name()), - QPixmap(":/images/trash-document-page.png")); + return false; } - else + + // decline attempt to remove all pages + if (indexes.count() == pageCount()) { - accepted = UBApplication::mainWindow->yesNoQuestion( - UBDocumentController::tr("Remove page %1").arg(indexes.at(0) + 1), - UBDocumentController::tr("You are about to remove page %1 of the document \"%2\". Are you sure ?") - .arg(indexes.at(0) + 1) - .arg(mProxy->name()), - QPixmap(":/images/trash-document-page.png")); + // deleting all pages is blocked in the UI. Just log if it happens anyway + qWarning() << "UBDocument::deletePages: declined attempt to remove all pages of a document"; + return false; } - if (!accepted) + // copy to-be-deleted pages to trash document + QString sourceName = mProxy->metaData(UBSettings::documentName).toString(); + auto trashDocProxy = UBPersistenceManager::persistenceManager()->createDocument(UBSettings::trashedDocumentGroupNamePrefix, sourceName, false); + auto trashDocument = UBDocument::getDocument(trashDocProxy); + int trashIndex = 0; + QList pageIds; + + for (auto index : indexes) { - return; + copyPage(index, trashDocument, trashIndex++); + pageIds << mToc->pageId(index); } - std::sort(indexes.begin(), indexes.end()); - UBPersistenceManager::persistenceManager()->deleteDocumentScenes(mProxy, indexes); + // delete the scenes + UBPersistenceManager::persistenceManager()->deleteDocumentScenes(mProxy, pageIds); + assureHeaderLoaderFinished(); for (int i = indexes.size() - 1; i >= 0; --i) { - mThumbnailScene->deleteThumbnail(indexes.at(i), false); - emit UBPersistenceManager::persistenceManager()->documentSceneDeleted(mProxy, indexes.at(i)); + const auto index{indexes.at(i)}; + mToc->remove(index); + thumbnailScene()->deleteThumbnail(index, false); + emit UBPersistenceManager::persistenceManager()->documentSceneDeleted(this, index); } - mThumbnailScene->renumberThumbnails(indexes.first()); - mThumbnailScene->arrangeThumbnails(indexes.first()); + thumbnailScene()->renumberThumbnails(indexes.first()); + thumbnailScene()->arrangeThumbnails(indexes.first()); + mToc->save(); + return true; +} - QDateTime now = QDateTime::currentDateTime(); - mProxy->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(now)); +bool UBDocument::duplicatePage(int index) +{ + if (copyPage(index, this, index + 1)) + { + emit UBPersistenceManager::persistenceManager()->documentSceneDuplicated(this, index + 1); + return true; + } + + return false; } -void UBDocument::duplicatePage(int index) +bool UBDocument::movePage(int fromIndex, int toIndex) { - UBPersistenceManager::persistenceManager()->duplicateDocumentScene(mProxy, index); + if (mToc->isValidIndex(fromIndex) && mToc->isValidIndex(toIndex)) + { + auto scene = getScene(fromIndex); - mThumbnailScene->insertThumbnail(index + 1); + if (scene && scene->isModified()) + { + persistPage(scene, fromIndex); + } - QDateTime now = QDateTime::currentDateTime(); - mProxy->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(now)); + assureHeaderLoaderFinished(); + thumbnailScene()->moveThumbnail(fromIndex, toIndex); + mToc->move(fromIndex, toIndex); + mToc->save(); + emit UBPersistenceManager::persistenceManager()->documentSceneMoved(this, fromIndex, toIndex); + return true; + } - emit UBPersistenceManager::persistenceManager()->documentSceneDuplicated(mProxy, index + 1); + return false; } -void UBDocument::movePage(int fromIndex, int toIndex) +bool UBDocument::copyPage(int fromIndex, std::shared_ptr to, int toIndex) { - UBPersistenceManager::persistenceManager()->moveSceneToIndex(mProxy, fromIndex, toIndex); - mThumbnailScene->moveThumbnail(fromIndex, toIndex); - emit UBPersistenceManager::persistenceManager()->documentSceneMoved(mProxy, fromIndex, toIndex); + return copyPage(fromIndex, to.get(), toIndex); } -void UBDocument::copyPage(int fromIndex, std::shared_ptr to, int toIndex) +std::shared_ptr UBDocument::createPage(int index, bool saveToc, bool cached, bool useUndoRedoStack, bool createThumbnail) { - UBPersistenceManager::persistenceManager()->copyDocumentScene(mProxy, fromIndex, to, toIndex); + // create a new TOC entry for the page + assureHeaderLoaderFinished(); + auto pageId = mToc->insert(index); + + if (pageId < 0) + { + return nullptr; + } - const auto toDocument = findDocument(to); + auto scene = UBPersistenceManager::persistenceManager()->createDocumentSceneAt(mProxy, pageId, cached, useUndoRedoStack); + mToc->setUuid(index, scene->uuid()); + mToc->setAssets(index, {}); - if (toDocument) + if (saveToc) { - toDocument->mThumbnailScene->insertThumbnail(toIndex); + mToc->save(); } + + thumbnailScene()->insertThumbnail(index, false); + + if (createThumbnail) + { + mThumbnailScene->ensureThumbnail(index, scene.get()); + } + + return scene; } -void UBDocument::insertPage(std::shared_ptr scene, int index, bool persist, bool deleting) +void UBDocument::persistPage(std::shared_ptr scene, int index, bool isAutomaticBackup, + bool forceImmediateSaving, bool persistThumbnail, bool addToCache) { - UBPersistenceManager::persistenceManager()->insertDocumentSceneAt(mProxy, scene, index, persist, deleting); + const auto pageId = mToc->pageId(index); + + if (persistThumbnail) + { + const auto pixmap = UBThumbnailAdaptor::persistScene(this, scene, index); + + if (!pixmap.isNull()) + { + thumbnailScene()->thumbnailAt(index)->setPixmap(pixmap); + } + } - mThumbnailScene->insertThumbnail(index); + UBPersistenceManager::persistenceManager()->persistDocumentScene(mProxy, scene, pageId, isAutomaticBackup, + forceImmediateSaving, addToCache); + + const auto assets = scene->relativeDependencies(); + + if (assets != mToc->assets(index)) + { + mToc->setAssets(index, assets); + + if (addToCache) + { + // when scene is cached it is also time to save the TOC + mToc->save(); + } + } } -std::shared_ptr UBDocument::createPage(int index, bool useUndoRedoStack) +std::shared_ptr UBDocument::loadScene(int index, bool cacheNeighboringScenes) { - auto scene = UBPersistenceManager::persistenceManager()->createDocumentSceneAt(mProxy, index, useUndoRedoStack); + if (cacheNeighboringScenes) + { + auto persistenceManager = UBPersistenceManager::persistenceManager(); - mThumbnailScene->insertThumbnail(index, scene); + if(index + 1 < pageCount()) + { + persistenceManager->prepareSceneLoading(mProxy, mToc->pageId(index + 1)); + } - return scene; + if(index + 2 < pageCount()) + { + persistenceManager->prepareSceneLoading(mProxy, mToc->pageId(index + 2)); + } + + if(index - 1 >= 0) + { + persistenceManager->prepareSceneLoading(mProxy, mToc->pageId(index - 1)); + } + } + + return UBPersistenceManager::persistenceManager()->loadDocumentScene(mProxy, mToc->pageId(index)); +} + +std::shared_ptr UBDocument::getScene(int index) +{ + return UBPersistenceManager::persistenceManager()->getDocumentScene(mProxy, mToc->pageId(index)); +} + +QList UBDocument::pageRelativeDependencies(int index) +{ + if (mToc->hasAssetsEntry(index)) + { + return mToc->assets(index); + } + + auto scene = loadScene(index, false); + + if (scene) + { + const auto dependencies = scene->relativeDependencies(); + mToc->setAssets(index, dependencies); + return dependencies; + } + + return {}; } -void UBDocument::persistPage(std::shared_ptr scene, const int index, bool isAutomaticBackup, - bool forceImmediateSaving) +UBThumbnailScene* UBDocument::thumbnailScene(bool startLoader) { - UBPersistenceManager::persistenceManager()->persistDocumentScene(mProxy, scene, index, isAutomaticBackup, - forceImmediateSaving); - mThumbnailScene->reloadThumbnail(index); + if (!mThumbnailScene) + { + mThumbnailScene = std::unique_ptr(new UBThumbnailScene(this)); + + if (startLoader) + { + mThumbnailScene->createThumbnails(); + } + } + + return mThumbnailScene.get(); } -UBThumbnailScene* UBDocument::thumbnailScene() const +UBDocumentToc* UBDocument::toc() { - return mThumbnailScene; + if (!mToc) + { + mToc = new UBDocumentToc{mProxy->persistencePath()}; + const auto tocPresent = mToc->load(); + + if (!tocPresent || + QVersionNumber::fromString(mProxy->metaData(UBSettings::documentVersion).toString()) < + QVersionNumber::fromString(UBSettings::currentFileVersion)) + { + scan(tocPresent); + mToc->save(); + } + + // scan assets if missing + for (int index = 0; index < pageCount(); ++index) + { + if (!mToc->hasAssetsEntry(index)) + { + scanAssets(); + break; + } + } + } + + return mToc; +} + +int UBDocument::pageCount() +{ + return mToc->pageCount(); +} + +QString UBDocument::sceneFile(int index) +{ + if (index >= pageCount()) + { + qWarning() << "Index " << index << "above ToC size " << pageCount(); + return ""; + } + + const auto filename = UBPersistenceManager::persistenceManager()->sceneFilenameForId(mToc->pageId(index)); + return mProxy->persistencePath() + filename; +} + +QString UBDocument::thumbnailFile(int index) +{ + if (index >= pageCount()) + { + qWarning() << "Index " << index << "above ToC size " << pageCount(); + return ""; + } + + const auto filename = UBPersistenceManager::persistenceManager()->thumbnailFilenameForId(mToc->pageId(index)); + return mProxy->persistencePath() + filename; +} + +void UBDocument::sceneLoaded(UBGraphicsScene* scene, std::shared_ptr handle) +{ + mLoaderHandles.erase(handle); + + if (scene) + { + const auto persistenceManager = UBPersistenceManager::persistenceManager(); + auto index = mToc->findUuid(scene->uuid()); + + if (index >= 0 && !mToc->hasAssetsEntry(index)) + { + const auto mediaAssetItems = scene->mediaAssetItems(); + + for (auto item : mediaAssetItems) + { + // Check the media asset UUIDs. + // Convert it to a SHA-1 based UUIDv5 if needed. Copy the asset file in this case. + QUuid assetUuid = item->mediaAssetUuid(); + + if (assetUuid.version() != QUuid::Sha1) + { + // lookup UUIDv5 in cache or create from content + const auto source = item->mediaAssets().at(0); + const auto sourcePath = mProxy->persistencePath() + "/" + source; + + QUuid assetUuidV5; + bool needsCopy = false; + + if (mUuidV5Map.contains(assetUuid)) + { + assetUuidV5 = mUuidV5Map.value(assetUuid); + } + else + { + assetUuidV5 = UBMediaAssetItem::createMediaAssetUuid(sourcePath); + mUuidV5Map[assetUuid] = assetUuidV5; + needsCopy = true; + } + + auto target = source; + target.replace(assetUuid.toString(QUuid::WithoutBraces), assetUuidV5.toString(QUuid::WithoutBraces)); + + // copy assets + if (needsCopy) + { + persistenceManager->copyAsset(mProxy, source, target); + } + + item->setMediaAsset(mProxy->persistencePath(), target); + scene->setModified(true); + } + } + + mToc->setAssets(index, scene->relativeDependencies()); + } + + if (scene->isModified()) + { + persistenceManager->persistDocumentScene(mProxy, scene->shared_from_this(), mToc->pageId(index)); + } + + // generate and load thumbnail from scene if not already existing and loaded + mThumbnailScene->ensureThumbnail(index, scene); + } + + // prepare next scene + QTimer::singleShot(5, mSceneAssetLoader, [this](){ + if (mSceneAssetLoader) + { + mSceneAssetLoader->resultProcessed(); + } + }); +} + +void UBDocument::scanAssets() +{ + assureHeaderLoaderFinished(); + + // create a list of page IDs and paths of pages without asset info for background loading + QList> paths; + const auto persistenceManager = UBPersistenceManager::persistenceManager(); + persistenceManager->waitForAllSaved(); + + for (int index = 0; index < pageCount(); ++index) + { + if (!mToc->hasAssetsEntry(index)) + { + int pageId = mToc->pageId(index); + paths.append({pageId, mProxy->persistencePath() + persistenceManager->sceneFilenameForId(pageId)}); + } + } + + if (!paths.empty()) + { + qDebug() << "Scan" << paths.count() << "scenes for assets"; + // load scenes for scanning + mSceneAssetLoader = new UBBackgroundLoader{UBBackgroundLoader::ByteArray}; + + QObject::connect(mSceneAssetLoader, &UBBackgroundLoader::resultAvailable, mSceneAssetLoader, [this](int pageId, const QVariant& data){ + UBApplication::showMessage(UBDocumentController::tr("Scanning page %1 of %2").arg(pageId).arg(pageCount()), true); + + if (pageId >= 0 && !data.isNull()) + { + auto scene = UBPersistenceManager::persistenceManager()->getDocumentScene(mProxy, pageId); + + if (scene) + { + sceneLoaded(scene.get(), nullptr); + return; + } + + const auto handle = UBPersistenceManager::persistenceManager()->prepareSceneLoading(mProxy, pageId, data.toByteArray(), false); + + if (handle) + { + // loading started, keep handle until finished + mLoaderHandles.insert(handle); + return; + } + } + + // page deleted or already in cache, loading not necessary + mSceneAssetLoader->resultProcessed(); + }); + + QObject::connect(mSceneAssetLoader, &UBBackgroundLoader::finished, mSceneAssetLoader, [this](){ + qDebug() << "Asset scan completed"; + mSceneAssetLoader->deleteLater(); + mSceneAssetLoader = nullptr; + }); + + QObject::connect(mSceneAssetLoader, &QObject::destroyed, [](){ + // loader either completed or aborted + UBApplication::showMessage(UBDocumentController::tr("Scan terminated")); + }); + + mSceneAssetLoader->load(paths, -1, [this](int pageId, QString path){ + // check that the scene file actually exists to avoid a race condition + // with saving scene files during import + while (!QFile::exists(path)) + { + QThread::msleep(10); + } + }); + } } /** @@ -185,12 +554,215 @@ std::shared_ptr UBDocument::getDocument(std::shared_ptr(new UBDocument(proxy)); + document->toc(); // load and initialize TOC sDocuments << document; } return document; } +void UBDocument::scan(bool tocPresent) +{ + QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + + // scan through the pages starting from the smallest number and counting up. + QStringList pages = UBPersistenceManager::persistenceManager()->pageFiles(mProxy->persistencePath()); + + if (pages.isEmpty()) + { + qWarning() << "No pages found - " << mProxy->persistencePath(); + mProxy->setMetaData(UBSettings::documentName, UBPersistenceManager::tr("Broken - %1").arg(mProxy->documentFolderName())); + } + + // create a list of page IDs and paths for background loading + QList> paths; + + for (const auto page : pages) + { + int pageId = page.mid(4, page.length() - 8).toInt(); + paths.append({pageId, mProxy->persistencePath() + "/" + page}); + } + + if (tocPresent) + { + // we have a TOC, but the document was last modified by an earlier version of OpenBoard + // necessary to verify all scene UUIDs and update TOC accordingly + + // create a set of all scene UUIDs in the TOC + QSet tocSceneUuids; + + for (int i = 0; i < pageCount(); ++i) + { + tocSceneUuids.insert(mToc->uuid(i)); + } + + // load the first 300 bytes of each file in background, enough to get version and UUID + mSceneHeaderLoader = new UBBackgroundLoader{UBBackgroundLoader::ByteArray}; + int pageProcessed = -1; + int currentPage = 0; + const auto currentVersion = QVersionNumber::fromString(UBSettings::currentFileVersion); + + QObject::connect(mSceneHeaderLoader, &UBBackgroundLoader::resultAvailable, mSceneHeaderLoader, [&](int index, const QVariant& data){ + UBApplication::showMessage(UBDocumentController::tr("Scanning page %1 of %2").arg(++currentPage).arg(pages.count()), true); + const auto uuid = UBSvgSubsetAdaptor::sceneUuid(data.toByteArray()); + + // Check whether the tocSceneUuids already contains this scene. + // If yes, locate and update the TOC entry + if (tocSceneUuids.contains(uuid)) + { + const int index = mToc->findUuid(uuid); + + if (index >= 0) + { + pageProcessed = index; + tocSceneUuids.remove(uuid); + + // invalidate assets if scene was modified by an earlier version + if (UBSvgSubsetAdaptor::sceneVersion(data.toByteArray()) < currentVersion) + { + mToc->unsetAssets(index); + } + } + } + else + { + // add the file name and thumbnail file name to the TOC after the last processed TOC entry + qDebug() << "scan: insert scene" << index << "at offset" << pageProcessed; + ++pageProcessed; + mToc->insert(pageProcessed); + mToc->setUuid(pageProcessed, uuid); + mToc->setPageId(pageProcessed, index); + } + + mSceneHeaderLoader->resultProcessed(); + }); + + QObject::connect(mSceneHeaderLoader, &UBBackgroundLoader::finished, mSceneHeaderLoader, [&](){ + // When finished, remove all scenes still in tocSceneUuids from the TOC. + // Those scenes might have been deleted using a previous version of OpenBoard. + for (const auto uuid : tocSceneUuids) + { + qDebug() << "scan: removing scene from TOC" << uuid; + mToc->remove(mToc->findUuid(uuid)); + } + + UBApplication::showMessage(UBDocumentController::tr("Scan completed")); + mSceneHeaderLoader->deleteLater(); + mSceneHeaderLoader = nullptr; + mToc->save(); + }); + + mSceneHeaderLoader->load(paths, 300); + assureHeaderLoaderFinished(); + } + else if (!paths.empty()) + { + // start reading the UUIDs in background, but make sure it is completed when the UBDocument is deleted + mSceneHeaderLoader = new UBBackgroundLoader{UBBackgroundLoader::ByteArray}; + mSceneHeaderLoader->setKeepAlive(shared_from_this()); + + QObject::connect(mSceneHeaderLoader, &UBBackgroundLoader::resultAvailable, mSceneHeaderLoader, [this](int index, const QVariant& data){ + const auto uuid = UBSvgSubsetAdaptor::sceneUuid(data.toByteArray()); + mToc->setUuid(index, uuid); + mSceneHeaderLoader->resultProcessed(); + }); + + QObject::connect(mSceneHeaderLoader, &UBBackgroundLoader::finished, mSceneHeaderLoader, [this](){ + mSceneHeaderLoader->deleteLater(); + mSceneHeaderLoader = nullptr; + mToc->save(); + }); + + mSceneHeaderLoader->load(paths, 300); + + // set the page ID for the pages + for (int index = 0; index < pages.count(); ++index) + { + mToc->setPageId(index, paths.at(index).first); + } + } + + // now we have updated the document version + mProxy->setMetaData(UBSettings::documentVersion, UBSettings::currentFileVersion); + UBMetadataDcSubsetAdaptor::persist(mProxy); + + UBApplication::showMessage(UBDocumentController::tr("Scan completed")); + + QApplication::restoreOverrideCursor(); +} + +bool UBDocument::copyPage(int fromIndex, UBDocument* to, int toIndex) +{ + if (mToc->isValidIndex(fromIndex) && to->mToc->isValidIndex(toIndex)) + { + const auto dependencies = pageRelativeDependencies(fromIndex); + + // copy scene + to->assureHeaderLoaderFinished(); + const auto pageId = to->mToc->insert(toIndex); + const auto uuid = UBPersistenceManager::persistenceManager()->copyDocumentScene(mProxy, mToc->pageId(fromIndex), to->proxy(), pageId, dependencies); + + // update target TOC + to->mToc->setUuid(toIndex, uuid); + to->mToc->setAssets(toIndex, dependencies); + to->mToc->save(); + + // add thumbnail + to->thumbnailScene()->insertThumbnail(toIndex); + return true; + } + + return false; +} + +void UBDocument::deleteUnreferencedAssets() +{ + // Go through the TOC and create a set of all referenced media assets. + QSet referencedMediaAssets; + + for (int index = 0; index < pageCount(); ++index) + { + if (!mToc->hasAssetsEntry(index)) + { + // no cleanup with incomplete asset information + return; + } + + const auto assets = mToc->assets(index); + + for (const auto asset : assets) + { + referencedMediaAssets << asset; + } + } + + // Cleanup unused media assets + UBPersistenceManager::persistenceManager()->cleanupMediaAssets(mProxy, referencedMediaAssets); +} + +void UBDocument::assureHeaderLoaderFinished(bool visualFeedback) +{ + if (mSceneHeaderLoader) + { + if (visualFeedback) + { + UBApplication::setOverrideCursor(Qt::WaitCursor); + UBApplication::showMessage(UBDocumentController::tr("Wait for the document scan to finish"), true); + } + + while (mSceneHeaderLoader) + { + QApplication::processEvents(QEventLoop::AllEvents, 100); + } + + if (visualFeedback) + { + UBApplication::showMessage(UBDocumentController::tr("Document scan finished")); + UBApplication::restoreOverrideCursor(); + } + } +} + std::shared_ptr UBDocument::findDocument(std::shared_ptr proxy) { if (!proxy) diff --git a/src/document/UBDocument.h b/src/document/UBDocument.h index 7135e4765..5adfe168f 100644 --- a/src/document/UBDocument.h +++ b/src/document/UBDocument.h @@ -22,13 +22,18 @@ #pragma once +#include #include +#include #include +#include // forward +class UBBackgroundLoader; class UBDocumentProxy; +class UBDocumentToc; class UBGraphicsScene; class UBThumbnailScene; @@ -44,7 +49,7 @@ class UBThumbnailScene; * getDocument() function. They are always referenced by a shared pointer and live as long * as anybody wants to work with the doument. */ -class UBDocument +class UBDocument : public std::enable_shared_from_this { private: UBDocument(std::shared_ptr proxy); @@ -54,24 +59,46 @@ class UBDocument std::shared_ptr proxy() const; - void deletePages(QList indexes); - void duplicatePage(int index); - void movePage(int fromIndex, int toIndex); - void copyPage(int fromIndex, std::shared_ptr to, int toIndex); - void insertPage(std::shared_ptr scene, int index, bool persist = true, bool deleting = false); - std::shared_ptr createPage(int index, bool useUndoRedoStack = true); - void persistPage(std::shared_ptr scene, const int index, bool isAutomaticBackup = false, - bool forceImmediateSaving = false); - UBThumbnailScene* thumbnailScene() const; + bool deletePages(QList indexes, bool approve = true); + bool duplicatePage(int index); + bool movePage(int fromIndex, int toIndex); + bool copyPage(int fromIndex, std::shared_ptr to, int toIndex); + std::shared_ptr createPage(int index, bool saveToc = true, bool cached = true, + bool useUndoRedoStack = true, bool createThumbnail = true); + void persistPage(std::shared_ptr scene, int index, bool isAutomaticBackup = false, + bool forceImmediateSaving = false, bool persistThumbnail = true, bool addToCache = true); + std::shared_ptr loadScene(int index, bool cacheNeighboringScenes = true); + std::shared_ptr getScene(int index); + QList pageRelativeDependencies(int index); + UBThumbnailScene* thumbnailScene(bool startLoader = true); + UBDocumentToc* toc(); + + int pageCount(); + + QString sceneFile(int index); + QString thumbnailFile(int index); + + void sceneLoaded(UBGraphicsScene* scene, std::shared_ptr handle); + void scanAssets(); static std::shared_ptr getDocument(std::shared_ptr proxy); private: + void scan(bool tocPresent); + bool copyPage(int fromIndex, UBDocument* to, int toIndex); + void deleteUnreferencedAssets(); + void assureHeaderLoaderFinished(bool visualFeedback = true); + static std::shared_ptr findDocument(std::shared_ptr proxy); private: std::shared_ptr mProxy{nullptr}; - UBThumbnailScene* mThumbnailScene{nullptr}; + std::unique_ptr mThumbnailScene; + UBDocumentToc* mToc{nullptr}; + UBBackgroundLoader* mSceneHeaderLoader{nullptr}; + UBBackgroundLoader* mSceneAssetLoader{nullptr}; + QHash mUuidV5Map{}; + std::unordered_set> mLoaderHandles; static QList> sDocuments; }; diff --git a/src/document/UBDocumentContainer.cpp b/src/document/UBDocumentContainer.cpp index b9a486100..04c865c02 100644 --- a/src/document/UBDocumentContainer.cpp +++ b/src/document/UBDocumentContainer.cpp @@ -52,10 +52,16 @@ void UBDocumentContainer::setDocument(std::shared_ptr document, if (mCurrentDocument != document || forceReload) { mCurrentDocument = document; - emit documentSet(mCurrentDocument); + mActiveDocument = UBDocument::getDocument(mCurrentDocument); + emit documentSet(document); } } +std::shared_ptr UBDocumentContainer::activeDocument() +{ + return mActiveDocument; +} + void UBDocumentContainer::duplicatePage(int index) { auto document = UBDocument::getDocument(selectedDocument()); @@ -80,14 +86,12 @@ void UBDocumentContainer::moveSceneToIndex(std::shared_ptr prox void UBDocumentContainer::deletePages(QList& pageIndexes) { - auto document = UBDocument::getDocument(mCurrentDocument); - document->deletePages(pageIndexes); + mActiveDocument->deletePages(pageIndexes); } void UBDocumentContainer::addPage(int index) { - auto document = UBDocument::getDocument(mCurrentDocument); - document->createPage(index); + mActiveDocument->createPage(index); } diff --git a/src/document/UBDocumentContainer.h b/src/document/UBDocumentContainer.h index 157fcaf76..bab7598fc 100644 --- a/src/document/UBDocumentContainer.h +++ b/src/document/UBDocumentContainer.h @@ -33,6 +33,9 @@ #include #include "UBDocumentProxy.h" +// forward +class UBDocument; + class UBDocumentContainer : public QObject { Q_OBJECT @@ -42,10 +45,9 @@ class UBDocumentContainer : public QObject virtual ~UBDocumentContainer(); void setDocument(std::shared_ptr document, bool forceReload = false); - void pureSetDocument(std::shared_ptr document) {mCurrentDocument = document;} std::shared_ptr selectedDocument(){return mCurrentDocument;} - int pageCount() const{return mCurrentDocument->pageCount();} + std::shared_ptr activeDocument(); static int pageFromSceneIndex(int sceneIndex); static int sceneIndexFromPage(int sceneIndex); @@ -59,6 +61,7 @@ class UBDocumentContainer : public QObject private: std::shared_ptr mCurrentDocument; + std::shared_ptr mActiveDocument; signals: void documentSet(std::shared_ptr document); diff --git a/src/document/UBDocumentController.cpp b/src/document/UBDocumentController.cpp index e4c92dd5e..14c6c2e5a 100644 --- a/src/document/UBDocumentController.cpp +++ b/src/document/UBDocumentController.cpp @@ -109,8 +109,6 @@ UBDocumentReplaceDialog::UBDocumentReplaceDialog(const QString &pIncommingName, , mCancel(false) , mMultipleFiles(multipleFiles) { - this->setStyleSheet("background:white;"); - QVBoxLayout *mainLayout = new QVBoxLayout(this); QVBoxLayout *labelLayout = new QVBoxLayout(); @@ -222,20 +220,16 @@ void UBDocumentReplaceDialog::reject() void UBDocumentReplaceDialog::reactOnTextChanged(const QString &pStr) { // if !mFileNameList.contains(pStr.trimmed(), Qt::CaseSensitive) - if (!validString(pStr)) { acceptButton->setEnabled(false); - mLineEdit->setStyleSheet("background:#FFB3C8;"); acceptButton->setEnabled(false); } else if (mFileNameList.contains(pStr.trimmed(), Qt::CaseSensitive)) { acceptButton->setEnabled(true); - mLineEdit->setStyleSheet("background:#FFB3C8;"); acceptButton->setText(replaceText); } else { acceptButton->setEnabled(true); - mLineEdit->setStyleSheet("background:white;"); acceptButton->setText(acceptText); } } @@ -513,12 +507,14 @@ QVariant UBDocumentTreeModel::data(const QModelIndex &index, int role) const } if(role == Qt::BackgroundRole){ - if (isConstant(index)) { - return QBrush(0xD9DFEB); + if (isConstant(index)) + { + return QBrush(QApplication::palette().color(QPalette::AlternateBase)); } - if (mHighLighted.isValid() && index == mHighLighted) { - return QBrush(0x6682B5); + if (mHighLighted.isValid() && index.row() == mHighLighted.row()) + { + return QBrush(QApplication::palette().color(QPalette::Highlight)); } } @@ -564,7 +560,7 @@ QVariant UBDocumentTreeModel::data(const QModelIndex &index, int role) const break; case (Qt::ForegroundRole) : if (isConstant(index)) { - return QColor(Qt::darkGray); + return QColor(QApplication::palette().color(QPalette::WindowText)); } break; } @@ -684,109 +680,24 @@ QDateTime UBDocumentTreeModel::findCatalogCreationDate(UBDocumentTreeNode *node) QStringList UBDocumentTreeModel::mimeTypes() const { - QStringList types; - types << "text/uri-list" << "image/png" << "image/tiff" << "image/gif" << "image/jpeg"; + static const QStringList types{UBApplication::mimeTypeUniboardDocument}; 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()); - } - } - -#if defined(Q_OS_OSX) - #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) - if (QOperatingSystemVersion::current().majorVersion() == 10 && QOperatingSystemVersion::current().minorVersion() < 15) /* <= Mojave */ - mimeData->setUrls(urlList); - #endif -#else - mimeData->setUrls(urlList); -#endif - 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)); - std::shared_ptr 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 total = ubMime->items().size(); - - QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); - - foreach (UBMimeDataItem sourceItem, ubMime->items()) - { - std::shared_ptr fromProxy = sourceItem.documentProxy(); - int fromIndex = sourceItem.sceneIndex(); - int toIndex = targetDocProxy->pageCount(); - - auto fromDocument = UBDocument::getDocument(fromProxy); - fromDocument->copyPage(fromIndex, targetDocProxy, toIndex); - } - - QApplication::restoreOverrideCursor(); - - UBApplication::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(); + QModelIndexList indexList; - foreach (QModelIndex curIndex, incomingIndexes) + for (const auto& index : indexes) { - //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); - } + // only add indexes for first column + if (index.isValid() && index.column() == 0) + { + indexList.append(index); } } - Q_UNUSED(action) - Q_UNUSED(row) - Q_UNUSED(column) - Q_UNUSED(parent) - - return true; + return new UBDocumentTreeMimeData(indexList); } bool UBDocumentTreeModel::removeRows(int row, int count, const QModelIndex &parent) @@ -1448,6 +1359,37 @@ bool UBDocumentTreeModel::nodeLessThan(const UBDocumentTreeNode *firstIndex, con return firstIndex->nodeName() < secondIndex->nodeName(); } +void UBDocumentTreeModel::setHighLighted(const QModelIndex& newHighLighted) +{ + QModelIndex from; + QModelIndex to; + + if (mHighLighted.isValid()) + { + from = mHighLighted.siblingAtColumn(0); + to = mHighLighted.siblingAtColumn(1); + }; + + if (newHighLighted.isValid()) + { + if (!from.isValid()) + { + from = newHighLighted.siblingAtColumn(0); + } + + to = newHighLighted.siblingAtColumn(1); + } + + mHighLighted = newHighLighted; + + if (from.row() > to.row()) + { + std::swap(from, to); + } + + emit dataChanged(from, to, {Qt::BackgroundRole}); +} + UBDocumentTreeModel::~UBDocumentTreeModel() { delete mRootNode; @@ -1522,89 +1464,69 @@ void UBDocumentTreeView::mousePressEvent(QMouseEvent *event) void UBDocumentTreeView::dragEnterEvent(QDragEnterEvent *event) { - QTreeView::dragEnterEvent(event); - event->accept(); - event->acceptProposedAction(); + if (event->mimeData()->hasFormat(UBApplication::mimeTypeUniboardPage)) + { + // accept copying a page + event->setDropAction(Qt::CopyAction); + event->accept(); + } + else if (event->mimeData()->hasFormat(UBApplication::mimeTypeUniboardDocument)) + { + // accept moving a document + event->setDropAction(Qt::MoveAction); + event->accept(); + } } 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()); + baseModel()->setHighLighted(QModelIndex()); update(); } void UBDocumentTreeView::dragMoveEvent(QDragMoveEvent *event) { - QModelIndex index; - if (selectedIndexes().count() > 0) + if (event->mimeData()->hasFormat(UBApplication::mimeTypeUniboardPage)) { - index = selectedIndexes().first(); - } - #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) - QPoint eventPos = event->position().toPoint(); + QPoint eventPos = event->position().toPoint(); #else - QPoint eventPos = event->pos(); + QPoint eventPos = event->pos(); #endif - bool acceptIt = isAcceptable(index, indexAt(eventPos)); - - 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()); - } + UBDocumentTreeModel* docModel{baseModel()}; QModelIndex targetIndex = mapIndexToSource(indexAt(eventPos)); - if (!docModel || !docModel->isDocument(targetIndex) || docModel->inTrash(targetIndex)) { - event->ignore(); - event->setDropAction(Qt::IgnoreAction); + if (!targetIndex.isValid() || !docModel || !docModel->isDocument(targetIndex) || docModel->inTrash(targetIndex)) + { + event->ignore(visualRect(targetIndex)); if (docModel) { docModel->setHighLighted(QModelIndex()); } - - acceptIt = false; - } else { + } + else + { + event->setDropAction(Qt::CopyAction); + event->accept(visualRect(targetIndex)); docModel->setHighLighted(targetIndex); - acceptIt = true; } - updateIndexEnvirons(indexAt(eventPos)); } - QTreeView::dragMoveEvent(event); - - event->setAccepted(acceptIt); + else if (event->mimeData()->hasFormat(UBApplication::mimeTypeUniboardDocument)) + { + event->accept(); + QTreeView::dragMoveEvent(event); + } } 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()); - } + UBDocumentTreeModel* docModel{baseModel()}; #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) QPoint eventPos = event->position().toPoint(); @@ -1612,9 +1534,6 @@ void UBDocumentTreeView::dropEvent(QDropEvent *event) QPoint eventPos = event->pos(); #endif QModelIndex targetIndex = mapIndexToSource(indexAt(eventPos)); - QModelIndexList dropIndex = mapIndexesToSource(selectionModel()->selectedRows(0)); - - bool isUBPage = event->mimeData()->hasFormat(UBApplication::mimeTypeUniboardPage); //issue 1629 - NNE - 20131212 bool targetIsInTrash = docModel && (docModel->inTrash(targetIndex) || docModel->trashIndex() == targetIndex); @@ -1623,13 +1542,13 @@ void UBDocumentTreeView::dropEvent(QDropEvent *event) if (!targetIsInMyDocuments && !targetIsInTrash) return; - if (isUBPage) + if (event->mimeData()->hasFormat(UBApplication::mimeTypeUniboardPage)) { std::shared_ptr targetDocProxy = docModel->proxyData(targetIndex); auto targetDocument = UBDocument::getDocument(targetDocProxy); const UBMimeData *ubMime = qobject_cast (event->mimeData()); - if (!targetDocProxy || !ubMime || !ubMime->items().count()) { + if (!targetDocument || !ubMime || !ubMime->items().count()) { qDebug() << "an error ocured while parsing " << UBApplication::mimeTypeUniboardPage; QTreeView::dropEvent(event); return; @@ -1643,66 +1562,37 @@ void UBDocumentTreeView::dropEvent(QDropEvent *event) { std::shared_ptr fromProxy = sourceItem.documentProxy(); int fromIndex = sourceItem.sceneIndex(); - int toIndex = targetDocProxy->pageCount(); - - count++; + int toIndex = targetDocument->pageCount(); - UBApplication::showMessage(tr("Copying page %1/%2").arg(count).arg(total), true); - // FIXME code is similar to UBPersistenceManager::copyDocumentScene() - // PersistenceManaget works with UBForeignObjectHandler, while we here ask the scene for relative dependencies - // one is at file level, the other at scene level. I prefer scene level, as it avoids another SVG parser. - // Implement one single UBDocument::copyPage + UBApplication::showMessage(tr("Copying page %1/%2").arg(++count).arg(total), true); - // TODO UB 4.x Move following code to some controller class - std::shared_ptr scene = UBPersistenceManager::persistenceManager()->loadDocumentScene(sourceItem.documentProxy(), fromIndex); - if (scene) - { - std::shared_ptr sceneClone = scene->sceneDeepCopy(); - - std::shared_ptr targetDocProxy = docModel->proxyForIndex(targetIndex); - - foreach (QUrl relativeFile, scene->relativeDependencies()) - { - QString source = scene->document()->persistencePath() + "/" + relativeFile.toString(); - QString target = targetDocProxy->persistencePath() + "/" + relativeFile.toString(); - - QString sourceDecoded = scene->document()->persistencePath() + "/" + relativeFile.toString(QUrl::DecodeReserved); - QString targetDecoded = targetDocProxy->persistencePath() + "/" + relativeFile.toString(QUrl::DecodeReserved); - - if(QFileInfo(source).isDir()) - UBFileSystemUtils::copyDir(source,target); - else{ - QFileInfo fi(targetDecoded); - QDir d = fi.dir(); - d.mkpath(d.absolutePath()); - QFile::copy(sourceDecoded, targetDecoded); - } - } - - targetDocument->insertPage(sceneClone, toIndex); - } - - QApplication::restoreOverrideCursor(); - - docModel->setHighLighted(QModelIndex()); + auto sourceDocument = UBDocument::getDocument(fromProxy); + sourceDocument->copyPage(fromIndex, targetDocument, toIndex); } + QApplication::restoreOverrideCursor(); + docModel->setHighLighted(QModelIndex()); + UBApplication::showMessage(tr("%1 pages copied", "", total).arg(total), false); } - else + else if (event->mimeData()->hasFormat(UBApplication::mimeTypeUniboardDocument)) { - if(targetIsInTrash) + const auto* documentMimeData = dynamic_cast(event->mimeData()); + QModelIndexList dropIndex = documentMimeData->indexes(); + + if (targetIsInTrash) { UBApplication::documentController->moveIndexesToTrash(dropIndex, docModel); - }else{ + } + else + { docModel->moveIndexes(dropIndex, targetIndex); } } + UBSortFilterProxyModel *proxy = dynamic_cast(model()); expand(proxy->mapFromSource(targetIndex)); - QTreeView::dropEvent(event); - UBApplication::documentController->pageSelectionChanged(); } @@ -1716,6 +1606,12 @@ void UBDocumentTreeView::rowsAboutToBeRemoved(const QModelIndex &parent, int sta QTreeView::rowsAboutToBeRemoved(parent, start, end); } +UBDocumentTreeModel* UBDocumentTreeView::baseModel() const +{ + UBSortFilterProxyModel *proxy = dynamic_cast(model()); + return dynamic_cast(proxy ? proxy->sourceModel() : model()); +} + bool UBDocumentTreeView::isAcceptable(const QModelIndex &dragIndex, const QModelIndex &atIndex) { QModelIndex dragIndexSource = mapIndexToSource(dragIndex); @@ -1732,14 +1628,6 @@ Qt::DropAction UBDocumentTreeView::acceptableAction(const QModelIndex &dragIndex return Qt::MoveAction; } -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) { @@ -1751,23 +1639,6 @@ QModelIndex UBDocumentTreeView::mapIndexToSource(const QModelIndex &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) @@ -1805,7 +1676,7 @@ void UBDocumentTreeItemDelegate::processChangedText(const QString &str) const } else { - editor->setStyleSheet("background-color: #FFFFFF;"); + editor->setStyleSheet(""); } } } @@ -1926,6 +1797,8 @@ UBDocumentController::~UBDocumentController() void UBDocumentController::createNewDocument() { + mDocumentUI->filterText->clear(); + UBPersistenceManager *pManager = UBPersistenceManager::persistenceManager(); UBDocumentTreeModel *docModel = pManager->mDocumentTreeStructureModel; QModelIndex selectedIndex = firstSelectedTreeIndex(); @@ -1984,6 +1857,8 @@ void UBDocumentController::selectDocument(std::shared_ptr proxy void UBDocumentController::createNewDocumentGroup() { + mDocumentUI->filterText->clear(); + UBPersistenceManager *pManager = UBPersistenceManager::persistenceManager(); UBDocumentTreeModel *docModel = pManager->mDocumentTreeStructureModel; QModelIndex selectedIndex = firstSelectedTreeIndex(); @@ -2267,6 +2142,7 @@ void UBDocumentController::setupViews() mDocumentUI->documentTreeView->setItemDelegate(new UBDocumentTreeItemDelegate(this)); mDocumentUI->documentTreeView->setDragEnabled(true); mDocumentUI->documentTreeView->setAcceptDrops(true); + mDocumentUI->documentTreeView->setDefaultDropAction(Qt::MoveAction); mDocumentUI->documentTreeView->viewport()->setAcceptDrops(true); mDocumentUI->documentTreeView->setDropIndicatorShown(true); mDocumentUI->documentTreeView->header()->setStretchLastSection(false); @@ -2288,6 +2164,7 @@ void UBDocumentController::setupViews() connect(mDocumentUI->sortKind, SIGNAL(activated(int)), this, SLOT(onSortKindChanged(int))); connect(mDocumentUI->sortOrder, SIGNAL(toggled(bool)), this, SLOT(onSortOrderChanged(bool))); + connect(mDocumentUI->filterText, &QLineEdit::textChanged, this, &UBDocumentController::onFilterTextChanged); connect(mDocumentUI->splitter, SIGNAL(splitterMoved(int,int)), this, SLOT(onSplitterMoved(int, int))); @@ -2306,8 +2183,6 @@ void UBDocumentController::setupViews() mDocumentUI->thumbnailWidget->setDocument(UBDocument::getDocument(proxy)); }); - mDocumentUI->thumbnailWidget->setBackgroundBrush(UBSettings::documentViewLightColor); - #ifdef Q_WS_MACX mMessageWindow = new UBMessageWindow(NULL); #else @@ -2392,6 +2267,21 @@ void UBDocumentController::onSortKindChanged(int index) UBSettings::settings()->documentSortKind->setInt(index); } +void UBDocumentController::onFilterTextChanged(const QString& filter) +{ + mSortFilterProxyModel->setFilterCaseSensitivity(Qt::CaseSensitivity::CaseInsensitive); + mSortFilterProxyModel->setFilterRegularExpression(filter); + + if (filter.isEmpty()) + { + collapseAll(); + } + else + { + expandAll(); + } +} + void UBDocumentController::onSplitterMoved(int size, int index) { Q_UNUSED(index); @@ -2437,6 +2327,8 @@ void UBDocumentController::show() if(!mToolsPalette) setupPalettes(); + + mDocumentUI->filterText->clear(); } @@ -3108,6 +3000,8 @@ void UBDocumentController::documentZoomSliderValueChanged (int value) void UBDocumentController::importFile() { + mDocumentUI->filterText->clear(); + UBDocumentManager *docManager = UBDocumentManager::documentManager(); QString defaultPath = UBSettings::settings()->lastImportFilePath->get().toString(); @@ -3138,7 +3032,7 @@ void UBDocumentController::importFile() if (filePath.length() > 0) { - std::shared_ptr createdDocument = nullptr; + std::shared_ptr createdDocument = nullptr; QApplication::processEvents(); QFile selectedFile(filePath); @@ -3158,7 +3052,7 @@ void UBDocumentController::importFile() createdDocument = docManager->importFile(selectedFile, groupName); if (createdDocument && !multipleFilesImported) { - selectDocument(createdDocument, true, true, true); + selectDocument(createdDocument->proxy(), true, true, true); pageSelectionChanged(); } else { @@ -3397,7 +3291,7 @@ void UBDocumentController::addToDocument() mBoardController->addScene(pageInfoList.at(i).first, pageInfoList.at(i).second, false); } - int newActiveSceneIndex = selectedItems.count() == mBoardController->selectedDocument()->pageCount() ? 0 : oldActiveSceneIndex + 1; + int newActiveSceneIndex = selectedItems.count() == mBoardController->activeDocument()->pageCount() ? 0 : oldActiveSceneIndex + 1; mDocumentUI->thumbnailWidget->selectItemAt(newActiveSceneIndex, false); selectDocument(mBoardController->selectedDocument()); QDateTime now = QDateTime::currentDateTime(); @@ -3428,7 +3322,7 @@ bool UBDocumentController::isOKToOpenDocument(std::shared_ptr p 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.8") || docVersion.startsWith("4.9")) { // Invoke widget upgrader widgetUpgradeAdaptor.upgradeWidgets(proxy); @@ -3624,10 +3518,6 @@ void UBDocumentController::updateActions() QModelIndex selectedIndex = firstSelectedTreeIndex(); std::shared_ptr selectedProxy = docModel->proxyData(selectedIndex); - int pageCount = -1; - if (selectedProxy) { - pageCount = selectedProxy->pageCount(); - } bool pageSelected = false; bool groupSelected = false; @@ -3670,7 +3560,7 @@ void UBDocumentController::updateActions() } else if (pageSelected) { QList selection = mDocumentUI->thumbnailWidget->selectedItems(); - if(pageCount == 1) { + if(mDocumentUI->thumbnailWidget->document()->pageCount() == 1) { mMainWindow->actionDuplicate->setEnabled(!trashSelected && pageCanBeDuplicated(UBDocumentContainer::pageFromSceneIndex(0))); } else { @@ -3846,7 +3736,7 @@ bool UBDocumentController::pageCanBeMovedUp(int page) bool UBDocumentController::pageCanBeMovedDown(int page) { - return page < selectedDocument()->pageCount() - 1; + return page < activeDocument()->pageCount() - 1; } bool UBDocumentController::pageCanBeDuplicated(int page) @@ -3910,7 +3800,7 @@ bool UBDocumentController::everySceneSelected() const UBThumbnail* p = dynamic_cast(selection.at(0)); if (p) { - return (selection.count() == mDocumentUI->thumbnailWidget->document()->proxy()->pageCount()); + return (selection.count() == mDocumentUI->thumbnailWidget->document()->pageCount()); } } return false; @@ -3924,7 +3814,7 @@ bool UBDocumentController::firstAndOnlySceneSelected() const UBThumbnail* p = dynamic_cast(selection.at(i)); if (p) { - int pageCount = mDocumentUI->thumbnailWidget->document()->proxy()->pageCount(); + int pageCount = mDocumentUI->thumbnailWidget->document()->pageCount(); if (pageCount > 1) //not the only scene { return false; @@ -3945,6 +3835,8 @@ bool UBDocumentController::firstAndOnlySceneSelected() const void UBDocumentController::createNewDocumentInUntitledFolder() { + mDocumentUI->filterText->clear(); + UBPersistenceManager *pManager = UBPersistenceManager::persistenceManager(); UBDocumentTreeModel *docModel = pManager->mDocumentTreeStructureModel; @@ -3995,3 +3887,14 @@ void UBDocumentController::clearThumbnailsSelection() mDocumentUI->thumbnailWidget->clearSelection(); pageSelectionChanged(); } + +UBDocumentTreeMimeData::UBDocumentTreeMimeData(const QModelIndexList& pIndexes) + : mIndexes{pIndexes} +{ + setData(UBApplication::mimeTypeUniboardDocument, {}); +} + +QModelIndexList UBDocumentTreeMimeData::indexes() const +{ + return mIndexes; +} diff --git a/src/document/UBDocumentController.h b/src/document/UBDocumentController.h index bd094bbb0..bb6d47b1b 100644 --- a/src/document/UBDocumentController.h +++ b/src/document/UBDocumentController.h @@ -192,14 +192,12 @@ class UBDocumentTreeModel : public QAbstractItemModel { 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); bool containsDocuments(const QModelIndex& index); 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); @@ -241,7 +239,7 @@ class UBDocumentTreeModel : public QAbstractItemModel { QPersistentModelIndex untitledDocumentsIndex() const {return mMyDocuments;} UBDocumentTreeNode *nodeFromIndex(const QModelIndex &pIndex) const; static bool nodeLessThan(const UBDocumentTreeNode *firstIndex, const UBDocumentTreeNode *secondIndex); - void setHighLighted(const QModelIndex &newHighLighted) {mHighLighted = newHighLighted;} + void setHighLighted(const QModelIndex &newHighLighted); QModelIndex highLighted() {return mHighLighted;} std::shared_ptr findDocumentByFolderName(QString folderName) const; std::shared_ptr findDocumentByFolderName(UBDocumentTreeNode* node, QString folderName) const; @@ -291,12 +289,12 @@ class UBDocumentTreeMimeData : public QMimeData { Q_OBJECT - public: - QList indexes() const {return mIndexes;} - void setIndexes (const QList &pIndexes) {mIndexes = pIndexes;} +public: + UBDocumentTreeMimeData(const QModelIndexList& pIndexes); + QModelIndexList indexes() const; - private: - QList mIndexes; +private: + QModelIndexList mIndexes; }; class UBDocumentTreeView : public QTreeView @@ -308,7 +306,6 @@ class UBDocumentTreeView : public QTreeView //N/C - NNE - 20140404 QModelIndex mapIndexToSource(const QModelIndex &index); - QModelIndexList mapIndexesToSource(const QModelIndexList &indexes); public slots: void setSelectedAndExpanded(const QModelIndex &pIndex, bool pExpand = true, bool pEdit = false); @@ -329,8 +326,8 @@ public slots: private: bool isAcceptable(const QModelIndex &dragIndex, const QModelIndex &atIndex); + UBDocumentTreeModel* baseModel() const; Qt::DropAction acceptableAction(const QModelIndex &dragIndex, const QModelIndex &atIndex); - void updateIndexEnvirons(const QModelIndex &index); }; class UBValidator : public QValidator @@ -506,6 +503,7 @@ class UBDocumentController : public UBDocumentContainer //N/C - NNE - 20140403 void onSortKindChanged(int index); void onSortOrderChanged(bool order); + void onFilterTextChanged(const QString& filter); void onSplitterMoved(int size, int index); void collapseAll(); void expandAll(); diff --git a/src/document/UBDocumentProxy.cpp b/src/document/UBDocumentProxy.cpp index 9c4b64d7e..958ce45f6 100644 --- a/src/document/UBDocumentProxy.cpp +++ b/src/document/UBDocumentProxy.cpp @@ -40,8 +40,7 @@ #include "adaptors/UBMetadataDcSubsetAdaptor.h" UBDocumentProxy::UBDocumentProxy() - : mPageCount(0) - , mPageDpi(0) + : mPageDpi(0) , mPersistencePath("") , mDocumentDateLittleEndian("") , mDocumentUpdatedAtLittleEndian("") @@ -54,8 +53,7 @@ UBDocumentProxy::UBDocumentProxy() UBDocumentProxy::UBDocumentProxy(const QString& pPersistancePath) - : mPageCount(0) - , mPageDpi(0) + : mPageDpi(0) , mNeedsCleanup(true) , mLastVisitedIndex(0) , mIsInFavoriteList(false) @@ -95,7 +93,6 @@ std::shared_ptr UBDocumentProxy::deepCopy() const copy->mPersistencePath = QString(mPersistencePath); copy->mMetaDatas = QMap(mMetaDatas); - copy->mPageCount = mPageCount; copy->mLastVisitedIndex = mLastVisitedIndex; copy->mIsInFavoriteList = mIsInFavoriteList; @@ -122,17 +119,6 @@ void UBDocumentProxy::setIsInFavoristeList(bool isInFavoriteList) mIsInFavoriteList = isInFavoriteList; } -int UBDocumentProxy::pageCount() -{ - return mPageCount; -} - - -void UBDocumentProxy::setPageCount(int pPageCount) -{ - mPageCount = pPageCount; -} - int UBDocumentProxy::pageDpi() { return mPageDpi; @@ -160,36 +146,6 @@ bool UBDocumentProxy::testAndResetCleanupNeeded() return tmp; } -int UBDocumentProxy::incPageCount() -{ - if (mPageCount <= 0) - { - mPageCount = 1; - } - else - { - mPageCount++; - } - - return mPageCount; - -} - - -int UBDocumentProxy::decPageCount() -{ - mPageCount --; - - if (mPageCount < 0) - { - mPageCount = 0; - } - - mNeedsCleanup = true; - - return mPageCount; -} - QString UBDocumentProxy::persistencePath() const { return mPersistencePath; diff --git a/src/document/UBDocumentProxy.h b/src/document/UBDocumentProxy.h index dc12644ca..5d8603937 100644 --- a/src/document/UBDocumentProxy.h +++ b/src/document/UBDocumentProxy.h @@ -96,8 +96,6 @@ class UBDocumentProxy QUuid uuid() const; void setUuid(const QUuid& uuid); - int pageCount(); - int pageDpi(); void setPageDpi(int dpi); @@ -112,11 +110,6 @@ class UBDocumentProxy bool isInFavoriteList() const; void setIsInFavoristeList(bool isInFavoristeList); - protected: - void setPageCount(int pPageCount); - int incPageCount(); - int decPageCount(); - private: void init(); @@ -128,8 +121,6 @@ class UBDocumentProxy QMap mMetaDatas; - int mPageCount; - int mPageDpi; QMap mWidgetCompatibility; diff --git a/src/document/UBDocumentToc.cpp b/src/document/UBDocumentToc.cpp new file mode 100644 index 000000000..5db2e147a --- /dev/null +++ b/src/document/UBDocumentToc.cpp @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2015-2025 Département de l'Instruction Publique (DIP-SEM) + * + * This file is part of OpenBoard. + * + * OpenBoard 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, + * 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 OpenBoard. If not, see . + */ + + +#include "UBDocumentToc.h" + +#include + +#include "document/UBTocSerializer.h" + +static const QString UUID{"uuid"}; +static const QString PAGE_ID{"id"}; +static const QString ASSETS("assets"); + +UBDocumentToc::UBDocumentToc(const QString& documentPath) + : mDocumentPath{documentPath} +{ +} + +UBDocumentToc::UBDocumentToc(const UBDocumentToc& other, const QString& documentPath) + : mDocumentPath{documentPath} + , mVersion{other.mVersion} + , mToc{other.mToc} +{ +} + +QVersionNumber UBDocumentToc::version() const +{ + return mVersion; +} + +int UBDocumentToc::pageCount() const +{ + return mToc.count(); +} + +int UBDocumentToc::insert(int index) +{ + if (index >= 0 && index <= mToc.count()) + { + int pageId = nextAvailablePageId(); + mToc.insert(index, {{PAGE_ID, pageId}}); + mModified = true; + return pageId; + } + + return -1; +} + +void UBDocumentToc::move(int fromIndex, int toIndex) +{ + if (isValidIndex(fromIndex) && isValidIndex(toIndex)) + { + auto entry = mToc.at(fromIndex); + mToc.removeAt(fromIndex); + mToc.insert(toIndex, entry); + mModified = true; + } +} + +void UBDocumentToc::remove(int index) +{ + if (isValidIndex(index)) + { + mToc.remove(index); + mModified = true; + } +} + +QUuid UBDocumentToc::uuid(int index) const +{ + if (isValidIndex(index)) + { + return mToc.at(index).value(UUID).toUuid(); + } + + return {}; +} + +void UBDocumentToc::setUuid(int index, const QUuid& uuid) +{ + if (index < 0) + { + return; + } + + assureSize(index); + mToc[index][UUID] = uuid; + mModified = true; +} + +int UBDocumentToc::findUuid(const QUuid& sceneUuid) const +{ + for (int i = 0; i < mToc.size(); ++i) + { + if (uuid(i) == sceneUuid) + { + return i; + } + } + + return -1; +} + +int UBDocumentToc::pageId(int index) const +{ + if (isValidIndex(index)) + { + return mToc.at(index).value(PAGE_ID).toInt(); + } + + return -1; +} + +void UBDocumentToc::setPageId(int index, int pageId) +{ + if (index < 0) + { + return; + } + + assureSize(index); + mToc[index][PAGE_ID] = pageId; + mModified = true; +} + +QStringList UBDocumentToc::assets(int index) const +{ + if (isValidIndex(index)) + { + return mToc.at(index).value(ASSETS).toStringList(); + } + + return {}; +} + +void UBDocumentToc::setAssets(int index, const QStringList& assets) +{ + if (index < 0) + { + return; + } + + assureSize(index); + mToc[index][ASSETS] = assets; + mModified = true; +} + +void UBDocumentToc::unsetAssets(int index) +{ + if (isValidIndex(index)) + { + mToc[index].remove(ASSETS); + } +} + +bool UBDocumentToc::hasAssetsEntry(int index) const +{ + return isValidIndex(index) && mToc.at(index).contains(ASSETS); +} + +bool UBDocumentToc::load() +{ + UBTocJsonSerializer serializer(mDocumentPath); + const auto ok = serializer.load(mVersion, mToc); + + for (const auto& entry : mToc) + { + mNextAvailablePageId = std::max(mNextAvailablePageId, entry[PAGE_ID].toInt() + 1); + } + + mModified = false; + return ok; +} + +void UBDocumentToc::save() +{ + if (mModified) + { + UBTocJsonSerializer serializer(mDocumentPath); + serializer.save(mVersion, mToc); + mModified = false; + } +} + +int UBDocumentToc::nextAvailablePageId() +{ + return mNextAvailablePageId++; +} + +bool UBDocumentToc::isValidIndex(int index) const +{ + return index >= 0 && index < mToc.count(); +} + +void UBDocumentToc::assureSize(int index) +{ + if (index >= mToc.count()) + { + mToc.resize(index + 1); + } +} diff --git a/src/document/UBDocumentToc.h b/src/document/UBDocumentToc.h new file mode 100644 index 000000000..e5ce1f9f4 --- /dev/null +++ b/src/document/UBDocumentToc.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2015-2025 Département de l'Instruction Publique (DIP-SEM) + * + * This file is part of OpenBoard. + * + * OpenBoard 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, + * 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 OpenBoard. If not, see . + */ + + +#pragma once + +#include +#include +#include +#include + + +static const QVersionNumber DOCUMENT_TOC_VERSION{1, 0}; + +class UBDocumentToc +{ +public: + UBDocumentToc(const QString& documentPath); + UBDocumentToc(const UBDocumentToc& other, const QString& documentPath); + + QVersionNumber version() const; + + // size + int pageCount() const; + + // insert, move and remove + int insert(int index); + void move(int fromIndex, int toIndex); + void remove(int index); + + // access values + QUuid uuid(int index) const; + void setUuid(int index, const QUuid& uuid); + int findUuid(const QUuid& sceneUuid) const; + + int pageId(int index) const; + void setPageId(int index, int pageId); + + QStringList assets(int index) const; + void setAssets(int index, const QStringList& assets); + void unsetAssets(int index); + bool hasAssetsEntry(int index) const; + + // load and save + bool load(); + void save(); + + // next available pageId + int nextAvailablePageId(); + bool isValidIndex(int index) const; + +private: + void assureSize(int index); + +private: + const QString mDocumentPath; + QVersionNumber mVersion{DOCUMENT_TOC_VERSION}; + QVector mToc; + int mNextAvailablePageId{0}; + bool mModified{false}; +}; diff --git a/src/document/UBSortFilterProxyModel.cpp b/src/document/UBSortFilterProxyModel.cpp index b46465fba..7c29cb883 100644 --- a/src/document/UBSortFilterProxyModel.cpp +++ b/src/document/UBSortFilterProxyModel.cpp @@ -6,6 +6,8 @@ UBSortFilterProxyModel::UBSortFilterProxyModel(): { setDynamicSortFilter(false); setSortCaseSensitivity(Qt::CaseInsensitive); + setRecursiveFilteringEnabled(true); + setAutoAcceptChildRows(true); } bool UBSortFilterProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const @@ -23,3 +25,9 @@ bool UBSortFilterProxyModel::lessThan(const QModelIndex &left, const QModelIndex return QSortFilterProxyModel::lessThan(left, right); } + +bool UBSortFilterProxyModel::filterAcceptsRow(int sourceRow, + const QModelIndex &sourceParent) const +{ + return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent); +} diff --git a/src/document/UBSortFilterProxyModel.h b/src/document/UBSortFilterProxyModel.h index 97afba9f6..c4fef8905 100644 --- a/src/document/UBSortFilterProxyModel.h +++ b/src/document/UBSortFilterProxyModel.h @@ -2,14 +2,14 @@ #define UBSORTFILTERPROXYMODEL_H #include -#include "core/UBPersistenceManager.h" class UBSortFilterProxyModel : public QSortFilterProxyModel { public: UBSortFilterProxyModel(); - bool lessThan(const QModelIndex &left, const QModelIndex &right) const; +protected: + bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; }; - #endif // UBSORTFILTERPROXYMODEL_H diff --git a/src/document/UBTocSerializer.cpp b/src/document/UBTocSerializer.cpp new file mode 100644 index 000000000..5977e563f --- /dev/null +++ b/src/document/UBTocSerializer.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2015-2025 Département de l'Instruction Publique (DIP-SEM) + * + * This file is part of OpenBoard. + * + * OpenBoard 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, + * 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 OpenBoard. If not, see . + */ + + +#include "UBTocSerializer.h" + +#include +#include +#include +#include + +static const QString VERSION{"version"}; +static const QString PAGES("pages"); + +UBTocSerializer::UBTocSerializer(QString path) + : mPath{path} +{ +} + +UBTocJsonSerializer::UBTocJsonSerializer(QString filename) + : UBTocSerializer{filename} +{ +} + +bool UBTocJsonSerializer::load(QVersionNumber& version, QVector& toc) +{ + if (!toc.isEmpty()) + { + qWarning() << "Refused to load non-empty TOC"; + return false; + } + + QFile file{mPath + "/toc.json"}; + + if (!file.open(QFile::ReadOnly)) + { + qDebug() << "Cannot load TOC: Cannot open file" << file.fileName(); + return false; + } + + const auto json = file.readAll(); + file.close(); + QJsonParseError error; + const auto jsonDoc = QJsonDocument::fromJson(json, &error); + + if (error.error != QJsonParseError::NoError) + { + qWarning() << "Parse error reading TOC:" << error.errorString() << "at" << error.offset; + return false; + } + + if (!jsonDoc.isObject()) + { + qWarning() << "Error reading TOC: JSON document is not an object"; + return false; + } + + version = QVersionNumber::fromString(jsonDoc.object().value(VERSION).toString()); + + const auto pages = jsonDoc.object().value(PAGES); + + if (!pages.isArray()) + { + qWarning() << "Error reading TOC: pages is not an array"; + return false; + } + + int index = 0; + + toc.resize(pages.toArray().size()); + + for (const auto entry : pages.toArray()) + { + if (!entry.isObject()) + { + qWarning() << "Error reading TOC: JSON entry is not an object"; + return false; + } + + toc[index++] = entry.toObject().toVariantMap(); + } + + return true; +} + +bool UBTocJsonSerializer::save(const QVersionNumber& version, const QVector& toc) +{ + QJsonArray list; + + for (const auto entry : toc) + { + list.append(QJsonObject::fromVariantMap(entry)); + } + + QJsonObject document; + document[VERSION] = version.toString(); + document[PAGES] = list; + + QJsonDocument doc{document}; + auto json = doc.toJson(QJsonDocument::Indented); + + // reduce indentation to one space + json.replace(" ", " "); + + QFile file{mPath + "/toc.json"}; + + if (!file.open(QFile::WriteOnly)) + { + qWarning() << "Cannot save TOC: Cannot open file" << file.fileName(); + return false; + } + + file.write(json); + file.close(); + return true; +} diff --git a/src/document/UBTocSerializer.h b/src/document/UBTocSerializer.h new file mode 100644 index 000000000..9d7d9d1e2 --- /dev/null +++ b/src/document/UBTocSerializer.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2015-2025 Département de l'Instruction Publique (DIP-SEM) + * + * This file is part of OpenBoard. + * + * OpenBoard 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, + * 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 OpenBoard. If not, see . + */ + + +#pragma once + +#include +#include +#include +#include + + +class UBTocSerializer +{ +public: + UBTocSerializer(QString path); + virtual ~UBTocSerializer() = default; + + virtual bool load(QVersionNumber& version, QVector& toc) = 0; + virtual bool save(const QVersionNumber& version, const QVector& toc) = 0; + +protected: + QString mPath; +}; + + +class UBTocJsonSerializer : public UBTocSerializer +{ +public: + UBTocJsonSerializer(QString filename); + virtual ~UBTocJsonSerializer() = default; + + virtual bool load(QVersionNumber& version, QVector& toc) override; + virtual bool save(const QVersionNumber& version, const QVector& toc) override; +}; diff --git a/src/document/document.pri b/src/document/document.pri index 5815df9f3..68d165a7d 100644 --- a/src/document/document.pri +++ b/src/document/document.pri @@ -3,11 +3,14 @@ HEADERS += \ src/document/UBDocumentContainer.h \ src/document/UBDocumentController.h \ src/document/UBDocumentProxy.h \ - src/document/UBSortFilterProxyModel.h + src/document/UBDocumentToc.h \ + src/document/UBSortFilterProxyModel.h \ + src/document/UBTocSerializer.h SOURCES += \ src/document/UBDocument.cpp \ src/document/UBDocumentContainer.cpp \ src/document/UBDocumentController.cpp \ src/document/UBDocumentProxy.cpp \ - src/document/UBSortFilterProxyModel.cpp - + src/document/UBDocumentToc.cpp \ + src/document/UBSortFilterProxyModel.cpp \ + src/document/UBTocSerializer.cpp diff --git a/src/domain/CMakeLists.txt b/src/domain/CMakeLists.txt index 15bcab6b5..d5e687cbd 100644 --- a/src/domain/CMakeLists.txt +++ b/src/domain/CMakeLists.txt @@ -45,6 +45,8 @@ target_sources(${PROJECT_NAME} PRIVATE UBGraphicsWidgetItemDelegate.h UBItem.cpp UBItem.h + UBMediaAssetItem.cpp + UBMediaAssetItem.h UBPageSizeUndoCommand.cpp UBPageSizeUndoCommand.h UBResizableGraphicsItem.cpp diff --git a/src/domain/UBGraphicsGroupContainerItem.cpp b/src/domain/UBGraphicsGroupContainerItem.cpp index 974c10101..d19de3ade 100644 --- a/src/domain/UBGraphicsGroupContainerItem.cpp +++ b/src/domain/UBGraphicsGroupContainerItem.cpp @@ -245,12 +245,6 @@ void UBGraphicsGroupContainerItem::copyItemParameters(UBItem *copy) const } } -void UBGraphicsGroupContainerItem::setUuid(const QUuid &pUuid) -{ - UBItem::setUuid(pUuid); - setData(UBGraphicsItemData::ItemUuid, QVariant(pUuid)); //store item uuid inside the QGraphicsItem to fast operations with Items on the scene -} - void UBGraphicsGroupContainerItem::destroy(bool canUndo) { foreach (QGraphicsItem *item, childItems()) { @@ -262,18 +256,6 @@ void UBGraphicsGroupContainerItem::destroy(bool canUndo) { remove(canUndo); } -void UBGraphicsGroupContainerItem::clearSource() -{ - foreach(QGraphicsItem *child, childItems()) - { - UBGraphicsItem *item = dynamic_cast(child); - if (item) - { - item->clearSource(); - } - } -} - void UBGraphicsGroupContainerItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { if (Delegate()->mousePressEvent(event)) { diff --git a/src/domain/UBGraphicsGroupContainerItem.h b/src/domain/UBGraphicsGroupContainerItem.h index 1c99e4ba4..4e7047fe0 100644 --- a/src/domain/UBGraphicsGroupContainerItem.h +++ b/src/domain/UBGraphicsGroupContainerItem.h @@ -63,11 +63,8 @@ class UBGraphicsGroupContainerItem : public QGraphicsItem, public UBItem, public return Type; } - virtual void setUuid(const QUuid &pUuid); void destroy(bool canUndo = true); - virtual void clearSource(); - protected: virtual void mousePressEvent(QGraphicsSceneMouseEvent *event); virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event); diff --git a/src/domain/UBGraphicsItemDelegate.cpp b/src/domain/UBGraphicsItemDelegate.cpp index 764861579..e4193c836 100644 --- a/src/domain/UBGraphicsItemDelegate.cpp +++ b/src/domain/UBGraphicsItemDelegate.cpp @@ -719,13 +719,7 @@ void UBGraphicsItemDelegate::setAsBackground() void UBGraphicsItemDelegate::gotoContentSource() { - UBItem* item = dynamic_cast(mDelegated); - - if(item && !item->sourceUrl().isEmpty()) - { - UBApplication::applicationController->showInternet(); - UBApplication::webController->loadUrl(item->sourceUrl()); - } + // only used in UBGraphicsWidgetItemDelegate } void UBGraphicsItemDelegate::startUndoStep() @@ -1462,7 +1456,7 @@ void DelegateMediaControl::paint(QPainter *painter, int radius = frameWidth-clearance; QRectF r(position - radius, clearance+mSeecAreaBorderHeight, radius * 2, radius * 2); - painter->setBrush(UBSettings::documentViewLightColor); + painter->setBrush(UBSettings::documentThumbnailsViewColor); painter->setPen(Qt::black); painter->drawEllipse(r); } diff --git a/src/domain/UBGraphicsMediaItem.cpp b/src/domain/UBGraphicsMediaItem.cpp index 8b1c7b112..edf39db9d 100644 --- a/src/domain/UBGraphicsMediaItem.cpp +++ b/src/domain/UBGraphicsMediaItem.cpp @@ -34,8 +34,10 @@ #include "UBGraphicsDelegateFrame.h" #include "document/UBDocumentProxy.h" #include "core/UBApplication.h" +#include "core/UBPersistenceManager.h" #include "board/UBBoardController.h" #include "core/memcheck.h" +#include "frameworks/UBFileSystemUtils.h" #include @@ -137,6 +139,13 @@ UBGraphicsAudioItem::UBGraphicsAudioItem(const QUrl &pMediaFileUrl, QGraphicsIte #endif } +QList UBGraphicsAudioItem::mediaAssets() const +{ + const QString completeFileName = QFileInfo(mediaFileUrl().toLocalFile()).fileName(); + const QString path = UBPersistenceManager::audioDirectory + "/"; + return {path + completeFileName}; +} + UBGraphicsVideoItem::UBGraphicsVideoItem(const QUrl &pMediaFileUrl, QGraphicsItem *parent) :UBGraphicsMediaItem(pMediaFileUrl, parent) { @@ -192,6 +201,13 @@ UBGraphicsVideoItem::UBGraphicsVideoItem(const QUrl &pMediaFileUrl, QGraphicsIte update(); } +QList UBGraphicsVideoItem::mediaAssets() const +{ + const QString completeFileName = QFileInfo(mediaFileUrl().toLocalFile()).fileName(); + const QString path = UBPersistenceManager::videoDirectory + "/"; + return {path + completeFileName}; +} + UBGraphicsMediaItem::~UBGraphicsMediaItem() { } @@ -311,12 +327,6 @@ void UBGraphicsMediaItem::setMinimumSize(const QSize& size) this->setSize(width, height); } -void UBGraphicsMediaItem::setUuid(const QUuid &pUuid) -{ - UBItem::setUuid(pUuid); - setData(UBGraphicsItemData::ItemUuid, QVariant(pUuid)); -} - void UBGraphicsMediaItem::setMediaFileUrl(QUrl url) { mMediaFileUrl = url; @@ -344,22 +354,6 @@ void UBGraphicsMediaItem::setSelected(bool selected) QGraphicsRectItem::setSelected(selected); } -void UBGraphicsMediaItem::setSourceUrl(const QUrl &pSourceUrl) -{ - UBItem::setSourceUrl(pSourceUrl); -} - -void UBGraphicsMediaItem::clearSource() -{ - QString path = mediaFileUrl().toLocalFile(); - //if path is absolute clean duplicated path string - if (!path.contains(UBApplication::boardController->selectedDocument()->persistencePath())) - path = UBApplication::boardController->selectedDocument()->persistencePath() + "/" + path; - - if (!UBFileSystemUtils::deleteFile(path)) - qDebug() << "cannot delete file: " << path; -} - void UBGraphicsMediaItem::toggleMute() { mMuted = !mMuted; @@ -524,7 +518,6 @@ void UBGraphicsMediaItem::copyItemParameters(UBItem *copy) const cp->setData(UBGraphicsItemData::ItemLocked, this->data(UBGraphicsItemData::ItemLocked)); cp->setData(UBGraphicsItemData::ItemIsHiddenOnDisplay, this->data(UBGraphicsItemData::ItemIsHiddenOnDisplay)); cp->setData(UBGraphicsItemData::ItemOwnZValue, this->data(UBGraphicsItemData::ItemOwnZValue)); - cp->setSourceUrl(this->sourceUrl()); cp->setSize(rect().width(), rect().height()); cp->setZValue(this->zValue()); @@ -572,6 +565,11 @@ QRectF UBGraphicsMediaItem::boundingRect() const return rect(); } +void UBGraphicsMediaItem::setMediaAsset(const QString& documentPath, const QString& mediaAsset) +{ + setMediaFileUrl(QUrl::fromLocalFile(documentPath + "/" + mediaAsset)); +} + void UBGraphicsMediaItem::setSize(int width, int height) { QRectF r = rect(); diff --git a/src/domain/UBGraphicsMediaItem.h b/src/domain/UBGraphicsMediaItem.h index 7223728a7..7b2d2ea72 100644 --- a/src/domain/UBGraphicsMediaItem.h +++ b/src/domain/UBGraphicsMediaItem.h @@ -32,21 +32,19 @@ #include -#include +// #include #include -#include -#include +// #include +// #include -#include "core/UBApplication.h" #include "board/UBBoardController.h" -#include "domain/UBItem.h" +#include "domain/UBMediaAssetItem.h" #include "domain/UBResizableGraphicsItem.h" -#include "frameworks/UBFileSystemUtils.h" class QGraphicsVideoItem; -class UBGraphicsMediaItem : public QObject, public UBItem, public UBGraphicsItem, public QGraphicsRectItem, public UBResizableGraphicsItem +class UBGraphicsMediaItem : public QObject, public UBMediaAssetItem, public UBGraphicsItem, public QGraphicsRectItem, public UBResizableGraphicsItem { Q_OBJECT @@ -73,7 +71,7 @@ class UBGraphicsMediaItem : public QObject, public UBItem, public UBGraphicsItem virtual mediaType getMediaType() const = 0; - virtual std::shared_ptr scene(); + virtual std::shared_ptr scene() override; bool hasLinkedImage() const { return haveLinkedImage; } virtual QUrl mediaFileUrl() const { return mMediaFileUrl; } bool isMuted() const { return mMuted; } @@ -97,25 +95,24 @@ class UBGraphicsMediaItem : public QObject, public UBItem, public UBGraphicsItem bool firstLoad() const; void setFirstLoad(bool firstLoad); - QRectF boundingRect() const; + QRectF boundingRect() const override; - QSizeF size() const { return rect().size(); } + QSizeF size() const override { return rect().size(); } // Setters + virtual void setMediaAsset(const QString& documentPath, const QString& mediaAsset) override; virtual void setMediaFileUrl(QUrl url); void setInitialPos(qint64 p); void setMediaPos(qint64 p); - virtual void setSourceUrl(const QUrl &pSourceUrl); void setSelected(bool selected); void setMinimumSize(const QSize& size); - void setUuid(const QUuid &pUuid); - virtual void copyItemParameters(UBItem *copy) const; + virtual void copyItemParameters(UBItem *copy) const override; virtual void setSize(int width, int height); - void resize(qreal w, qreal h) { setSize(w, h); } + void resize(qreal w, qreal h) override { setSize(w, h); } - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; public slots: @@ -135,10 +132,8 @@ protected slots: protected: - virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value); - virtual void mousePressEvent(QGraphicsSceneMouseEvent *event); - - virtual void clearSource(); + virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; + virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override; QMediaPlayer *mMediaObject; @@ -171,15 +166,18 @@ class UBGraphicsAudioItem: public UBGraphicsMediaItem public: enum { Type = UBGraphicsItemType::AudioItemType}; - virtual int type() const + virtual int type() const override { return Type; } UBGraphicsAudioItem(const QUrl& pMediaFileUrl, QGraphicsItem *parent = 0); - mediaType getMediaType() const { return mediaType_Audio; } - virtual UBItem* deepCopy() const; + virtual QList mediaAssets() const override; + + mediaType getMediaType() const override { return mediaType_Audio; } + + virtual UBItem* deepCopy() const override; }; class UBGraphicsVideoItem: public UBGraphicsMediaItem @@ -188,20 +186,22 @@ class UBGraphicsVideoItem: public UBGraphicsMediaItem public: enum { Type = UBGraphicsItemType::VideoItemType}; - virtual int type() const + virtual int type() const override { return Type; } UBGraphicsVideoItem(const QUrl& pMediaFileUrl, QGraphicsItem *parent = 0); - mediaType getMediaType() const { return mediaType_Video; } + virtual QList mediaAssets() const override; + + mediaType getMediaType() const override { return mediaType_Video; } - void setSize(int width, int height); + void setSize(int width, int height) override; - virtual UBItem* deepCopy() const; + virtual UBItem* deepCopy() const override; - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; public slots: void videoSizeChanged(QSizeF newSize); @@ -222,10 +222,10 @@ protected slots: QGraphicsVideoItem *mVideoItem; - virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value); - virtual void hoverEnterEvent(QGraphicsSceneHoverEvent *event); - virtual void hoverMoveEvent(QGraphicsSceneHoverEvent *event); - virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent *event); + virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; + virtual void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override; + virtual void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override; + virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override; void setPlaceholderVisible(bool visible); diff --git a/src/domain/UBGraphicsPDFItem.cpp b/src/domain/UBGraphicsPDFItem.cpp index 9fa6b4096..b2054336d 100644 --- a/src/domain/UBGraphicsPDFItem.cpp +++ b/src/domain/UBGraphicsPDFItem.cpp @@ -35,6 +35,7 @@ #include "UBGraphicsPixmapItem.h" #include "UBGraphicsItemDelegate.h" +#include "core/UBPersistenceManager.h" #include "core/memcheck.h" UBGraphicsPDFItem::UBGraphicsPDFItem(PDFRenderer *renderer, int pageNumber, QGraphicsItem* parent) @@ -51,6 +52,21 @@ UBGraphicsPDFItem::~UBGraphicsPDFItem() { } +QList UBGraphicsPDFItem::mediaAssets() const +{ + return {UBPersistenceManager::objectDirectory + "/" + fileUuid().toString() + ".pdf"}; +} + +void UBGraphicsPDFItem::setMediaAsset(const QString& documentPath, const QString& mediaAsset) +{ + if (mediaAsset.at(0) != mediaAsset) + { + // exchange renderer for new asset + const auto uuid = uuidFromPath(mediaAsset); + auto newRenderer = PDFRenderer::rendererForUuid(uuid, documentPath + "/" + mediaAsset); + switchRenderer(newRenderer); + } +} QVariant UBGraphicsPDFItem::itemChange(GraphicsItemChange change, const QVariant &value) { @@ -68,12 +84,6 @@ void UBGraphicsPDFItem::updateChild() setCacheMode(prevCacheMode); } -void UBGraphicsPDFItem::setUuid(const QUuid &pUuid) -{ - UBItem::setUuid(pUuid); - setData(UBGraphicsItemData::ItemUuid, QVariant(pUuid)); -} - void UBGraphicsPDFItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { if (Delegate()->mousePressEvent(event)) @@ -135,7 +145,6 @@ void UBGraphicsPDFItem::copyItemParameters(UBItem *copy) const cp->setFlag(QGraphicsItem::ItemIsSelectable, true); cp->setData(UBGraphicsItemData::ItemLayerType, this->data(UBGraphicsItemData::ItemLayerType)); cp->setData(UBGraphicsItemData::ItemOwnZValue, this->data(UBGraphicsItemData::ItemOwnZValue)); - cp->setSourceUrl(this->sourceUrl()); cp->setZValue(this->zValue()); } } diff --git a/src/domain/UBGraphicsPDFItem.h b/src/domain/UBGraphicsPDFItem.h index 4fe21169f..6e6587ae5 100644 --- a/src/domain/UBGraphicsPDFItem.h +++ b/src/domain/UBGraphicsPDFItem.h @@ -32,7 +32,7 @@ #include -#include "UBItem.h" +#include "UBMediaAssetItem.h" #include "core/UB.h" #include "pdf/GraphicsPDFItem.h" @@ -40,7 +40,7 @@ class UBGraphicsItemDelegate; class UBGraphicsPixmapItem; -class UBGraphicsPDFItem: public GraphicsPDFItem, public UBItem, public UBGraphicsItem +class UBGraphicsPDFItem: public GraphicsPDFItem, public UBMediaAssetItem, public UBGraphicsItem { public: UBGraphicsPDFItem(PDFRenderer *renderer, int pageNumber, QGraphicsItem* parent = 0); @@ -48,34 +48,35 @@ class UBGraphicsPDFItem: public GraphicsPDFItem, public UBItem, public UBGraphic enum { Type = UBGraphicsItemType::PDFItemType }; - virtual int type() const + virtual int type() const override { return Type; } - virtual UBItem* deepCopy() const; + virtual QList mediaAssets() const override; + virtual void setMediaAsset(const QString& documentPath, const QString& mediaAsset) override; - virtual void copyItemParameters(UBItem *copy) const; + virtual UBItem* deepCopy() const override; - virtual void setRenderingQuality(RenderingQuality pRenderingQuality); + virtual void copyItemParameters(UBItem *copy) const override; - virtual void setCacheBehavior(CacheBehavior cacheBehavior); + virtual void setRenderingQuality(RenderingQuality pRenderingQuality) override; - virtual std::shared_ptr scene(); + virtual void setCacheBehavior(CacheBehavior cacheBehavior) override; + + virtual std::shared_ptr scene() override; virtual UBGraphicsPixmapItem* toPixmapItem() const; - virtual void clearSource(){;} - virtual void setUuid(const QUuid &pUuid); protected: - virtual void mousePressEvent(QGraphicsSceneMouseEvent *event); - virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event); - virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override; + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; - virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); - virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value); - virtual void updateChild(); + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; + virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; + virtual void updateChild() override; private slots: void OnRequireUpdate(); }; diff --git a/src/domain/UBGraphicsPixmapItem.cpp b/src/domain/UBGraphicsPixmapItem.cpp index ccb7d10a7..be1683821 100644 --- a/src/domain/UBGraphicsPixmapItem.cpp +++ b/src/domain/UBGraphicsPixmapItem.cpp @@ -63,23 +63,32 @@ UBGraphicsPixmapItem::UBGraphicsPixmapItem(QGraphicsItem* parent) setData(UBGraphicsItemData::ItemCanBeSetAsBackground, true); - setUuid(QUuid::createUuid()); //more logical solution is in creating uuid for element in element's constructor + UBGraphicsPixmapItem::setUuid(QUuid::createUuid()); //more logical solution is in creating uuid for element in element's constructor } UBGraphicsPixmapItem::~UBGraphicsPixmapItem() { } +QList UBGraphicsPixmapItem::mediaAssets() const +{ + if (!mMediaAsset.isEmpty()) + { + return {mMediaAsset}; + } + + return {}; +} + QVariant UBGraphicsPixmapItem::itemChange(GraphicsItemChange change, const QVariant &value) { QVariant newValue = Delegate()->itemChange(change, value); return QGraphicsPixmapItem::itemChange(change, newValue); } -void UBGraphicsPixmapItem::setUuid(const QUuid &pUuid) +void UBGraphicsPixmapItem::setMediaAsset(const QString& documentPath, const QString& mediaAsset) { - UBItem::setUuid(pUuid); - setData(UBGraphicsItemData::ItemUuid, QVariant(pUuid)); + mMediaAsset = mediaAsset; } void UBGraphicsPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *event) @@ -164,9 +173,9 @@ void UBGraphicsPixmapItem::copyItemParameters(UBItem *copy) const cp->setData(UBGraphicsItemData::ItemLocked, this->data(UBGraphicsItemData::ItemLocked)); cp->setData(UBGraphicsItemData::ItemIsHiddenOnDisplay, this->data(UBGraphicsItemData::ItemIsHiddenOnDisplay)); cp->setData(UBGraphicsItemData::ItemOwnZValue, this->data(UBGraphicsItemData::ItemOwnZValue)); - cp->setSourceUrl(this->sourceUrl()); cp->setZValue(this->zValue()); + cp->mMediaAsset = mMediaAsset; } } @@ -187,15 +196,3 @@ qreal UBGraphicsPixmapItem::opacity() const { return QGraphicsPixmapItem::opacity(); } - - -void UBGraphicsPixmapItem::clearSource() -{ - QDir imageDir = UBApplication::boardController->selectedDocument()->persistencePath() + "/" + UBPersistenceManager::imageDirectory; - const QStringList imageFiles = imageDir.entryList({uuid().toString() + ".*"}); - - for (const auto& imageFile : imageFiles) - { - UBFileSystemUtils::deleteFile(imageFile); - } -} diff --git a/src/domain/UBGraphicsPixmapItem.h b/src/domain/UBGraphicsPixmapItem.h index ddc794172..b0a597728 100644 --- a/src/domain/UBGraphicsPixmapItem.h +++ b/src/domain/UBGraphicsPixmapItem.h @@ -34,11 +34,11 @@ #include "core/UB.h" -#include "UBItem.h" +#include "UBMediaAssetItem.h" class UBGraphicsItemDelegate; -class UBGraphicsPixmapItem : public QObject, public QGraphicsPixmapItem, public UBItem, public UBGraphicsItem +class UBGraphicsPixmapItem : public QObject, public QGraphicsPixmapItem, public UBMediaAssetItem, public UBGraphicsItem { Q_OBJECT @@ -48,34 +48,38 @@ class UBGraphicsPixmapItem : public QObject, public QGraphicsPixmapItem, public enum { Type = UBGraphicsItemType::PixmapItemType }; - virtual int type() const + virtual int type() const override { return Type; } - virtual UBItem* deepCopy() const; - virtual void copyItemParameters(UBItem *copy) const; + virtual QList mediaAssets() const override; - virtual std::shared_ptr scene(); + virtual UBItem* deepCopy() const override; + + virtual void copyItemParameters(UBItem *copy) const override; + + virtual std::shared_ptr scene() override; Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity) void setOpacity(qreal op); qreal opacity() const; - virtual void clearSource(); - - virtual void setUuid(const QUuid &pUuid); + virtual void setMediaAsset(const QString& documentPath, const QString& mediaAsset) override; protected: - virtual void mousePressEvent(QGraphicsSceneMouseEvent *event); - virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event); - virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override; + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; + + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; - virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; - virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value); +private: + QString mMediaAsset; }; #endif /* UBGRAPHICSPIXMAPITEM_H_ */ diff --git a/src/domain/UBGraphicsPolygonItem.cpp b/src/domain/UBGraphicsPolygonItem.cpp index 556a84b70..a3d2405d1 100644 --- a/src/domain/UBGraphicsPolygonItem.cpp +++ b/src/domain/UBGraphicsPolygonItem.cpp @@ -92,12 +92,6 @@ void UBGraphicsPolygonItem::initialize() setUuid(QUuid::createUuid()); } -void UBGraphicsPolygonItem::setUuid(const QUuid &pUuid) -{ - UBItem::setUuid(pUuid); - setData(UBGraphicsItemData::ItemUuid, QVariant(pUuid)); //store item uuid inside the QGraphicsItem to fast operations with Items on the scene -} - void UBGraphicsPolygonItem::clearStroke() { if (mStroke!=NULL) diff --git a/src/domain/UBGraphicsPolygonItem.h b/src/domain/UBGraphicsPolygonItem.h index c1311180d..cdfaa8c3c 100644 --- a/src/domain/UBGraphicsPolygonItem.h +++ b/src/domain/UBGraphicsPolygonItem.h @@ -53,8 +53,6 @@ class UBGraphicsPolygonItem : public QGraphicsPolygonItem, public UBItem void initialize(); - void setUuid(const QUuid &pUuid); - void setStrokesGroup(UBGraphicsStrokesGroup* group); UBGraphicsStrokesGroup* strokesGroup() const{return mpGroup;} void setColor(const QColor& color); diff --git a/src/domain/UBGraphicsScene.cpp b/src/domain/UBGraphicsScene.cpp index f8ccacdb6..47d26c643 100644 --- a/src/domain/UBGraphicsScene.cpp +++ b/src/domain/UBGraphicsScene.cpp @@ -25,8 +25,6 @@ */ - - #include "UBGraphicsScene.h" #include @@ -42,6 +40,7 @@ #include "core/UBPersistenceManager.h" #include "core/UBTextTools.h" +#include "gui/UBBackgroundRuling.h" #include "gui/UBMagnifer.h" #include "gui/UBMainWindow.h" #include "gui/UBResources.h" @@ -321,7 +320,6 @@ UBGraphicsScene::UBGraphicsScene(std::shared_ptr document, bool , mPenCircle(0) , mDocument(document) , mDarkBackground(false) - , mPageBackground(UBPageBackground::plain) , mIsDesktopMode(false) , mZoomFactor(1) , mBackgroundObject(0) @@ -361,7 +359,6 @@ UBGraphicsScene::UBGraphicsScene(std::shared_ptr document, bool } mBackgroundGridSize = UBSettings::settings()->crossSize; - mIntermediateLines = UBSettings::settings()->intermediateLines; // Just for debug. Do not delete please // connect(this, SIGNAL(selectionChanged()), this, SLOT(selectionChangedProcessing())); @@ -1148,7 +1145,7 @@ void UBGraphicsScene::drawArcTo(const QPointF& pCenterPoint, qreal pSpanAngle) setDocumentUpdated(); } -void UBGraphicsScene::setBackground(bool pIsDark, UBPageBackground pBackground) +void UBGraphicsScene::setSceneBackground(bool pIsDark, const UBBackgroundRuling *background) { bool needRepaint = false; @@ -1164,9 +1161,9 @@ void UBGraphicsScene::setBackground(bool pIsDark, UBPageBackground pBackground) needRepaint = true; } - if (mPageBackground != pBackground) + if (mBackground != background) { - mPageBackground = pBackground; + mBackground = background; needRepaint = true; } @@ -1190,12 +1187,6 @@ void UBGraphicsScene::setBackgroundGridSize(int pSize) } } -void UBGraphicsScene::setIntermediateLines(bool checked) -{ - mIntermediateLines = checked; - updateBackground(); -} - void UBGraphicsScene::setDrawingMode(bool bModeDesktop) { mIsDesktopMode = bModeDesktop; @@ -1401,8 +1392,10 @@ std::shared_ptr UBGraphicsScene::sceneDeepCopy() const { std::shared_ptr copy = std::make_shared(this->document(), this->mUndoRedoStackEnabled); - copy->setBackground(this->isDarkBackground(), mPageBackground); + copy->setUuid(this->uuid()); copy->setBackgroundGridSize(mBackgroundGridSize); + copy->mDarkBackground = mDarkBackground; + copy->mBackground = mBackground; copy->setSceneRect(this->sceneRect()); if (this->mNominalSize.isValid()) @@ -1522,9 +1515,11 @@ void UBGraphicsScene::clearContent(clearCase pCase) if (itemGroup) { itemGroup->removeFromGroup(item); - groupsMap.insert(itemGroup, UBGraphicsItem::getOwnUuid(item)); + UBItem* ubitem = dynamic_cast(item); + groupsMap.insert(itemGroup, ubitem ? ubitem->uuid() : QUuid{}); if (itemGroup->childItems().count() == 1) { - groupsMap.insert(itemGroup, UBGraphicsItem::getOwnUuid(itemGroup->childItems().constFirst())); + UBItem* ubitem = dynamic_cast(itemGroup->childItems().constFirst()); + groupsMap.insert(itemGroup, ubitem ? ubitem->uuid() : QUuid{}); QGraphicsItem *lastItem = itemGroup->childItems().constFirst(); bool isSelected = itemGroup->isSelected(); itemGroup->destroy(false); @@ -1634,7 +1629,8 @@ UBGraphicsPixmapItem* UBGraphicsScene::addImage(QByteArray pData, QGraphicsItem* format = "png." + format; } - QString fileName = UBPersistenceManager::imageDirectory + "/" + pixmapItem->uuid().toString() + "." + format; + QString fileName = UBPersistenceManager::imageDirectory + "/" + UBMediaAssetItem::createMediaAssetUuid(pData).toString() + "." + format; + pixmapItem->setMediaAsset(documentPath, fileName); QString path = documentPath + "/" + fileName; @@ -1711,11 +1707,7 @@ UBGraphicsWidgetItem* UBGraphicsScene::addWidget(const QUrl& pWidgetUrl, const Q { int widgetType = UBGraphicsWidgetItem::widgetType(pWidgetUrl); - if(widgetType == UBWidgetType::Apple) // NOTE @letsfindaway obsolete - { - return addAppleWidget(pWidgetUrl, pPos); - } - else if(widgetType == UBWidgetType::W3C) + if(widgetType == UBWidgetType::W3C) { return addW3CWidget(pWidgetUrl, pPos); } @@ -1726,16 +1718,6 @@ UBGraphicsWidgetItem* UBGraphicsScene::addWidget(const QUrl& pWidgetUrl, const Q } } -// NOTE @letsfindaway obsolete -UBGraphicsAppleWidgetItem* UBGraphicsScene::addAppleWidget(const QUrl& pWidgetUrl, const QPointF& pPos) -{ - UBGraphicsAppleWidgetItem *appleWidget = new UBGraphicsAppleWidgetItem(pWidgetUrl); - - addGraphicsWidget(appleWidget, pPos); - - return appleWidget; -} - UBGraphicsW3CWidgetItem* UBGraphicsScene::addW3CWidget(const QUrl& pWidgetUrl, const QPointF& pPos) { UBGraphicsW3CWidgetItem *w3CWidget = new UBGraphicsW3CWidgetItem(pWidgetUrl, 0); @@ -1862,7 +1844,7 @@ UBGraphicsSvgItem* UBGraphicsScene::addSvg(const QUrl& pSvgFileUrl, const QPoint QString documentPath = UBApplication::boardController->selectedDocument()->persistencePath(); - QString fileName = UBPersistenceManager::imageDirectory + "/" + svgItem->uuid().toString() + ".svg"; + QString fileName = svgItem->mediaAssets().at(0); QString completePath = documentPath + "/" + fileName; @@ -2181,21 +2163,6 @@ QRectF UBGraphicsScene::normalizedSceneRect(qreal ratio) return normalizedRect; } -QGraphicsItem *UBGraphicsScene::itemForUuid(QUuid uuid) -{ - QGraphicsItem *result = 0; - - //simple search before implementing container for fast access - foreach (QGraphicsItem *item, items()) - { - if (UBGraphicsScene::getPersonalUuid(item) == uuid && !uuid.isNull()) { - result = item; - } - } - - return result; -} - void UBGraphicsScene::setDocument(std::shared_ptr pDocument) { if (pDocument != mDocument) @@ -2209,10 +2176,15 @@ void UBGraphicsScene::setDocument(std::shared_ptr pDocument) } } +const UBBackgroundRuling* UBGraphicsScene::background() const +{ + return mBackground; +} + QGraphicsItem* UBGraphicsScene::scaleToFitDocumentSize(QGraphicsItem* item, bool center, int margin, bool expand) { - int maxWidth = mNominalSize.width() - (margin * 2); - int maxHeight = mNominalSize.height() - (margin * 2); + int maxWidth = nominalSize().width() - (margin * 2); + int maxHeight = nominalSize().height() - (margin * 2); QRectF size = item->sceneBoundingRect(); @@ -2508,56 +2480,14 @@ bool UBGraphicsScene::isSnapping() const QPointF UBGraphicsScene::snap(const QPointF& point, double* force, std::optional proposedPoint, QPointF* gridSnapPoint) const { - QPointF snapPoint{point}; - double snapForce{0}; - double gridSize = backgroundGridSize(); - - if (mIntermediateLines) + if (!mBackground) { - gridSize /= 2.; + return {}; } - // y axis - double floorY = std::floor(point.y () / gridSize) * gridSize; - snapPoint.setY(point.y() - floorY < gridSize / 2. ? floorY : floorY + gridSize); - - // for blank background, use same snapping as for grid - if (mPageBackground == UBPageBackground::crossed || mPageBackground == UBPageBackground::plain) - { - // x axis - double floorX = std::floor(point.x () / gridSize) * gridSize; - snapPoint.setX(point.x() - floorX < gridSize / 2. ? floorX : floorX + gridSize); - } - - // force is a number between 0 and 1 based on the manhattan distance of the snap point - // from the original point - double relativeDist = (snapPoint - point).manhattanLength() / gridSize; - snapForce = std::max(1. - relativeDist, 0.); - - if (gridSnapPoint) - { - *gridSnapPoint = snapPoint; - } - - if (proposedPoint) - { - // compute force for proposed point and take that if it has higher force - double relativeDist = (proposedPoint.value() - point).manhattanLength() / gridSize; - double proposedForce = std::max(1. - relativeDist, 0.); - - if (proposedForce > snapForce) - { - snapForce = proposedForce; - snapPoint = proposedPoint.value(); - } - } - - if (force) - { - *force = snapForce; - } - - return snapPoint - point; + QRectF nominalScene{{0, 0}, mNominalSize}; + nominalScene.moveCenter({0, 0}); + return mBackground->snap(point, backgroundGridSize(), nominalScene, force, proposedPoint, gridSnapPoint); } QPointF UBGraphicsScene::snap(const std::vector& corners, int* snapIndex) const @@ -2676,76 +2606,38 @@ void UBGraphicsScene::setRenderingQuality(UBItem::RenderingQuality pRenderingQua } } -QList UBGraphicsScene::relativeDependenciesOfItem(QGraphicsItem* item) const +QList UBGraphicsScene::relativeDependencies() const { - QList relativePaths; + QList relativePaths; - UBGraphicsVideoItem *videoItem = dynamic_cast (item); - if (videoItem){ - QString completeFileName = QFileInfo(videoItem->mediaFileUrl().toLocalFile()).fileName(); - QString path = UBPersistenceManager::videoDirectory + "/"; - relativePaths << QUrl(path + completeFileName); - return relativePaths; - } - - UBGraphicsAudioItem *audioItem = dynamic_cast (item); - if (audioItem){ - QString completeFileName = QFileInfo(audioItem->mediaFileUrl().toLocalFile()).fileName(); - QString path = UBPersistenceManager::audioDirectory + "/"; - relativePaths << QUrl(path + completeFileName); - return relativePaths; - } - - UBGraphicsWidgetItem* widget = dynamic_cast(item); - if(widget){ - QString widgetPath = UBPersistenceManager::widgetDirectory + "/" + widget->uuid().toString() + ".wgt"; - QString screenshotPath = UBPersistenceManager::widgetDirectory + "/" + widget->uuid().toString().remove("{").remove("}") + ".png"; - relativePaths << QUrl(widgetPath); - relativePaths << QUrl(screenshotPath); - return relativePaths; - } - - UBGraphicsPixmapItem* pixmapItem = dynamic_cast(item); - if(pixmapItem){ - QDir imageDir = mDocument->persistencePath() + "/" + UBPersistenceManager::imageDirectory; - QStringList imageFiles = imageDir.entryList({pixmapItem->uuid().toString() + ".*"}); - - if (!imageFiles.isEmpty()) - { - relativePaths << QUrl(UBPersistenceManager::imageDirectory + "/" + imageFiles.last()); - } - - return relativePaths; + for (const auto item : mediaAssetItems()) + { + relativePaths << item->mediaAssets(); } - UBGraphicsSvgItem* svgItem = dynamic_cast(item); - if(svgItem){ - relativePaths << QUrl(UBPersistenceManager::imageDirectory + "/" + svgItem->uuid().toString() + ".svg"); - return relativePaths; - } + // remove duplicates + std::sort(relativePaths.begin(), relativePaths.end()); + relativePaths.erase(std::unique(relativePaths.begin(), relativePaths.end()), relativePaths.end()); return relativePaths; } -QList UBGraphicsScene::relativeDependencies() const +QList UBGraphicsScene::mediaAssetItems() const { - QList relativePaths; + QList mediaAssetItemList; - foreach(auto item, items()) + for (const auto item : items()) { - UBGraphicsGroupContainerItem* groupItem = dynamic_cast(item); - if(groupItem) + // items() returns a list of all items, including child items + const auto mediaAssetItem = dynamic_cast(item); + + if (mediaAssetItem) { - foreach (QGraphicsItem* child, groupItem->childItems()) - { - relativePaths << relativeDependenciesOfItem(child); - } + mediaAssetItemList << mediaAssetItem; } - else - relativePaths << relativeDependenciesOfItem(item); } - return relativePaths; + return mediaAssetItemList; } QSize UBGraphicsScene::nominalSize() @@ -2822,12 +2714,6 @@ void UBGraphicsScene::setOwnZlevel(QGraphicsItem *item) item->setZValue(item->data(UBGraphicsItemData::ItemOwnZValue).toReal()); } -QUuid UBGraphicsScene::getPersonalUuid(QGraphicsItem *item) -{ - QString idCandidate = item->data(UBGraphicsItemData::ItemUuid).toString(); - return idCandidate == QUuid().toString() ? QUuid() : QUuid(idCandidate); -} - qreal UBGraphicsScene::changeZLevelTo(QGraphicsItem *item, UBZLayerController::moveDestination dest, bool addUndo) { qreal previousZVal = item->data(UBGraphicsItemData::ItemOwnZValue).toReal(); @@ -2935,124 +2821,17 @@ void UBGraphicsScene::drawBackground(QPainter *painter, const QRectF &rect) if (mZoomFactor > 0.5) { - QColor bgCrossColor; - - if (darkBackground) - bgCrossColor = QColor(UBSettings::settings()->boardCrossColorDarkBackground->get().toString()); - else - bgCrossColor = QColor(UBSettings::settings()->boardCrossColorLightBackground->get().toString()); if (mZoomFactor < 0.7) { - int alpha = 255 * mZoomFactor / 2; - bgCrossColor.setAlpha (alpha); // fade the crossing on small zooms + painter->setOpacity(mZoomFactor / 2); } - qreal gridSize = backgroundGridSize(); - painter->setPen (bgCrossColor); - - if (mPageBackground == UBPageBackground::crossed) + if (mBackground) { - qreal firstY = ((int) (rect.y () / gridSize)) * gridSize; - - for (qreal yPos = firstY; yPos < rect.y () + rect.height (); yPos += gridSize) - { - painter->drawLine (rect.x (), yPos, rect.x () + rect.width (), yPos); - } - - qreal firstX = ((int) (rect.x () / gridSize)) * gridSize; - - for (qreal xPos = firstX; xPos < rect.x () + rect.width (); xPos += gridSize) - { - painter->drawLine (xPos, rect.y (), xPos, rect.y () + rect.height ()); - } - - if (mIntermediateLines) - { - QColor intermediateColor = bgCrossColor; - intermediateColor.setAlphaF(0.5 * bgCrossColor.alphaF()); - painter->setPen(intermediateColor); - - for (qreal yPos = firstY - gridSize/2; yPos < rect.y () + rect.height (); yPos += gridSize) - { - painter->drawLine (rect.x (), yPos, rect.x () + rect.width (), yPos); - } - - for (qreal xPos = firstX - gridSize/2; xPos < rect.x () + rect.width (); xPos += gridSize) - { - painter->drawLine (xPos, rect.y (), xPos, rect.y () + rect.height ()); - } - } - } - - else if (mPageBackground == UBPageBackground::ruled) - { - if(UBSettings::settings()->isSeyesRuledBackground()) - { - qreal gridSizeSeyes = gridSize * 2; // The grid size must be bigger - int nbMarginCase = 1; // a small left margin of one gridSize - - QPen seyesSquare ("#8e7cc3"); - seyesSquare.setWidthF (2.); - - QColor interlineColor("#6fa8dc"); - interlineColor.setAlphaF(0.6); - QPen interlinePen(interlineColor); - interlinePen.setWidthF(2.); - - QPen redLineMargin(QColor("red")); - redLineMargin.setWidthF(2.); - - // Horizontal lines - - qreal firstY = ((int) (rect.y () / gridSizeSeyes)) * gridSizeSeyes; - - for (qreal yPos = firstY; yPos < rect.y () + rect.height (); yPos += gridSizeSeyes) - { - painter->setPen (seyesSquare); - painter->drawLine (rect.x (), yPos, rect.x () + rect.width (), yPos); - painter->setPen (interlinePen); - painter->drawLine (rect.x (), yPos+gridSizeSeyes/4, rect.x () + rect.width (), yPos+gridSizeSeyes/4); - painter->drawLine (rect.x (), yPos+2*gridSizeSeyes/4, rect.x () + rect.width (), yPos+2*gridSizeSeyes/4); - painter->drawLine (rect.x (), yPos+3*gridSizeSeyes/4, rect.x () + rect.width (), yPos+3*gridSizeSeyes/4); - } - - // Vertical margin - - qreal firstX = ((int) nbMarginCase * gridSizeSeyes) - mNominalSize.width() / 2.; - - painter->setPen(redLineMargin); - painter->drawLine (firstX, rect.y (), firstX, rect.y () + rect.height ()); - - // Vertical lines - - firstX = ((int) (nbMarginCase + 1) * gridSizeSeyes) - mNominalSize.width() / 2.; - - painter->setPen (seyesSquare); - for (qreal xPos = firstX; xPos < rect.x () + rect.width (); xPos += gridSizeSeyes) - { - painter->drawLine (xPos, rect.y (), xPos, rect.y () + rect.height ()); - } - } - else - { - qreal firstY = ((int) (rect.y () / backgroundGridSize())) * backgroundGridSize(); - - for (qreal yPos = firstY; yPos < rect.y () + rect.height (); yPos += backgroundGridSize()) - { - painter->drawLine (rect.x (), yPos, rect.x () + rect.width (), yPos); - } - - if (mIntermediateLines) { - QColor intermediateColor = bgCrossColor; - intermediateColor.setAlphaF(0.5 * bgCrossColor.alphaF()); - painter->setPen(intermediateColor); - - for (qreal yPos = firstY - gridSize/2; yPos < rect.y () + rect.height (); yPos += gridSize) - { - painter->drawLine (rect.x (), yPos, rect.x () + rect.width (), yPos); - } - } - } + qreal gridSize = backgroundGridSize(); + QRectF nominalScene{{0, 0}, mNominalSize}; + nominalScene.moveCenter({0, 0}); + mBackground->draw(painter, rect, gridSize, nominalScene, mDarkBackground); } } } diff --git a/src/domain/UBGraphicsScene.h b/src/domain/UBGraphicsScene.h index e0113d3d2..3133fbdb7 100644 --- a/src/domain/UBGraphicsScene.h +++ b/src/domain/UBGraphicsScene.h @@ -37,13 +37,13 @@ #include "UBItem.h" +class UBBackgroundRuling; class UBGraphicsPixmapItem; class UBGraphicsSvgItem; class UBGraphicsPolygonItem; class UBGraphicsMediaItem; class UBGraphicsWidgetItem; class UBGraphicsW3CWidgetItem; -class UBGraphicsAppleWidgetItem; class UBToolWidget; class UBGraphicsPDFItem; class UBGraphicsTextItem; @@ -57,6 +57,7 @@ class UBMagnifierParams; class UBMagnifier; class UBGraphicsCache; class UBGraphicsGroupContainerItem; +class UBMediaAssetItem; class UBSelectionFrame; class UBBoardView; @@ -152,7 +153,6 @@ class UBGraphicsScene: public UBCoreGraphicsScene, public UBItem, public std::en void removeItems(const QSet& item); UBGraphicsWidgetItem* addWidget(const QUrl& pWidgetUrl, const QPointF& pPos = QPointF(0, 0)); - UBGraphicsAppleWidgetItem* addAppleWidget(const QUrl& pWidgetUrl, const QPointF& pPos = QPointF(0, 0)); UBGraphicsW3CWidgetItem* addW3CWidget(const QUrl& pWidgetUrl, const QPointF& pPos = QPointF(0, 0)); void addGraphicsWidget(UBGraphicsWidgetItem* graphicsWidget, const QPointF& pPos = QPointF(0, 0)); @@ -186,8 +186,6 @@ class UBGraphicsScene: public UBCoreGraphicsScene, public UBItem, public std::en QRectF normalizedSceneRect(qreal ratio = -1.0); - QGraphicsItem *itemForUuid(QUuid uuid); - void moveTo(const QPointF& pPoint); void drawLineTo(const QPointF& pEndPoint, const qreal& pWidth, bool bLineStyle); void drawLineTo(const QPointF& pEndPoint, const qreal& pStartWidth, const qreal& endWidth, bool bLineStyle); @@ -215,21 +213,13 @@ class UBGraphicsScene: public UBCoreGraphicsScene, public UBItem, public std::en return !mDarkBackground; } - UBPageBackground pageBackground() const - { - return mPageBackground; - } + const UBBackgroundRuling* background() const; int backgroundGridSize() const { return mBackgroundGridSize; } - bool intermediateLines() const - { - return mIntermediateLines; - } - bool hasBackground() { return (mBackgroundObject != 0); @@ -285,8 +275,8 @@ class UBGraphicsScene: public UBCoreGraphicsScene, public UBItem, public std::en virtual void setRenderingQuality(UBItem::RenderingQuality pRenderingQuality, UBItem::CacheBehavior cacheBehavior); - QList relativeDependenciesOfItem(QGraphicsItem* item) const; - QList relativeDependencies() const; + QList relativeDependencies() const; + QList mediaAssetItems() const; QSize nominalSize(); @@ -329,8 +319,6 @@ class UBGraphicsScene: public UBCoreGraphicsScene, public UBItem, public std::en void setSelectedZLevel(QGraphicsItem *item); void setOwnZlevel(QGraphicsItem *item); - static QUuid getPersonalUuid(QGraphicsItem *item); - UBGraphicsPolygonItem* polygonToPolygonItem(const QPolygonF pPolygon); void clearSelectionFrame(); UBBoardView *controlView(); @@ -345,10 +333,9 @@ public slots: void initStroke(); void hideTool(); - void setBackground(bool pIsDark, UBPageBackground pBackground); + void setSceneBackground(bool pIsDark, const UBBackgroundRuling *background); void setBackgroundZoomFactor(qreal zoom); void setBackgroundGridSize(int pSize); - void setIntermediateLines(bool checked); void setDrawingMode(bool bModeDesktop); void deselectAllItems(); @@ -445,9 +432,8 @@ public slots: std::shared_ptr mDocument; bool mDarkBackground; - UBPageBackground mPageBackground; + const UBBackgroundRuling* mBackground{nullptr}; int mBackgroundGridSize; - bool mIntermediateLines; bool mIsDesktopMode; qreal mZoomFactor; diff --git a/src/domain/UBGraphicsStrokesGroup.cpp b/src/domain/UBGraphicsStrokesGroup.cpp index fe5f7c3ef..85a9d4308 100644 --- a/src/domain/UBGraphicsStrokesGroup.cpp +++ b/src/domain/UBGraphicsStrokesGroup.cpp @@ -58,12 +58,6 @@ UBGraphicsStrokesGroup::~UBGraphicsStrokesGroup() { } -void UBGraphicsStrokesGroup::setUuid(const QUuid &pUuid) -{ - UBItem::setUuid(pUuid); - setData(UBGraphicsItemData::ItemUuid, QVariant(pUuid)); //store item uuid inside the QGraphicsItem to fast operations with Items on the scene -} - void UBGraphicsStrokesGroup::setColor(const QColor &color, colorType pColorType) { //TODO Implement common mechanism of managing groups, drop UBGraphicsStroke if it's obsolete diff --git a/src/domain/UBGraphicsStrokesGroup.h b/src/domain/UBGraphicsStrokesGroup.h index 87a5e36aa..9b55921ad 100644 --- a/src/domain/UBGraphicsStrokesGroup.h +++ b/src/domain/UBGraphicsStrokesGroup.h @@ -54,7 +54,6 @@ class UBGraphicsStrokesGroup : public QGraphicsItemGroup, public UBItem, public { return Type; } - virtual void setUuid(const QUuid &pUuid); void setColor(const QColor &color, colorType pColorType = currentColor); QColor color(colorType pColorType = currentColor) const; diff --git a/src/domain/UBGraphicsSvgItem.cpp b/src/domain/UBGraphicsSvgItem.cpp index cdef67048..598743070 100644 --- a/src/domain/UBGraphicsSvgItem.cpp +++ b/src/domain/UBGraphicsSvgItem.cpp @@ -55,6 +55,7 @@ UBGraphicsSvgItem::UBGraphicsSvgItem(const QString& pFilePath, QGraphicsItem* pa { mFileData = f.readAll(); f.close(); + mMediaAssetUuid = createMediaAssetUuid(mFileData); } } @@ -67,6 +68,7 @@ UBGraphicsSvgItem::UBGraphicsSvgItem(const QByteArray& pFileData, QGraphicsItem* setSharedRenderer(renderer); mFileData = pFileData; + mMediaAssetUuid = createMediaAssetUuid(mFileData); } @@ -89,13 +91,23 @@ void UBGraphicsSvgItem::init() setData(UBGraphicsItemData::ItemCanBeSetAsBackground, true); - setUuid(QUuid::createUuid()); + UBGraphicsSvgItem::setUuid(QUuid::createUuid()); } UBGraphicsSvgItem::~UBGraphicsSvgItem() { } +QList UBGraphicsSvgItem::mediaAssets() const +{ + return {UBPersistenceManager::imageDirectory + "/" + mMediaAssetUuid.toString() + ".svg"}; +} + +void UBGraphicsSvgItem::setMediaAsset(const QString& documentPath, const QString& mediaAsset) +{ + mMediaAssetUuid = uuidFromPath(mediaAsset); +} + QByteArray UBGraphicsSvgItem::fileData() const { @@ -184,8 +196,8 @@ void UBGraphicsSvgItem::copyItemParameters(UBItem *copy) const cp->setData(UBGraphicsItemData::ItemLocked, this->data(UBGraphicsItemData::ItemLocked)); cp->setData(UBGraphicsItemData::ItemIsHiddenOnDisplay, this->data(UBGraphicsItemData::ItemIsHiddenOnDisplay)); cp->setData(UBGraphicsItemData::ItemOwnZValue, this->data(UBGraphicsItemData::ItemOwnZValue)); - cp->setSourceUrl(this->sourceUrl()); cp->setZValue(this->zValue()); + cp->mMediaAssetUuid = mMediaAssetUuid; } } @@ -229,17 +241,3 @@ UBGraphicsPixmapItem* UBGraphicsSvgItem::toPixmapItem() const return pixmapItem; } - -void UBGraphicsSvgItem::setUuid(const QUuid &pUuid) -{ - UBItem::setUuid(pUuid); - setData(UBGraphicsItemData::ItemUuid, QVariant(pUuid)); //store item uuid inside the QGraphicsItem to fast operations with Items on the scene -} - - -void UBGraphicsSvgItem::clearSource() -{ - QString fileName = UBPersistenceManager::imageDirectory + "/" + uuid().toString() + ".svg"; - QString diskPath = UBApplication::boardController->selectedDocument()->persistencePath() + "/" + fileName; - UBFileSystemUtils::deleteFile(diskPath); -} diff --git a/src/domain/UBGraphicsSvgItem.h b/src/domain/UBGraphicsSvgItem.h index f831c99a2..643e64a2b 100644 --- a/src/domain/UBGraphicsSvgItem.h +++ b/src/domain/UBGraphicsSvgItem.h @@ -33,14 +33,14 @@ #include #include -#include "UBItem.h" +#include "UBMediaAssetItem.h" #include "core/UB.h" class UBGraphicsItemDelegate; class UBGraphicsPixmapItem; -class UBGraphicsSvgItem: public QGraphicsSvgItem, public UBItem, public UBGraphicsItem +class UBGraphicsSvgItem: public QGraphicsSvgItem, public UBMediaAssetItem, public UBGraphicsItem { public: UBGraphicsSvgItem(const QString& pFile, QGraphicsItem* parent = 0); @@ -50,45 +50,40 @@ class UBGraphicsSvgItem: public QGraphicsSvgItem, public UBItem, public UBGraphi virtual ~UBGraphicsSvgItem(); - QByteArray fileData() const; + virtual QList mediaAssets() const override; + virtual void setMediaAsset(const QString& documentPath, const QString& mediaAsset) override; - void setFileData(const QByteArray& pFileData) - { - mFileData = pFileData; - } + QByteArray fileData() const; enum { Type = UBGraphicsItemType::SvgItemType }; - virtual int type() const + virtual int type() const override { return Type; } - virtual UBItem* deepCopy() const; + virtual UBItem* deepCopy() const override; - virtual void copyItemParameters(UBItem *copy) const; + virtual void copyItemParameters(UBItem *copy) const override; - virtual void setRenderingQuality(RenderingQuality pRenderingQuality); + virtual void setRenderingQuality(RenderingQuality pRenderingQuality) override; - virtual std::shared_ptr scene(); + virtual std::shared_ptr scene() override; virtual UBGraphicsPixmapItem* toPixmapItem() const; - virtual void setUuid(const QUuid &pUuid); - - virtual void clearSource(); - protected: - virtual void mousePressEvent(QGraphicsSceneMouseEvent *event); - virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event); - virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override; + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; - virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; - virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value); + virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; QByteArray mFileData; + QUuid mMediaAssetUuid; }; #endif /* UBGRAPHICSSVGITEM_H_ */ diff --git a/src/domain/UBGraphicsTextItem.cpp b/src/domain/UBGraphicsTextItem.cpp index 61b0bc232..2043dc36b 100644 --- a/src/domain/UBGraphicsTextItem.cpp +++ b/src/domain/UBGraphicsTextItem.cpp @@ -376,7 +376,6 @@ void UBGraphicsTextItem::copyItemParameters(UBItem *copy) const cp->setTextWidth(this->textWidth()); cp->setTextHeight(this->textHeight()); - cp->setSourceUrl(this->sourceUrl()); cp->setZValue(this->zValue()); } } @@ -488,12 +487,6 @@ QSizeF UBGraphicsTextItem::size() const return QSizeF(textWidth(), textHeight()); } -void UBGraphicsTextItem::setUuid(const QUuid &pUuid) -{ - UBItem::setUuid(pUuid); - setData(UBGraphicsItemData::ItemUuid, QVariant(pUuid)); //store item uuid inside the QGraphicsItem to fast operations with Items on the scene -} - void UBGraphicsTextItem::undoCommandAdded() { diff --git a/src/domain/UBGraphicsTextItem.h b/src/domain/UBGraphicsTextItem.h index a06470e2c..56f80b85c 100644 --- a/src/domain/UBGraphicsTextItem.h +++ b/src/domain/UBGraphicsTextItem.h @@ -95,8 +95,6 @@ class UBGraphicsTextItem : public QGraphicsTextItem, public UBItem, public UBRes mColorOnLightBackground = pColorOnLightBackground; } - virtual void clearSource(){;} - virtual void setUuid(const QUuid &pUuid); void activateTextEditor(bool activate); void setSelected(bool selected); void recolor(); diff --git a/src/domain/UBGraphicsWidgetItem.cpp b/src/domain/UBGraphicsWidgetItem.cpp index ce285bb6a..906b23003 100644 --- a/src/domain/UBGraphicsWidgetItem.cpp +++ b/src/domain/UBGraphicsWidgetItem.cpp @@ -48,10 +48,10 @@ #include "core/memcheck.h" #include "core/UBApplicationController.h" #include "core/UBApplication.h" +#include "core/UBPersistenceManager.h" #include "core/UBSettings.h" #include "frameworks/UBFileSystemUtils.h" -#include "frameworks/UBPlatformUtils.h" #include "web/UBWebController.h" #include "web/simplebrowser/webpage.h" @@ -168,10 +168,9 @@ UBGraphicsWidgetItem::UBGraphicsWidgetItem(const QUrl &pWidgetUrl, QGraphicsItem setAutoFillBackground(false); /* - * Quick workaround for https://bugreports.qt.io/browse/QTBUG-128241 (bug appearing with Qt 6.7.2) - * To test with Qt 6.8.1 and then change the following directive if really fixed with it + * Quick workaround for https://bugreports.qt.io/browse/QTBUG-128241 (bug appearing with Qt 6.7.2, fixed in 6.8.1) */ -#if (QT_VERSION < QT_VERSION_CHECK(6, 7, 2)) +#if (QT_VERSION < QT_VERSION_CHECK(6, 7, 2) || QT_VERSION > QT_VERSION_CHECK(6, 8, 0)) mWebEngineView->setAttribute(Qt::WA_TranslucentBackground); mWebEngineView->page()->setBackgroundColor(QColor(Qt::transparent)); #else @@ -198,6 +197,13 @@ UBGraphicsWidgetItem::~UBGraphicsWidgetItem() delete mWebEngineView; } +QList UBGraphicsWidgetItem::mediaAssets() const +{ + const QString widgetPath = UBPersistenceManager::widgetDirectory + "/" + mWidgetUrl.fileName(); + const QString screenshotPath = UBPersistenceManager::widgetDirectory + "/" + uuid().toString(QUuid::WithoutBraces) + ".png"; + return {widgetPath, screenshotPath}; +} + void UBGraphicsWidgetItem::initialize() { setMinimumSize(nominalSize()); @@ -390,18 +396,6 @@ QUrl UBGraphicsWidgetItem::getSnapshotPath() const return mSnapshotFile; } -void UBGraphicsWidgetItem::clearSource() -{ - UBFileSystemUtils::deleteDir(getOwnFolder().toLocalFile()); - UBFileSystemUtils::deleteFile(getSnapshotPath().toLocalFile()); -} - -void UBGraphicsWidgetItem::setUuid(const QUuid &pUuid) -{ - UBItem::setUuid(pUuid); - setData(UBGraphicsItemData::ItemUuid, QVariant(pUuid)); //store item uuid inside the QGraphicsItem to fast operations with Items on the scene -} - QSize UBGraphicsWidgetItem::nominalSize() const { return mNominalSize; @@ -488,9 +482,7 @@ int UBGraphicsWidgetItem::widgetType(const QUrl& pUrl) { QString mime = UBFileSystemUtils::mimeTypeFromFileName(pUrl.toString()); - if (mime == "application/vnd.apple-widget") // NOTE @letsfindaway obsolete - return UBWidgetType::Apple; - else if (mime == "application/widget") + if (mime == "application/widget") return UBWidgetType::W3C; else return UBWidgetType::Other; @@ -501,7 +493,6 @@ QString UBGraphicsWidgetItem::widgetName(const QUrl& widgetPath) QString name; QString version; QFile w3CConfigFile(widgetPath.toLocalFile() + "/config.xml"); - QFile appleConfigFile(widgetPath.toLocalFile() + "/Info.plist"); if (w3CConfigFile.exists() && w3CConfigFile.open(QFile::ReadOnly)) { QDomDocument doc; @@ -515,43 +506,7 @@ QString UBGraphicsWidgetItem::widgetName(const QUrl& widgetPath) } w3CConfigFile.close(); } - else if (appleConfigFile.exists() && appleConfigFile.open(QFile::ReadOnly)) { - QDomDocument doc; - doc.setContent(appleConfigFile.readAll()); - QDomElement root = doc.firstChildElement("plist"); - if (!root.isNull()) { - QDomElement dictElement = root.firstChildElement("dict"); - if (!dictElement.isNull()) { - QDomNodeList childNodes = dictElement.childNodes(); - - /* looking for something like - * .. - * CFBundleDisplayName - * brain scans - * .. - */ - - for(int i = 0; i < childNodes.count() - 1; i++) { - if (childNodes.at(i).isElement()) { - QDomElement elKey = childNodes.at(i).toElement(); - if (elKey.text() == "CFBundleDisplayName") { - if (childNodes.at(i + 1).isElement()) { - QDomElement elValue = childNodes.at(i + 1).toElement(); - name = elValue.text(); - } - } - else if (elKey.text() == "CFBundleShortVersionString") { - if (childNodes.at(i + 1).isElement()) { - QDomElement elValue = childNodes.at(i + 1).toElement(); - version = elValue.text(); - } - } - } - } - } - } - appleConfigFile.close(); - } + QString result; if (name.length() > 0) { @@ -955,95 +910,6 @@ QSizeF UBGraphicsWidgetItem::size() const } -// NOTE @letsfindaway obsolete -UBGraphicsAppleWidgetItem::UBGraphicsAppleWidgetItem(const QUrl& pWidgetUrl, QGraphicsItem *parent) - : UBGraphicsWidgetItem(pWidgetUrl, parent) -{ - QString path = pWidgetUrl.toLocalFile(); - - if (!path.endsWith(".wdgt") && !path.endsWith(".wdgt/")) { - int lastSlashIndex = path.lastIndexOf("/"); - if (lastSlashIndex > 0) - path = path.mid(0, lastSlashIndex + 1); - } - - QFile plistFile(path + "/Info.plist"); - plistFile.open(QFile::ReadOnly); - - QByteArray plistBin = plistFile.readAll(); - QString plist = QString::fromUtf8(plistBin); - - int mainHtmlIndex = plist.indexOf("MainHTML"); - int mainHtmlIndexStart = plist.indexOf("", mainHtmlIndex); - int mainHtmlIndexEnd = plist.indexOf("", mainHtmlIndexStart); - - if (mainHtmlIndex > -1 && mainHtmlIndexStart > -1 && mainHtmlIndexEnd > -1) - mMainHtmlFileName = plist.mid(mainHtmlIndexStart + 8, mainHtmlIndexEnd - mainHtmlIndexStart - 8); - - mMainHtmlUrl = pWidgetUrl; - mMainHtmlUrl.setPath(pWidgetUrl.path() + "/" + mMainHtmlFileName); - - mWebEngineView->load(mMainHtmlUrl); - - QPixmap defaultPixmap(pWidgetUrl.toLocalFile() + "/Default.png"); - - setMaximumSize(defaultPixmap.size()); - - mNominalSize = defaultPixmap.size(); - - initialize(); -} - - -UBGraphicsAppleWidgetItem::~UBGraphicsAppleWidgetItem() -{ - /* NOOP */ -} - -void UBGraphicsAppleWidgetItem::setUuid(const QUuid &pUuid) -{ - UBItem::setUuid(pUuid); - setData(UBGraphicsItemData::ItemUuid, QVariant(pUuid)); //store item uuid inside the QGraphicsItem to fast operations with Items on the scene -} - -UBItem* UBGraphicsAppleWidgetItem::deepCopy() const -{ - UBGraphicsAppleWidgetItem *appleWidget = new UBGraphicsAppleWidgetItem(mWebEngineView->url(), parentItem()); - - copyItemParameters(appleWidget); - - return appleWidget; - -} - -void UBGraphicsAppleWidgetItem::copyItemParameters(UBItem *copy) const -{ - UBGraphicsAppleWidgetItem *cp = dynamic_cast(copy); - if (cp) - { - foreach(QString key, mPreferences.keys()) - { - cp->setPreference(key, mPreferences.value(key)); - } - - foreach(QString key, mDatastore.keys()) - { - cp->setDatastoreEntry(key, mDatastore.value(key)); - } - - cp->setSourceUrl(this->sourceUrl()); - cp->setZValue(this->zValue()); - } - -} - - - - -bool UBGraphicsW3CWidgetItem::sTemplateLoaded = false; -QString UBGraphicsW3CWidgetItem::sNPAPIWrappperConfigTemplate; -QMap UBGraphicsW3CWidgetItem::sNPAPIWrapperTemplates; - UBGraphicsW3CWidgetItem::UBGraphicsW3CWidgetItem(const QUrl& pWidgetUrl, QGraphicsItem *parent) : UBGraphicsWidgetItem(pWidgetUrl, parent) , mW3CWidgetAPI(0) @@ -1064,10 +930,19 @@ UBGraphicsW3CWidgetItem::UBGraphicsW3CWidgetItem(const QUrl& pWidgetUrl, QGraphi int height = 150; QFile configFile(path + "config.xml"); - configFile.open(QFile::ReadOnly); + QByteArray widgetContent; + if (configFile.open(QFile::ReadOnly)) + { + widgetContent = configFile.readAll(); + configFile.close(); + } + else + { + qDebug() << "Could not open widget configuration at " << configFile.fileName(); + } QDomDocument doc; - doc.setContent(configFile.readAll()); + doc.setContent(widgetContent); QDomNodeList widgetDomList = doc.elementsByTagName("widget"); if (widgetDomList.count() > 0) { @@ -1191,12 +1066,6 @@ UBGraphicsW3CWidgetItem::~UBGraphicsW3CWidgetItem() /* NOOP */ } -void UBGraphicsW3CWidgetItem::setUuid(const QUuid &pUuid) -{ - UBItem::setUuid(pUuid); - setData(UBGraphicsItemData::ItemUuid, QVariant(pUuid)); //store item uuid inside the QGraphicsItem to fast operations with Items on the scene -} - UBItem* UBGraphicsW3CWidgetItem::deepCopy() const { UBGraphicsW3CWidgetItem *copy = new UBGraphicsW3CWidgetItem(mWidgetUrl, parentItem()); @@ -1243,107 +1112,6 @@ void UBGraphicsW3CWidgetItem::sendJSLeaveEvent() } } -QString UBGraphicsW3CWidgetItem::createNPAPIWrapper(const QString& url, const QString& pMimeType, const QSize& sizeHint, const QString& pName) -{ - const QString userWidgetPath = UBSettings::settings()->userInteractiveDirectory() + "/" + tr("Web"); - QDir userWidgetDir(userWidgetPath); - - return createNPAPIWrapperInDir(url, userWidgetDir, pMimeType, sizeHint, pName); -} - -QString UBGraphicsW3CWidgetItem::createNPAPIWrapperInDir(const QString& pUrl, const QDir& pDir, const QString& pMimeType, const QSize& sizeHint, const QString& pName) -{ - QString url = pUrl; - url = UBFileSystemUtils::removeLocalFilePrefix(url); - QString name = pName; - - QFileInfo fi(url); - - if (name.length() == 0) - name = fi.baseName(); - - if (fi.exists()) - url = fi.fileName(); - - loadNPAPIWrappersTemplates(); - - QString htmlTemplate; - - if (pMimeType.length() > 0 && sNPAPIWrapperTemplates.contains(pMimeType)) - htmlTemplate = sNPAPIWrapperTemplates.value(pMimeType); - else { - QString extension = UBFileSystemUtils::extension(url); - if (sNPAPIWrapperTemplates.contains(extension)) - htmlTemplate = sNPAPIWrapperTemplates.value(extension); - } - - if (htmlTemplate.length() > 0) { - htmlTemplate = htmlTemplate.replace(QString("{in.url}"), url) - .replace(QString("{in.width}"), QString("%1").arg(sizeHint.width())) - .replace(QString("{in.height}"), QString("%1").arg(sizeHint.height())); - - QString configTemplate = sNPAPIWrappperConfigTemplate - .replace(QString("{in.id}"), url) - .replace(QString("{in.width}"), QString("%1").arg(sizeHint.width())) - .replace(QString("{in.height}"), QString("%1").arg(sizeHint.height())) - .replace(QString("{in.name}"), name) - .replace(QString("{in.startFile}"), QString("index.htm")); - - QString dirPath = pDir.path(); - if (!pDir.exists()) - pDir.mkpath(dirPath); - - QString widgetLibraryPath = dirPath + "/" + name + ".wgt"; - QDir widgetLibraryDir(widgetLibraryPath); - - if (widgetLibraryDir.exists()) - if (!UBFileSystemUtils::deleteDir(widgetLibraryDir.path())) - qWarning() << "Cannot delete old widget " << widgetLibraryDir.path(); - - widgetLibraryDir.mkpath(widgetLibraryPath); - if (fi.exists()) { - QString target = widgetLibraryPath + "/" + fi.fileName(); - QString source = pUrl; - source = UBFileSystemUtils::removeLocalFilePrefix(source); - QFile::copy(source, target); - } - - QFile configFile(widgetLibraryPath + "/config.xml"); - - if (!configFile.open(QIODevice::WriteOnly)) { - qWarning() << "Cannot open file " << configFile.fileName(); - return QString(); - } - - QTextStream outConfig(&configFile); -#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) - outConfig.setCodec("UTF-8"); -#endif - - outConfig << configTemplate; - configFile.close(); - - QFile indexFile(widgetLibraryPath + "/index.htm"); - - if (!indexFile.open(QIODevice::WriteOnly)) { - qWarning() << "Cannot open file " << indexFile.fileName(); - return QString(); - } - - QTextStream outIndex(&indexFile); -#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) - outIndex.setCodec("UTF-8"); -#endif - - outIndex << htmlTemplate; - indexFile.close(); - - return widgetLibraryPath; - } - else - return QString(); -} - QString UBGraphicsW3CWidgetItem::createHtmlWrapperInDir(const QString& html, const QDir& pDir, const QSize& sizeHint, const QString& pName) { QString widgetPath = pDir.path() + "/" + pName + ".wgt"; @@ -1414,13 +1182,6 @@ QString UBGraphicsW3CWidgetItem::createHtmlWrapperInDir(const QString& html, con return widgetPath; } -bool UBGraphicsW3CWidgetItem::hasNPAPIWrapper(const QString& pMimeType) -{ - loadNPAPIWrappersTemplates(); - - return sNPAPIWrapperTemplates.contains(pMimeType); -} - void UBGraphicsW3CWidgetItem::registerAPI() { UBGraphicsWidgetItem::registerAPI(); @@ -1432,40 +1193,6 @@ void UBGraphicsW3CWidgetItem::registerAPI() } } -void UBGraphicsW3CWidgetItem::loadNPAPIWrappersTemplates() -{ - if (!sTemplateLoaded) { - sNPAPIWrapperTemplates.clear(); - - QString templatePath = UBPlatformUtils::applicationTemplateDirectory(); - - QDir templateDir(templatePath); - - foreach(QString fileName, templateDir.entryList()) { - if (fileName.startsWith("npapi-wrapper") && (fileName.endsWith(".htm") || fileName.endsWith(".html"))) { - - QString htmlContent = UBFileSystemUtils::readTextFile(templatePath + fileName); - - if (htmlContent.length() > 0) { - QStringList tokens = fileName.split("."); - - if (tokens.length() >= 4) { - QString mime = tokens.at(tokens.length() - 4 ); - mime += "/" + tokens.at(tokens.length() - 3); - - QString fileExtension = tokens.at(tokens.length() - 2); - - sNPAPIWrapperTemplates.insert(mime, htmlContent); - sNPAPIWrapperTemplates.insert(fileExtension, htmlContent); - } - } - } - } - sNPAPIWrappperConfigTemplate = UBFileSystemUtils::readTextFile(templatePath + "npapi-wrapper.config.xml"); - sTemplateLoaded = true; - } -} - QString UBGraphicsW3CWidgetItem::textForSubElementByLocale(QDomElement rootElement, QString subTagName, QLocale locale) { QDomNodeList subList = rootElement.elementsByTagName(subTagName); @@ -1508,7 +1235,6 @@ void UBGraphicsW3CWidgetItem::copyItemParameters(UBItem *copy) const cp->setData(UBGraphicsItemData::ItemLocked, this->data(UBGraphicsItemData::ItemLocked)); cp->setData(UBGraphicsItemData::ItemIsHiddenOnDisplay, this->data(UBGraphicsItemData::ItemIsHiddenOnDisplay)); cp->setData(UBGraphicsItemData::ItemOwnZValue, this->data(UBGraphicsItemData::ItemOwnZValue)); - cp->setSourceUrl(this->sourceUrl()); cp->resize(this->size()); @@ -1527,3 +1253,13 @@ void UBGraphicsW3CWidgetItem::copyItemParameters(UBItem *copy) const } } +void UBGraphicsW3CWidgetItem::setMediaAsset(const QString& documentPath, const QString& mediaAsset) +{ + if (mediaAsset.endsWith(".wgt")) + { + widgetUrl(QUrl::fromLocalFile(documentPath + "/" + mediaAsset)); + setOwnFolder(widgetUrl()); + mMainHtmlUrl.setPath(widgetUrl().path() + "/" + mMainHtmlFileName); + } +} + diff --git a/src/domain/UBGraphicsWidgetItem.h b/src/domain/UBGraphicsWidgetItem.h index 0919651ae..92fc1f21f 100644 --- a/src/domain/UBGraphicsWidgetItem.h +++ b/src/domain/UBGraphicsWidgetItem.h @@ -36,7 +36,7 @@ #include "core/UB.h" -#include "UBItem.h" +#include "UBMediaAssetItem.h" #include "UBResizableGraphicsItem.h" class QWebChannel; @@ -53,11 +53,11 @@ struct UBWidgetType { enum Enum { - W3C = 0, Apple, Other + W3C = 0, Other }; }; -class UBGraphicsWidgetItem : public QGraphicsProxyWidget, public UBItem, public UBResizableGraphicsItem, public UBGraphicsItem +class UBGraphicsWidgetItem : public QGraphicsProxyWidget, public UBMediaAssetItem, public UBResizableGraphicsItem, public UBGraphicsItem { Q_OBJECT @@ -68,7 +68,7 @@ class UBGraphicsWidgetItem : public QGraphicsProxyWidget, public UBItem, public enum { Type = UBGraphicsItemType::GraphicsWidgetItemType }; virtual int type() const override { return Type; } - + virtual QList mediaAssets() const override; virtual void initialize(); virtual void resize(qreal w, qreal h) override; @@ -109,10 +109,6 @@ class UBGraphicsWidgetItem : public QGraphicsProxyWidget, public UBItem, public virtual void setSnapshotPath(const QUrl &newFilePath); virtual QUrl getSnapshotPath() const; - virtual void clearSource() override; - - virtual void setUuid(const QUuid &pUuid) override; - QSize nominalSize() const; bool hasLoadedSuccessfully() const; @@ -217,20 +213,6 @@ class UBGraphicsWidgetItem : public QGraphicsProxyWidget, public UBItem, public static QStringList sInlineJavaScripts; }; -// NOTE @letsfindaway obsolete -class UBGraphicsAppleWidgetItem : public UBGraphicsWidgetItem -{ - Q_OBJECT - - public: - UBGraphicsAppleWidgetItem(const QUrl& pWidgetUrl, QGraphicsItem *parent = 0); - ~UBGraphicsAppleWidgetItem(); - - virtual void copyItemParameters(UBItem *copy) const; - virtual void setUuid(const QUuid &pUuid); - virtual UBItem* deepCopy() const; -}; - class UBGraphicsW3CWidgetItem : public UBGraphicsWidgetItem { Q_OBJECT @@ -271,36 +253,28 @@ class UBGraphicsW3CWidgetItem : public UBGraphicsWidgetItem UBGraphicsW3CWidgetItem(const QUrl& pWidgetUrl, QGraphicsItem *parent = 0); ~UBGraphicsW3CWidgetItem(); - virtual void setUuid(const QUuid &pUuid); - virtual UBItem* deepCopy() const; - virtual void copyItemParameters(UBItem *copy) const; + virtual UBItem* deepCopy() const override; + virtual void copyItemParameters(UBItem *copy) const override; + virtual void setMediaAsset(const QString& documentPath, const QString& mediaAsset) override; QMap preferences() const; Metadata metadatas() const; - virtual void removeScript(); - virtual void sendJSEnterEvent(); - virtual void sendJSLeaveEvent(); + virtual void removeScript() override; + virtual void sendJSEnterEvent() override; + virtual void sendJSLeaveEvent() override; - static QString createNPAPIWrapper(const QString& url, const QString& pMimeType = QString(), const QSize& sizeHint = QSize(300, 150), const QString& pName = QString()); - static QString createNPAPIWrapperInDir(const QString& url, const QDir& pDir, const QString& pMimeType = QString(), const QSize& sizeHint = QSize(300, 150), const QString& pName = QString()); static QString createHtmlWrapperInDir(const QString& html, const QDir& pDir, const QSize& sizeHint, const QString& pName); - static bool hasNPAPIWrapper(const QString& pMimeType); Metadata mMetadatas; private slots: - virtual void registerAPI(); + virtual void registerAPI() override; private: - static void loadNPAPIWrappersTemplates(); static QString textForSubElementByLocale(QDomElement rootElement, QString subTagName, QLocale locale); UBW3CWidgetAPI* mW3CWidgetAPI; QMap mPreferences; - - static bool sTemplateLoaded; - static QString sNPAPIWrappperConfigTemplate; - static QMap sNPAPIWrapperTemplates; }; #endif // UBGRAPHICSWIDGETITEM_H diff --git a/src/domain/UBItem.cpp b/src/domain/UBItem.cpp index 80c489086..9a0cfa1e8 100644 --- a/src/domain/UBItem.cpp +++ b/src/domain/UBItem.cpp @@ -97,12 +97,6 @@ bool UBGraphicsItem::isHiddenOnDisplay(QGraphicsItem *item) return item->data(UBGraphicsItemData::ItemIsHiddenOnDisplay).toBool(); } -QUuid UBGraphicsItem::getOwnUuid(QGraphicsItem *item) -{ - QString idCandidate = item->data(UBGraphicsItemData::ItemUuid).toString(); - return idCandidate == QUuid().toString() ? QUuid() : QUuid(idCandidate); -} - void UBGraphicsItem::remove(bool canUndo) { if (Delegate()) diff --git a/src/domain/UBItem.h b/src/domain/UBItem.h index 5d6b1679c..23b6416a4 100644 --- a/src/domain/UBItem.h +++ b/src/domain/UBItem.h @@ -31,7 +31,7 @@ #define UBITEM_H #include -#include "core/UB.h" + #include "domain/UBGraphicsItemDelegate.h" class UBGraphicsScene; @@ -92,24 +92,12 @@ class UBItem return 0; } - virtual QUrl sourceUrl() const - { - return mSourceUrl; - } - - virtual void setSourceUrl(const QUrl& pSourceUrl) - { - mSourceUrl = pSourceUrl; - } - protected: QUuid mUuid; RenderingQuality mRenderingQuality; - QUrl mSourceUrl; - CacheBehavior mCacheBehavior; }; @@ -133,14 +121,11 @@ class UBGraphicsItem static bool isFlippable(QGraphicsItem *item); static bool isLocked(QGraphicsItem *item); static bool isHiddenOnDisplay(QGraphicsItem *item); - static QUuid getOwnUuid(QGraphicsItem *item); static UBGraphicsItemDelegate *Delegate(QGraphicsItem *pItem); void remove(bool canUndo = true); - virtual void clearSource(){} - private: UBGraphicsItemDelegate* mDelegate; }; diff --git a/src/domain/UBMediaAssetItem.cpp b/src/domain/UBMediaAssetItem.cpp new file mode 100644 index 000000000..71ffbd5cb --- /dev/null +++ b/src/domain/UBMediaAssetItem.cpp @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2015-2025 Département de l'Instruction Publique (DIP-SEM) + * + * This file is part of OpenBoard. + * + * OpenBoard 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, + * 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 OpenBoard. If not, see . + */ + +#include "UBMediaAssetItem.h" + +#include +#include + +QUuid UBMediaAssetItem::mediaAssetUuid() const +{ + return uuidFromPath(mediaAssets().value(0, "")); +} + +QUuid UBMediaAssetItem::createMediaAssetUuid(const QByteArray& data) +{ + static QUuid namespaceUuid{QUuid::createUuidV5(QUuid(), QString("OpenBoard"))}; + + return QUuid::createUuidV5(namespaceUuid, data); +} + +QUuid UBMediaAssetItem::createMediaAssetUuid(const QString& path) +{ + QFileInfo fileInfo{path}; + + if (!fileInfo.exists()) + { + return {}; + } + + if (fileInfo.isFile() && fileInfo.isReadable()) + { + QFile file{path}; + + if (file.open(QFile::ReadOnly)) + { + return createMediaAssetUuid(file.readAll()); + } + + return {}; + } + + if (fileInfo.isDir()) + { + // check for a config.xml and read the id + QFile configFile(path + "/config.xml"); + + if (configFile.open(QFile::ReadOnly)) + { + QDomDocument doc; + doc.setContent(configFile.readAll()); + QDomNodeList widgetDomList = doc.elementsByTagName("widget"); + + if (widgetDomList.count() > 0) + { + QDomElement widgetElement = widgetDomList.item(0).toElement(); + const auto id = widgetElement.attribute("id", ""); + + if (id.startsWith("urn:uuid:")) + { + const auto uuid = QUuid{id.mid(9)}; + + if (uuid.version() == QUuid::Sha1) + { + return uuid; + } + } + else + { + return createMediaAssetUuid(id.toUtf8()); + } + } + } + } + + return {}; +} + +QUuid UBMediaAssetItem::uuidFromPath(const QString& path) const +{ + static QRegularExpression uuidPattern("[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"); + + // scan asset file name for UUID + QRegularExpressionMatch match = uuidPattern.match(path); + + if (match.hasMatch()) + { + return QUuid{match.captured()}; + } + + return {}; +} diff --git a/src/core/UBForeignObjectsHandler.h b/src/domain/UBMediaAssetItem.h similarity index 53% rename from src/core/UBForeignObjectsHandler.h rename to src/domain/UBMediaAssetItem.h index 7d03464b9..c572b2121 100644 --- a/src/core/UBForeignObjectsHandler.h +++ b/src/domain/UBMediaAssetItem.h @@ -1,10 +1,5 @@ /* - * Copyright (C) 2015-2022 Département de l'Instruction Publique (DIP-SEM) - * - * Copyright (C) 2013 Open Education Foundation - * - * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour - * l'Education Numérique en Afrique (GIP ENA) + * Copyright (C) 2015-2025 Département de l'Instruction Publique (DIP-SEM) * * This file is part of OpenBoard. * @@ -24,31 +19,30 @@ * along with OpenBoard. If not, see . */ -#ifndef UBFOREIGHNOBJECTSHANDLER_H -#define UBFOREIGHNOBJECTSHANDLER_H + +#pragma once #include #include -#include -class UBForeighnObjectsHandlerPrivate; +#include "domain/UBItem.h" -class UBForeighnObjectsHandler +class UBMediaAssetItem : public UBItem { +protected: + UBMediaAssetItem() = default; + public: - UBForeighnObjectsHandler(); - ~UBForeighnObjectsHandler(); + virtual ~UBMediaAssetItem() = default; - void cure(const QList &dirs); - void cure(const QUrl &dir); + virtual QList mediaAssets() const = 0; + virtual void setMediaAsset(const QString& documentPath, const QString& mediaAsset) = 0; - void copyPage(const QUrl &fromDir, int fromIndex, - const QUrl &toDir, int toIndex); + QUuid mediaAssetUuid() const; -private: - UBForeighnObjectsHandlerPrivate *d; + static QUuid createMediaAssetUuid(const QByteArray& data); + static QUuid createMediaAssetUuid(const QString& path); - friend class UBForeighnObjectsHandlerPrivate; +protected: + QUuid uuidFromPath(const QString& path) const; }; - -#endif // UBFOREIGHNOBJECTSHANDLER_H diff --git a/src/domain/domain.pri b/src/domain/domain.pri index 94603caab..0776fe94d 100644 --- a/src/domain/domain.pri +++ b/src/domain/domain.pri @@ -8,6 +8,7 @@ HEADERS += src/domain/UBGraphicsScene.h \ src/domain/UBGraphicsSvgItem.h \ src/domain/UBGraphicsPolygonItem.h \ src/domain/UBItem.h \ + src/domain/UBMediaAssetItem.h \ src/domain/UBGraphicsWidgetItem.h \ src/domain/UBGraphicsPDFItem.h \ src/domain/UBGraphicsTextItem.h \ @@ -37,6 +38,7 @@ SOURCES += src/domain/UBGraphicsScene.cpp \ src/domain/UBGraphicsSvgItem.cpp \ src/domain/UBGraphicsPolygonItem.cpp \ src/domain/UBItem.cpp \ + src/domain/UBMediaAssetItem.cpp \ src/domain/UBGraphicsWidgetItem.cpp \ src/domain/UBGraphicsPDFItem.cpp \ src/domain/UBGraphicsTextItem.cpp \ diff --git a/src/frameworks/CMakeLists.txt b/src/frameworks/CMakeLists.txt index faa702e72..1b12387e2 100644 --- a/src/frameworks/CMakeLists.txt +++ b/src/frameworks/CMakeLists.txt @@ -3,6 +3,8 @@ target_sources(openboard PRIVATE UBBackgroundLoader.h UBBase32.cpp UBBase32.h + UBBlockingBuffer.cpp + UBBlockingBuffer.h UBCoreGraphicsScene.cpp UBCoreGraphicsScene.h UBCryptoUtils.cpp @@ -21,6 +23,10 @@ target_sources(openboard PRIVATE if(CMAKE_SYSTEM_NAME STREQUAL Linux) target_sources(openboard PRIVATE + UBDesktopPortal.cpp + UBDesktopPortal.h + UBPipewireSink.cpp + UBPipewireSink.h UBPlatformUtils_linux.cpp ) endif() diff --git a/src/frameworks/UBBackgroundLoader.cpp b/src/frameworks/UBBackgroundLoader.cpp index f27d0e773..fc19b4a2b 100644 --- a/src/frameworks/UBBackgroundLoader.cpp +++ b/src/frameworks/UBBackgroundLoader.cpp @@ -22,106 +22,126 @@ #include "UBBackgroundLoader.h" +#include #include +#include +#include +#include -#include "core/UBApplication.h" +#include "frameworks/UBBlockingBuffer.h" -UBBackgroundLoader::UBBackgroundLoader(QObject* parent) - : QThread{parent} +UBBackgroundLoader::UBBackgroundLoader(ResultType resultType, QObject* parent) + : QObject{parent} + , mResultType{resultType} { -} + mWatcher = new QFutureWatcher>; + mWatcher->setPendingResultsLimit(2); + mWatcherThread = new QThread{this}; + mWatcher->moveToThread(mWatcherThread); -UBBackgroundLoader::UBBackgroundLoader(QList> paths, QObject* parent) - : QThread{parent} -{ - mPaths.insert(mPaths.cend(), paths.constBegin(), paths.constEnd()); - mPathCounter.release(paths.size()); + mBlockingBuffer = new UBBlockingBuffer{this}; + mBlockingBuffer->setWatcher(mWatcher); + + connect(mBlockingBuffer, &UBBlockingBuffer::resultAvailable, this, &UBBackgroundLoader::resultAvailable); + connect(mBlockingBuffer, &UBBlockingBuffer::finished, this, &UBBackgroundLoader::finished); + + connect(mWatcher, &QFutureWatcher>::finished, mWatcherThread, &QThread::quit); + + mBlockingBuffer->start(); + mWatcherThread->start(); } UBBackgroundLoader::~UBBackgroundLoader() { + qDebug() << "Destruct UBBackgroundLoader"; + mWatcher->cancel(); + mWatcherThread->quit(); + mBlockingBuffer->halt(); + mWatcherThread->wait(); + mBlockingBuffer->wait(); abort(); - wait(); -} -bool UBBackgroundLoader::isIdle() -{ - QMutexLocker lock{&mMutex}; - return mPaths.empty() && mResults.empty(); + delete mWatcher; } -bool UBBackgroundLoader::isResultAvailable() +void UBBackgroundLoader::load(const QList>& paths, int maxBytes, std::function preCheck) { - QMutexLocker lock{&mMutex}; - return !mResults.empty(); +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + mFuture = QtConcurrent::mapped(paths, ReadData{mResultType, maxBytes, preCheck}); +#else + // use a separate thread pool for each loader so that tasks are interwoven between loaders + auto threadPool = new QThreadPool{this}; + threadPool->setMaxThreadCount(4); + mFuture = QtConcurrent::mapped(threadPool, paths, ReadData{mResultType, maxBytes, preCheck}); +#endif + + mWatcher->setFuture(mFuture); + + qDebug() << "UBBackgroundLoader: Start loading" << paths.at(0).second; } -std::pair UBBackgroundLoader::takeResult() +void UBBackgroundLoader::abort() { - QMutexLocker lock{&mMutex}; - - if (mResults.empty()) - { - return {}; - } - - const auto result = mResults.front(); - mResults.pop_front(); - return result; + mFuture.cancel(); } -void UBBackgroundLoader::start() +void UBBackgroundLoader::setKeepAlive(std::shared_ptr keepAlive) { - mRunning = true; - QThread::start(); + mKeepAlive = keepAlive; } -void UBBackgroundLoader::addPaths(QList> paths) +void UBBackgroundLoader::resultProcessed() { - QMutexLocker lock{&mMutex}; - mPaths.insert(mPaths.cend(), paths.constBegin(), paths.constEnd()); - mPathCounter.release(paths.size()); + mBlockingBuffer->resultProcessed(); } -void UBBackgroundLoader::abort() +UBBackgroundLoader::ReadData::ReadData(ResultType resultType, int maxBytes, std::function preCheck) + : mResultType{resultType} + , mMaxBytes{maxBytes} + , mPreCheck{preCheck} { - mRunning = false; - mPathCounter.release(); } -void UBBackgroundLoader::run() +UBBackgroundLoader::ReadData::result_type UBBackgroundLoader::ReadData::operator()(const std::pair& path) { - while (mRunning && !UBApplication::isClosing) + if (mPreCheck) { - mPathCounter.acquire(); + mPreCheck(path.first, path.second); + } - if (mRunning && !UBApplication::isClosing) - { - std::pair path; + QFile file{path.second}; + QVariant result; - { - QMutexLocker lock{&mMutex}; - path = mPaths.front(); - mPaths.pop_front(); - } + if (file.open(QFile::ReadOnly)) + { + QByteArray data; - QFile file{path.second}; - QByteArray result; + if (mMaxBytes < 0) + { + data = file.readAll(); + } + else + { + data = file.read(mMaxBytes); + } - if (file.open(QFile::ReadOnly)) - { - result = file.readAll(); - file.close(); - } + file.close(); - { - QMutexLocker lock{&mMutex}; - mResults.push_back({path.first, result}); - } + switch (mResultType) + { + case ByteArray: + result = data; + break; - emit resultAvailable(path.first, result); + case Pixmap: + { + QPixmap pixmap; + pixmap.loadFromData(data); + result = pixmap; + break; + } } } - quit(); + return {path.first, result}; } diff --git a/src/frameworks/UBBackgroundLoader.h b/src/frameworks/UBBackgroundLoader.h index 2fec80318..4b825ebf9 100644 --- a/src/frameworks/UBBackgroundLoader.h +++ b/src/frameworks/UBBackgroundLoader.h @@ -22,40 +22,59 @@ #pragma once -#include +#include #include -#include -#include -#include +#include -class UBBackgroundLoader : public QThread +// forward +class UBBlockingBuffer; + + +class UBBackgroundLoader : public QObject { Q_OBJECT public: - explicit UBBackgroundLoader(QObject* parent = nullptr); - UBBackgroundLoader(QList> paths, QObject* parent = nullptr); + enum ResultType { + ByteArray, + Pixmap + }; + + UBBackgroundLoader(ResultType resultType, QObject* parent = nullptr); virtual ~UBBackgroundLoader(); - bool isIdle(); - bool isResultAvailable(); - std::pair takeResult(); + void load(const QList>& paths, int maxBytes = -1, std::function preCheck = nullptr); + void abort(); + void setKeepAlive(std::shared_ptr keepAlive); public slots: - void start(); - void addPaths(QList> paths); - void abort(); + void resultProcessed(); signals: - void resultAvailable(int index, QByteArray data); + void resultAvailable(int index, const QVariant& data); + void finished(); + +private: + class ReadData + { + public: + typedef std::pair result_type; + + ReadData(ResultType resultType, int maxBytes, std::function preCheck); + result_type operator()(const std::pair& path); -protected: - void run() override; + private: + const ResultType mResultType{ByteArray}; + const int mMaxBytes{-1}; + std::function mPreCheck{}; + }; private: - std::deque> mPaths{}; - std::deque> mResults{}; - QMutex mMutex{}; - QSemaphore mPathCounter{}; - bool mRunning{false}; + const ResultType mResultType{ByteArray}; + QFuture> mFuture; + int mIndex{0}; + QFutureWatcher>* mWatcher{nullptr}; + std::shared_ptr mKeepAlive; + UBBlockingBuffer* mBlockingBuffer{nullptr}; + QThread* mWatcherThread{nullptr}; }; diff --git a/src/frameworks/UBBlockingBuffer.cpp b/src/frameworks/UBBlockingBuffer.cpp new file mode 100644 index 000000000..85f4959ad --- /dev/null +++ b/src/frameworks/UBBlockingBuffer.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2015-2025 Département de l'Instruction Publique (DIP-SEM) + * + * This file is part of OpenBoard. + * + * OpenBoard 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, + * 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 OpenBoard. If not, see . + */ + + +#include "UBBlockingBuffer.h" + + +UBBlockingBuffer::UBBlockingBuffer(QObject* parent) + : QThread{parent} +{ +} + +void UBBlockingBuffer::setWatcher(QFutureWatcher>* watcher) +{ + mWatcher = watcher; + + connect(mWatcher, &QFutureWatcher>::resultReadyAt, this, &UBBlockingBuffer::addResult, + Qt::DirectConnection); + connect(mWatcher, &QFutureWatcher>::finished, this, &UBBlockingBuffer::watcherFinished, + Qt::DirectConnection); +} + +void UBBlockingBuffer::addResult(int index) +{ + if (!isInterruptionRequested()) + { + mAvailableSpace.acquire(); + + if (mWatcher) + { + const auto result = mWatcher->resultAt(index); + + QMutexLocker lock{&mMutex}; + mBuffer.emplace_back(result.first, result.second); + mAvailableResults.release(); + } + } +} + +void UBBlockingBuffer::resultProcessed() +{ + mAvailableSpace.release(); +} + +void UBBlockingBuffer::watcherFinished() +{ + if (!isInterruptionRequested()) + { + // add a "finished" marker to the buffer + mAvailableSpace.acquire(); + QMutexLocker lock{&mMutex}; + mBuffer.emplace_back(-1, QByteArray{}); + mAvailableResults.release(); + } +} + +void UBBlockingBuffer::halt() +{ + requestInterruption(); + + mAvailableResults.release(); + mAvailableSpace.release(999); // last results and finish marker + mWatcher = nullptr; +} + +void UBBlockingBuffer::run() +{ + while (true) + { + mAvailableResults.acquire(); + QMutexLocker lock{&mMutex}; + + if (isInterruptionRequested()) + { + return; + } + + const auto result = mBuffer.front(); + mBuffer.pop_front(); + + if (result.first >= 0) + { + emit resultAvailable(result.first, result.second); + } + else + { + emit finished(); + } + } +} diff --git a/src/adaptors/UBExportCFF.h b/src/frameworks/UBBlockingBuffer.h similarity index 52% rename from src/adaptors/UBExportCFF.h rename to src/frameworks/UBBlockingBuffer.h index dc79a2a9a..70b26696b 100644 --- a/src/adaptors/UBExportCFF.h +++ b/src/frameworks/UBBlockingBuffer.h @@ -1,10 +1,5 @@ /* - * Copyright (C) 2015-2022 Département de l'Instruction Publique (DIP-SEM) - * - * Copyright (C) 2013 Open Education Foundation - * - * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour - * l'Education Numérique en Afrique (GIP ENA) + * Copyright (C) 2015-2025 Département de l'Instruction Publique (DIP-SEM) * * This file is part of OpenBoard. * @@ -25,29 +20,40 @@ */ -#ifndef UBExportCFF_H_ -#define UBExportCFF_H_ - -#include +#pragma once -#include "UBExportAdaptor.h" +#include +#include +#include +#include -#include "frameworks/UBFileSystemUtils.h" +#include -class UBDocumentProxy; - -class UBExportCFF : public UBExportAdaptor +class UBBlockingBuffer : public QThread { Q_OBJECT public: - UBExportCFF(QObject *parent = 0); - virtual ~UBExportCFF(); - - virtual QString exportName(); - virtual QString exportExtention(); - virtual void persist(std::shared_ptr pDocument); - virtual bool associatedActionactionAvailableFor(const QModelIndex &selectedIndex); + UBBlockingBuffer(QObject* parent = nullptr); + void setWatcher(QFutureWatcher>* watcher); + +public slots: + void addResult(int index); + void resultProcessed(); + void watcherFinished(); + void halt(); + +signals: + void resultAvailable(int index, const QVariant& data); + void finished(); + +protected: + void run() override; + +private: + QFutureWatcher>* mWatcher; + QSemaphore mAvailableResults; + QSemaphore mAvailableSpace{1}; + QMutex mMutex; + std::deque> mBuffer; }; - -#endif /* UBExportCFF_H_ */ diff --git a/src/frameworks/UBCoreGraphicsScene.cpp b/src/frameworks/UBCoreGraphicsScene.cpp index 1fe934ab3..f34e41ef9 100644 --- a/src/frameworks/UBCoreGraphicsScene.cpp +++ b/src/frameworks/UBCoreGraphicsScene.cpp @@ -29,8 +29,6 @@ #include "UBCoreGraphicsScene.h" -#include "domain/UBGraphicsMediaItem.h" -#include "domain/UBGraphicsWidgetItem.h" #include "domain/UBGraphicsGroupContainerItem.h" #include "core/memcheck.h" @@ -72,11 +70,25 @@ void UBCoreGraphicsScene::addItem(QGraphicsItem* item) if (item->scene() != this) QGraphicsScene::addItem(item); + + UBItem* ubitem = dynamic_cast(item); + + if (ubitem) + { + mItemsByUuid[ubitem->uuid()] = item; + } } void UBCoreGraphicsScene::removeItem(QGraphicsItem* item, bool forceDelete) { + UBItem* ubitem = dynamic_cast(item); + + if (ubitem) + { + mItemsByUuid.remove(ubitem->uuid()); + } + QGraphicsScene::removeItem(item); if (forceDelete) { @@ -88,10 +100,6 @@ bool UBCoreGraphicsScene::deleteItem(QGraphicsItem* item) { if(mItemsToDelete.contains(item)) { - UBGraphicsItem *item_casted = dynamic_cast(item); - if (item_casted != NULL) - item_casted->clearSource(); - mItemsToDelete.remove(item); delete item; item = NULL; @@ -114,3 +122,8 @@ void UBCoreGraphicsScene::addItemToDeletion(QGraphicsItem *item) mItemsToDelete.insert(item); } } + +QGraphicsItem* UBCoreGraphicsScene::itemForUuid(const QUuid& uuid) const +{ + return mItemsByUuid.value(uuid, nullptr); +} diff --git a/src/frameworks/UBCoreGraphicsScene.h b/src/frameworks/UBCoreGraphicsScene.h index 4df05d1fa..c010d87b3 100644 --- a/src/frameworks/UBCoreGraphicsScene.h +++ b/src/frameworks/UBCoreGraphicsScene.h @@ -33,6 +33,7 @@ #include #include #include +#include class UBCoreGraphicsScene : public QGraphicsScene { @@ -59,9 +60,11 @@ class UBCoreGraphicsScene : public QGraphicsScene mIsModified = pModified; } + QGraphicsItem* itemForUuid(const QUuid& uuid) const; private: QSet mItemsToDelete; + QHash mItemsByUuid; bool mIsModified; }; diff --git a/src/frameworks/UBDesktopPortal.cpp b/src/frameworks/UBDesktopPortal.cpp new file mode 100644 index 000000000..468630065 --- /dev/null +++ b/src/frameworks/UBDesktopPortal.cpp @@ -0,0 +1,578 @@ +/* + * Copyright (C) 2015-2025 Département de l'Instruction Publique (DIP-SEM) + * + * This file is part of OpenBoard. + * + * OpenBoard 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, + * 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 OpenBoard. If not, see . + */ + + +#include "UBDesktopPortal.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "board/UBBoardView.h" +#include "core/UBApplication.h" +#include "core/UBApplicationController.h" +#include "core/UBSettings.h" +#include "desktop/UBDesktopAnnotationController.h" +#include "frameworks/UBPlatformUtils.h" + + +enum : uint { MONITOR = 1, WINDOW = 2, VIRTUAL = 4 } SourceType; +enum : uint { HIDDEN = 1, EMBEDDED = 2, METADATA = 4 } CursorMode; +enum : uint { TRANSIENT = 0, APPLICATION = 1, PERSISTENT = 2 } PersistMode; + +Q_DECLARE_METATYPE(UBDesktopPortal::Stream) +Q_DECLARE_METATYPE(UBDesktopPortal::Streams) + +QPixmap UBDesktopPortal::loadScreenshotFromUri(const QUrl& uri) const +{ + if (uri.isEmpty()) + { + return {}; + } + + QFile file(uri.toLocalFile()); + + if (!file.exists()) + { + qDebug() << "Screenshot image file does not exist" << uri; + return {}; + } + + QPixmap pixmap{file.fileName()}; + file.remove(); + qDebug() << "DesktopPortal: loaded screenshot file" << uri; + return pixmap; +} + +QPixmap UBDesktopPortal::readClipboardScreenshot() const +{ + auto clipboard = QGuiApplication::clipboard(); + + if (!clipboard) + { + qDebug() << "DesktopPortal: no clipboard available"; + return {}; + } + + QImage cbImage = clipboard->image(); + + if (!cbImage.isNull()) + { + QPixmap fromImage = QPixmap::fromImage(cbImage); + qDebug() << "DesktopPortal: clipboard image found" << "size" << fromImage.size(); + return fromImage; + } + + QPixmap cbPixmap = clipboard->pixmap(); + + if (!cbPixmap.isNull()) + { + qDebug() << "DesktopPortal: clipboard pixmap found" << "size" << cbPixmap.size(); + return cbPixmap; + } + + qDebug() << "DesktopPortal: clipboard empty"; + return {}; +} + + +QDBusArgument &operator << (QDBusArgument &arg, const UBDesktopPortal::Stream &stream) +{ + arg.beginStructure(); + arg << stream.node_id; + + arg.beginMap(qMetaTypeId(), qMetaTypeId()); + for (auto it = stream.map.cbegin(); it != stream.map.cend(); ++it) + { + arg.beginMapEntry(); + arg << it.key() << QDBusVariant(it.value()); + arg.endMapEntry(); + } + arg.endMap(); + + arg.endStructure(); + return arg; +} + +const QDBusArgument &operator >> (const QDBusArgument &arg, UBDesktopPortal::Stream &stream) +{ + arg.beginStructure(); + arg >> stream.node_id; + + arg.beginMap(); + + while (!arg.atEnd()) + { + QString key; + QDBusVariant value; + arg.beginMapEntry(); + arg >> key >> value; + arg.endMapEntry(); + stream.map.insert(key, value.variant()); + } + + arg.endMap(); + arg.endStructure(); + + return arg; +} + +QDBusArgument &operator << (QDBusArgument &arg, const UBDesktopPortal::Streams &streams) +{ + arg.beginArray(qMetaTypeId()); + for (const auto &stream : streams) + { + arg << stream; + } + arg.endArray(); + return arg; +} + + +const QDBusArgument &operator >> (const QDBusArgument &arg, UBDesktopPortal::Streams &streams) +{ + streams.clear(); + arg.beginArray(); + while (!arg.atEnd()) + { + UBDesktopPortal::Stream stream; + arg >> stream; + streams.append(stream); + } + arg.endArray(); + return arg; +} + +UBDesktopPortal::UBDesktopPortal(QObject* parent) + : QObject{parent} +{ + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); +} + +UBDesktopPortal::~UBDesktopPortal() +{ + if (mScreencastPortal) + { + delete mScreencastPortal; + } +} + +void UBDesktopPortal::grabScreen(QScreen* screen, const QRect& rect) +{ + mScreenRect = screen->geometry(); + mInteractiveScreenshot = false; + + if (!rect.isNull()) + { + mScreenRect = rect.translated(mScreenRect.topLeft()); + } + + qDebug() << "DesktopPortal: grabScreen" + << "screen" << (screen ? screen->name() : QStringLiteral("")) + << "geom" << mScreenRect + << "rect" << rect; + + QDBusInterface screenshotPortal("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop", "org.freedesktop.portal.Screenshot"); + + if (screenshotPortal.isValid()) + { + QMap options; + options["handle_token"] = createRequestToken(); + options["interactive"] = true; // required by some portals (e.g. GNOME) + mInteractiveScreenshot = true; + QDBusReply reply = screenshotPortal.call("Screenshot", "", options); + QDBusObjectPath objectPath = reply.value(); + QString path = objectPath.path(); + qDebug() << "DesktopPortal: Screenshot reply valid" << reply.isValid() << "path" << path << "options" << options; + + if (!reply.isValid()) + { + qWarning() << "DesktopPortal: Screenshot call failed" << reply.error(); + emit screenGrabbed(QPixmap{}); + return; + } + + const bool connected = QDBusConnection::sessionBus().connect( + "", + path, + "org.freedesktop.portal.Request", + "Response", + "ua{sv}", + this, + SLOT(handleScreenshotResponse(uint,QMap))); + + if (!connected) + { + qWarning() << "DesktopPortal: failed to connect to screenshot response signal"; + } + } + else + { + qDebug() << "No valid screenshot portal"; + emit screenGrabbed(QPixmap{}); + } +} + +void UBDesktopPortal::startScreenCast(bool withCursor) +{ + mWithCursor = withCursor; + + QDBusInterface* portal = screencastPortal(); + + if (!portal) + { + return; + } + + // Create ScreenCast session + QString requestToken = createRequestToken(); + QMap options; + options["session_handle_token"] = createSessionToken(); + options["handle_token"] = requestToken; + + QDBusConnection::sessionBus().connect("", mRequestPath + requestToken, "org.freedesktop.portal.Request", "Response", "ua{sv}", this, + SLOT(handleCreateSessionResponse(uint,QMap))); + + const QDBusReply reply = portal->call("CreateSession", options); + + if (!reply.isValid()) + { + qWarning() << "Couldn't get reply to ScreenCast/CreateSession"; + qWarning() << "Error: " << reply.error().message(); + emit screenCastAborted(); + return; + } +} + +void UBDesktopPortal::stopScreenCast() +{ + if (mSession.isEmpty()) + { + return; + } + + QDBusInterface portal("org.freedesktop.portal.Desktop", mSession, "org.freedesktop.portal.Session"); + + if (portal.isValid()) + { + const QDBusReply reply = portal.call("Close"); + + if (!reply.isValid()) + { + qWarning() << "Couldn't get reply to ScreenCast/Close"; + qWarning() << "Error: " << reply.error().message(); + } + } + + mSession.clear(); +} + +void UBDesktopPortal::handleScreenshotResponse(uint code, const QMap& results) +{ + qDebug() << "DesktopPortal: handleScreenshotResponse code" << code << "results" << results; + + QPixmap pixmap; + auto finalizeScreenshot = [this](const QPixmap& pixmap){ + if (pixmap.isNull()) + { + qDebug() << "DesktopPortal: screenshot pixmap still null, aborting"; + emit screenGrabbed(QPixmap{}); + return; + } + + QPixmap screenshot; + + if (mInteractiveScreenshot) + { + // portal already applied the selection + screenshot = pixmap; + } + else + { + QRect targetRect = mScreenRect; + targetRect.moveTo(0, 0); // portal images are local to the grab + targetRect &= pixmap.rect(); + screenshot = pixmap.copy(targetRect); + qDebug() << "DesktopPortal: cropping screenshot" << "targetRect" << targetRect; + } + + qDebug() << "DesktopPortal: emitting screenshot" + << "interactive" << mInteractiveScreenshot + << "source size" << pixmap.size() + << "final size" << screenshot.size(); + emit screenGrabbed(screenshot); + }; + + const QUrl uri(results.value("uri").toUrl()); + qDebug() << "DesktopPortal: screenshot URI" << uri; + + pixmap = loadScreenshotFromUri(uri); + + // GNOME may only place the shot on the clipboard when capturing a full screen + if (pixmap.isNull()) + { + pixmap = readClipboardScreenshot(); + } + + if (pixmap.isNull()) + { + // Clipboard might not be populated yet on some portals (e.g. GNOME fullscreen); retry shortly. + QTimer::singleShot(500, this, [this, finalizeScreenshot](){ + QPixmap delayed = readClipboardScreenshot(); + finalizeScreenshot(delayed); + }); + return; + } + + finalizeScreenshot(pixmap); +} + +void UBDesktopPortal::handleCreateSessionResponse(uint response, const QVariantMap& results) +{ + if (response != 0) + { + qWarning() << "Failed to create session: " << response << results; + mSession.clear(); + emit screenCastAborted(); + return; + } + + mSession = results.value("session_handle").toString(); + + QDBusInterface* portal = screencastPortal(); + + if (!portal) + { + return; + } + + // Select sources + QString requestToken = createRequestToken(); + QMap options; + options["multiple"] = false; + options["types"] = MONITOR; + options["cursor_mode"] = mWithCursor ? EMBEDDED : HIDDEN; + options["handle_token"] = requestToken; + + if (mSupportsPersistentScreencast) + { + options["persist_mode"] = PERSISTENT; // restore token valid across application restart + + auto restoreToken = UBSettings::settings()->value("App/ScreenCastRestoreToken"); + + if (restoreToken.isValid()) + { + options["restore_token"] = restoreToken; + } + } + + // connect before call + QDBusConnection::sessionBus().connect("", mRequestPath + requestToken, "org.freedesktop.portal.Request", "Response", "ua{sv}", this, + SLOT(handleSelectSourcesResponse(uint,QMap))); + + const QDBusReply reply = portal->call("SelectSources", QDBusObjectPath(mSession), options); + + if (!reply.isValid()) + { + qWarning() << "Couldn't get reply to ScreenCast/SelectSources"; + qWarning() << "Error: " << reply.error().message(); + emit screenCastAborted(); + return; + } +} + +void UBDesktopPortal::handleSelectSourcesResponse(uint response, const QVariantMap& results) +{ + Q_UNUSED(results); + + if (response != 0) + { + qWarning() << "Failed to select sources: " << response; + mSession.clear(); + emit screenCastAborted(); + return; + } + + QDBusInterface* portal = screencastPortal(); + + if (!portal) + { + return; + } + + // Start ScreenCast + QString requestToken = createRequestToken(); + QMap options; + options["handle_token"] = requestToken; + + QDBusConnection::sessionBus().connect("", mRequestPath + requestToken, "org.freedesktop.portal.Request", "Response", "ua{sv}", this, + SLOT(handleStartResponse(uint,QMap))); + + const QDBusReply reply = portal->call("Start", QDBusObjectPath(mSession), "", options); + + if (!reply.isValid()) + { + qWarning() << "Couldn't get reply to ScreenCast/Start"; + qWarning() << "Error: " << reply.error().message(); + emit screenCastAborted(); + return; + } + + // Hide annotation drawing view in desktop mode so that portal dialog is topmost + showGlassPane(false); +} + +void UBDesktopPortal::handleStartResponse(uint response, const QVariantMap& results) +{ + // Show annotation drawing view in desktop mode after portal dialog was closed + showGlassPane(true); + + if (response != 0) + { + // The system Desktop dialog was canceled + qDebug() << "Failed to start or cancel dialog: " << response; + mSession.clear(); + emit screenCastAborted(); + return; + } + + // save restore token + const QVariant restoreTokenVariant = results.value("restore_token"); + QString restoreToken; + + if (restoreTokenVariant.canConvert()) + { + restoreToken = restoreTokenVariant.value().variant().toString(); + } + else + { + restoreToken = restoreTokenVariant.toString(); + } + + UBSettings::settings()->setValue("App/ScreenCastRestoreToken", restoreToken); + + // invalidate token when screen configuration changes + static bool connected{false}; + + if (!connected) + { + connected = true; + // don't use the four argument version of connect as this will be disconnected + // when the context object is destroyed + connect(UBSettings::settings()->appScreenList, &UBSetting::changed, [](){ + UBSettings::settings()->setValue("App/ScreenCastRestoreToken", {}); + }); + } + + const Streams streams = qdbus_cast(results.value("streams")); + const Stream stream = streams.last(); + + QDBusInterface* portal = screencastPortal(); + + if (!portal) + { + return; + } + + // Open PipeWire Remote + QMap options; + const QDBusReply reply = portal->call("OpenPipeWireRemote", QDBusObjectPath(mSession), options); + + if (!reply.isValid()) + { + qWarning() << "Couldn't get reply to ScreenCast/OpenPipeWireRemote"; + qWarning() << "Error: " << reply.error().message(); + emit screenCastAborted(); + return; + } + + const int fd = reply.value().fileDescriptor(); + emit streamStarted(fd, stream.node_id); +} + +QDBusInterface* UBDesktopPortal::screencastPortal() +{ + if (!mScreencastPortal) + { + mScreencastPortal = new QDBusInterface("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop", + "org.freedesktop.portal.ScreenCast"); + mScreencastPortal->setParent(this); + + QString baseService = QDBusConnection::sessionBus().baseService(); + baseService.remove(0, 1); + baseService.replace('.', '_'); + mRequestPath = "/org/freedesktop/portal/desktop/request/" + baseService + "/"; + + const QVariant version = mScreencastPortal->property("version"); + mScreencastPortalVersion = version.isValid() ? version.toUInt() : 0; + mSupportsPersistentScreencast = mScreencastPortalVersion >= 4; + + qDebug() << "request path" << mRequestPath << "portal version" << mScreencastPortalVersion + << "persistent support" << mSupportsPersistentScreencast; + } + + if (mScreencastPortal->isValid()) + { + return mScreencastPortal; + } + + emit screenCastAborted(); + return nullptr; +} + +QString UBDesktopPortal::createSessionToken() const +{ + static int sessionTokenCounter = 0; + + sessionTokenCounter += 1; + return QString("obsess%1").arg(sessionTokenCounter); +} + +QString UBDesktopPortal::createRequestToken() const +{ + static int requestTokenCounter = 0; + + requestTokenCounter += 1; + return QString("obreq%1").arg(requestTokenCounter); +} + +void UBDesktopPortal::showGlassPane(bool show) const +{ + if (UBApplication::applicationController->isShowingDesktop()) + { + UBApplication::applicationController->uninotesController()->drawingView()->setVisible(show); + + if (show) + { + UBPlatformUtils::keepOnTop(); + } + } +} diff --git a/src/frameworks/UBDesktopPortal.h b/src/frameworks/UBDesktopPortal.h new file mode 100644 index 000000000..1be0c6bef --- /dev/null +++ b/src/frameworks/UBDesktopPortal.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2015-2025 Département de l'Instruction Publique (DIP-SEM) + * + * This file is part of OpenBoard. + * + * OpenBoard 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, + * 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 OpenBoard. If not, see . + */ + + +#pragma once + +#include +#include +#include +#include +#include + +// forward +class QDBusInterface; +class QScreen; + +class UBDesktopPortal : public QObject +{ + Q_OBJECT + +public: + typedef struct + { + uint node_id; + QVariantMap map; + } Stream; + typedef QList Streams; + + explicit UBDesktopPortal(QObject* parent = nullptr); + virtual ~UBDesktopPortal(); + + void grabScreen(QScreen* screen, const QRect& rect = {}); + +public slots: + void startScreenCast(bool withCursor); + void stopScreenCast(); + +signals: + void screenGrabbed(QPixmap pixmap); + void streamStarted(int fd, int nodeId); + void screenCastAborted(); + +private slots: + void handleScreenshotResponse(uint code, const QMap& results); + void handleCreateSessionResponse(uint response, const QVariantMap& results); + void handleSelectSourcesResponse(uint response, const QVariantMap& results); + void handleStartResponse(uint response, const QVariantMap& results); + +private: + QDBusInterface* screencastPortal(); + QString createSessionToken() const; + QString createRequestToken() const; + void showGlassPane(bool show) const; + QPixmap loadScreenshotFromUri(const QUrl& uri) const; + QPixmap readClipboardScreenshot() const; + +private: + QRect mScreenRect; + bool mWithCursor{false}; + bool mInteractiveScreenshot{false}; + QString mSession; + QString mRequestPath; + uint mScreencastPortalVersion{0}; + bool mSupportsPersistentScreencast{false}; + QDBusInterface* mScreencastPortal{nullptr}; +}; diff --git a/src/frameworks/UBFileSystemUtils.cpp b/src/frameworks/UBFileSystemUtils.cpp index 2791fd3ef..54e128b9c 100644 --- a/src/frameworks/UBFileSystemUtils.cpp +++ b/src/frameworks/UBFileSystemUtils.cpp @@ -31,7 +31,7 @@ #include -#include "core/UBApplication.h" +#include "adaptors/UBPageMapper.h" #include "globals/UBGlobals.h" @@ -475,14 +475,10 @@ QString UBFileSystemUtils::mimeTypeFromFileName(const QString& fileName) if (ext == "wmx") return "video/x-ms-wmx"; if (ext == "avi") return "video/x-msvideo"; if (ext == "ogv") return "video/ogg"; - if (ext == "flv") return "video/x-flv"; // TODO UB 4.x ... we need to be smarter ... flash may need an external plugin :-( if (ext == "m4v") return "video/x-m4v"; // W3C widget if (ext == "wgt") return "application/widget"; if (ext == "wgs") return "application/search"; - // Apple widget - if (ext == "wdgt") return "application/vnd.apple-widget"; //mime type invented by us :-( // NOTE @letsfindaway obsolete - if (ext == "swf") return "application/x-shockwave-flash"; if (ext == "rdf") return "application/openboard-document"; return ""; @@ -555,8 +551,6 @@ QString UBFileSystemUtils::fileExtensionFromMimeType(const QString& pMimeType) if (pMimeType == "video/x-flv") return "flv"; if (pMimeType == "video/x-m4v") return "m4v"; if (pMimeType == "application/widget") return "wgt"; - if (pMimeType == "application/vnd.apple-widget") return "wdgt"; //mime type invented by us :-( - if (pMimeType == "application/x-shockwave-flash") return "swf"; return ""; @@ -584,10 +578,6 @@ UBMimeType::Enum UBFileSystemUtils::mimeTypeFromString(const QString& typeString { type = UBMimeType::Html; } - else if (typeString == "application/vnd.apple-widget") - { - type = UBMimeType::AppleWidget; - } else if (typeString == "application/widget") { type = UBMimeType::W3CWidget; @@ -600,10 +590,6 @@ UBMimeType::Enum UBFileSystemUtils::mimeTypeFromString(const QString& typeString { type = UBMimeType::Audio; } - else if (typeString.startsWith("application/x-shockwave-flash")) - { - type = UBMimeType::Flash; - } else if (typeString.startsWith("application/pdf")) { type = UBMimeType::PDF; @@ -657,7 +643,8 @@ QString UBFileSystemUtils::getFirstExistingFileFromList(const QString& path, con } -bool UBFileSystemUtils::compressDirInZip(const QDir& pDir, const QString& pDestPath, QuaZipFile *pOutZipFile, bool pRootDocumentFolder, UBProcessingProgressListener* progressListener) +bool UBFileSystemUtils::compressDirInZip(const QDir& pDir, const QString& pDestPath, QuaZipFile *pOutZipFile, + bool pRootDocumentFolder, UBPageMapper* mapper, UBProcessingProgressListener* progressListener) { QFileInfoList files = pDir.entryInfoList(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot); @@ -699,6 +686,16 @@ bool UBFileSystemUtils::compressDirInZip(const QDir& pDir, const QString& pDestP progressListener->processing(objectType, pageFiles.indexOf(file), pageFiles.size()); } + auto outFilename = file.fileName(); + + // map file + if (mapper) + { + auto result = mapper->map(file.fileName()); + file = result.input; + outFilename = result.output; + } + QFile inFile(file.absoluteFilePath()); if(!inFile.open(QIODevice::ReadOnly)) { @@ -708,7 +705,7 @@ bool UBFileSystemUtils::compressDirInZip(const QDir& pDir, const QString& pDestP qDebug() << "will open" << pDestPath << file.fileName() << inFile.fileName(); - if(!pOutZipFile->open(QIODevice::WriteOnly, QuaZipNewInfo(pDestPath + file.fileName(), inFile.fileName()))) + if(!pOutZipFile->open(QIODevice::WriteOnly, QuaZipNewInfo(pDestPath + outFilename, inFile.fileName()))) { qWarning() << "Compression of file" << inFile.fileName() << " failed. Cause: outFile.open(): " << pOutZipFile->getZipError(); inFile.close(); @@ -791,7 +788,11 @@ bool UBFileSystemUtils::expandZipToDir(const QFile& pZipFile, const QDir& pTarge root.mkpath(newFileInfo.absolutePath()); out.setFileName(newFileName); - out.open(QIODevice::WriteOnly); + if (!out.open(QIODevice::WriteOnly)) + { + qWarning() << "ZIP write failed. Cause: file.open(): " << out.fileName(); + return false; + } // Slow like hell (on GNU/Linux at least), but it is not my fault. // Not ZIP/UNZIP package's fault either. diff --git a/src/frameworks/UBFileSystemUtils.h b/src/frameworks/UBFileSystemUtils.h index 67b844a6d..56a37e528 100644 --- a/src/frameworks/UBFileSystemUtils.h +++ b/src/frameworks/UBFileSystemUtils.h @@ -36,6 +36,7 @@ #include "core/UB.h" class QuaZipFile; +class UBPageMapper; class UBProcessingProgressListener; class UBFileSystemUtils : public QObject @@ -104,7 +105,8 @@ class UBFileSystemUtils : public QObject * @return bool. true if compression is successful. */ static bool compressDirInZip(const QDir& pDir, const QString& pDestDir, QuaZipFile *pOutZipFile - , bool pRootDocumentFolder, UBProcessingProgressListener* progressListener = 0); + , bool pRootDocumentFolder, UBPageMapper* mapper = nullptr + , UBProcessingProgressListener* progressListener = nullptr); static bool expandZipToDir(const QFile& pZipFile, const QDir& pTargetDir); diff --git a/src/frameworks/UBPipewireSink.cpp b/src/frameworks/UBPipewireSink.cpp new file mode 100644 index 000000000..91139404c --- /dev/null +++ b/src/frameworks/UBPipewireSink.cpp @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2015-2025 Département de l'Instruction Publique (DIP-SEM) + * + * This file is part of OpenBoard. + * + * OpenBoard 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, + * 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 OpenBoard. If not, see . + */ + + +#include "UBPipewireSink.h" + +#include +#include + +#include +#include +#include +#include + +#include + + +UBPipewireSink::UBPipewireSink(QObject* parent) + : QObject{parent} +{ +} + +UBPipewireSink::~UBPipewireSink() +{ + qDebug() << "UBPipewireSink: stop threaded loop"; + pw_thread_loop_stop(mLoop); + pw_thread_loop_destroy(mLoop); +} + +bool UBPipewireSink::start(int fd, int nodeId) +{ + // stream event handler struct + // NOTE unused entries added for compatibility with some compilers + static const struct pw_stream_events streamEvents = { + .version = PW_VERSION_STREAM_EVENTS, + .destroy = nullptr, + .state_changed = [](void* userdata, enum pw_stream_state old, enum pw_stream_state state, const char* error){ + static_cast(userdata)->streamStateChanged(old, state, error); + }, + .control_info = nullptr, + .io_changed = nullptr, + .param_changed = [](void* userdata, uint32_t id, const struct spa_pod* param){ + static_cast(userdata)->streamParamChanged(id, param); + }, + .add_buffer = nullptr, + .remove_buffer = nullptr, + .process = [](void* userdata){ + static_cast(userdata)->process(); + }, + .drained = nullptr, + .command = nullptr, + .trigger_done = nullptr + }; + + qDebug() << "UBPipewireSink: start" << fd << nodeId; + + // Initialize PipeWire + pw_init(nullptr, nullptr); + + mLoop = pw_thread_loop_new(nullptr, nullptr); + + if (!mLoop) + { + qWarning() << "UBPipewireSink: unable to create main loop"; + return false; + } + + pw_thread_loop_lock(mLoop); + pw_thread_loop_start(mLoop); + + auto loop = pw_thread_loop_get_loop(mLoop); + + mContext = pw_context_new(loop, nullptr, 0); + + if (!mContext) + { + qWarning() << "UBPipewireSink: unable to create context"; + pw_thread_loop_destroy(mLoop); + return false; + } + + // connect context, duplicate fd before use, see + // https://chromium.googlesource.com/external/webrtc/+/master/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc#457 + // and https://doc.qt.io/qt-6/qdbusunixfiledescriptor.html#fileDescriptor + const auto fd2 = fcntl(fd, F_DUPFD_CLOEXEC, 0); + auto core = pw_context_connect_fd(mContext, fd2, nullptr, 0); + + if (!core) + { + qWarning() << "Cannot connect" << errno; + return false; + } + + // Create stream + struct pw_properties* props; + props = pw_properties_new( + PW_KEY_MEDIA_TYPE, "Video", + PW_KEY_MEDIA_CATEGORY, "Capture", + PW_KEY_MEDIA_ROLE, "Screen", + nullptr); + + mStream = pw_stream_new(core, "capture", props); + + if (!mStream) + { + qWarning() << "UBPipewireSink: unable to create stream"; + pw_context_destroy(mContext); + pw_thread_loop_destroy(mLoop); + return false; + } + + pw_stream_add_listener(mStream, &mHook, &streamEvents, this); + + // Set up stream parameters + const struct spa_pod* params[1]; + uint8_t buffer[1024]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + + const auto rDef = SPA_RECTANGLE(1024, 768); + const auto rMin = SPA_RECTANGLE(1, 1); + const auto rMax = SPA_RECTANGLE(4096, 4096); + const auto fDef = SPA_FRACTION(25, 1); + const auto fMin = SPA_FRACTION(0, 1); + const auto fMax = SPA_FRACTION(1000, 1); + + auto p = spa_pod_builder_add_object( + &b, + SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), + SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(7, + SPA_VIDEO_FORMAT_ARGB, + SPA_VIDEO_FORMAT_xRGB, + SPA_VIDEO_FORMAT_RGB, + SPA_VIDEO_FORMAT_RGBA, + SPA_VIDEO_FORMAT_RGBx, + SPA_VIDEO_FORMAT_BGRA, + SPA_VIDEO_FORMAT_BGRx), + SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(&rDef, &rMin, &rMax), + SPA_FORMAT_VIDEO_framerate, SPA_POD_CHOICE_RANGE_Fraction(&fDef, &fMin, &fMax)); + + params[0] = static_cast(p); + + // connect stream + // NOTE connection by nodeId is deprecated. But portal only provides this. + auto res = + pw_stream_connect(mStream, PW_DIRECTION_INPUT, nodeId, + pw_stream_flags(PW_STREAM_FLAG_AUTOCONNECT | // try to automatically connect this stream + PW_STREAM_FLAG_MAP_BUFFERS), // mmap the buffer data for us + params, 1); + + if (res < 0) + { + qWarning() << "UBPipewireSink: unable to connect stream" << res; + } + + pw_thread_loop_unlock(mLoop); + + return true; +} + +void UBPipewireSink::process() +{ + struct pw_buffer* b; + struct spa_buffer* buf; + + if ((b = pw_stream_dequeue_buffer(mStream)) == nullptr) + { + pw_log_warn("out of buffers: %m"); + return; + } + + buf = b->buffer; + if (buf->datas[0].data == NULL) + return; + + // create image from frame data + auto data = static_cast(buf->datas[0].data); + auto width = static_cast(mFormat.info.raw.size.width); + auto height = static_cast(mFormat.info.raw.size.height); + auto stride = buf->datas[0].chunk->stride; + + QImage image; + bool swapRgb{false}; + + switch (mFormat.info.raw.format) + { + case SPA_VIDEO_FORMAT_ARGB: + image = QImage(data, width, height, stride, QImage::Format_ARGB32); + break; + + case SPA_VIDEO_FORMAT_xRGB: + image = QImage(data, width, height, stride, QImage::Format_RGB32); + break; + + case SPA_VIDEO_FORMAT_RGB: + image = QImage(data, width, height, stride, QImage::Format_RGB888); + break; + + case SPA_VIDEO_FORMAT_RGBA: + image = QImage(data, width, height, stride, QImage::Format_RGBA8888); + break; + + case SPA_VIDEO_FORMAT_RGBx: + image = QImage(data, width, height, stride, QImage::Format_RGBX8888); + break; + + case SPA_VIDEO_FORMAT_BGRA: + image = QImage(data, width, height, stride, QImage::Format_RGBA8888); + swapRgb = true; + break; + + case SPA_VIDEO_FORMAT_BGRx: + image = QImage(data, width, height, stride, QImage::Format_RGBX8888); + swapRgb = true; + break; + + default: + qWarning() << "UBPipewireSink: unsupported image format"; + } + + if (swapRgb) + { +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + image.rgbSwap(); +#else + image = image.rgbSwapped(); +#endif + } + + emit gotImage(image); + + pw_stream_queue_buffer(mStream, b); +} + +void UBPipewireSink::streamStateChanged(pw_stream_state old, pw_stream_state state, const char* error) +{ + qDebug() << "UBPipewireSink: state changed" << state << error; + + if (old == PW_STREAM_STATE_STREAMING) + { + emit streamingInterrupted(); + } +} + +void UBPipewireSink::streamParamChanged(uint32_t id, const spa_pod* param) +{ + if (param == NULL || id != SPA_PARAM_Format) + return; + + if (spa_format_parse(param, &mFormat.media_type, &mFormat.media_subtype) < 0) + return; + + if (mFormat.media_type != SPA_MEDIA_TYPE_video || mFormat.media_subtype != SPA_MEDIA_SUBTYPE_raw) + return; + + if (spa_format_video_raw_parse(param, &mFormat.info.raw) < 0) + return; + + qDebug() << "got video format:"; + qDebug() << " format:" << mFormat.info.raw.format + << spa_debug_type_find_name(spa_type_video_format, mFormat.info.raw.format); + qDebug() << " size:" << mFormat.info.raw.size.width << mFormat.info.raw.size.height; + qDebug() << " framerate:" << mFormat.info.raw.framerate.num << "/" << mFormat.info.raw.framerate.denom; +} diff --git a/src/frameworks/UBPipewireSink.h b/src/frameworks/UBPipewireSink.h new file mode 100644 index 000000000..7b0fb1dde --- /dev/null +++ b/src/frameworks/UBPipewireSink.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2015-2025 Département de l'Instruction Publique (DIP-SEM) + * + * This file is part of OpenBoard. + * + * OpenBoard 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, + * 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 OpenBoard. If not, see . + */ + + +#pragma once + +#include +#include + +#include +#include +#include + + +class UBPipewireSink : public QObject +{ + Q_OBJECT + +public: + explicit UBPipewireSink(QObject* parent = nullptr); + ~UBPipewireSink(); + +public slots: + bool start(int fd, int nodeId); + +signals: + void gotImage(QImage image); + void streamingInterrupted(); + +private: + void process(); + void streamStateChanged(enum pw_stream_state old, enum pw_stream_state state, const char* error); + void streamParamChanged(uint32_t id, const struct spa_pod* param); + +private: + pw_thread_loop* mLoop{nullptr}; + pw_context* mContext{nullptr}; + pw_stream* mStream{nullptr}; + struct spa_video_info mFormat{}; + struct spa_hook mHook{}; +}; + diff --git a/src/frameworks/UBPlatformUtils.h b/src/frameworks/UBPlatformUtils.h index ee4b11790..b090c723c 100644 --- a/src/frameworks/UBPlatformUtils.h +++ b/src/frameworks/UBPlatformUtils.h @@ -35,6 +35,7 @@ #include class QMainWindow; +class QScreen; #define SYMBOL_KEYS_COUNT 47 @@ -212,12 +213,25 @@ class UBPlatformUtils static void setFrontProcess(); static void showFullScreen(QWidget * pWidget); static void showOSK(bool show); + static void grabScreen(QScreen* screen, std::function callback, QRect rect = {}); #ifdef Q_OS_OSX static void SetMacLocaleByIdentifier(const QString& id); static void toggleFinder(const bool on); static bool errorOpeningVirtualKeyboard; +#endif + enum SessionType { + UNKNOWN, + X11, + WAYLAND + }; + +#ifdef Q_OS_LINUX + static SessionType sessionType(); + static void keepOnTop(); +#else + static SessionType sessionType() { return UNKNOWN; } #endif }; diff --git a/src/frameworks/UBPlatformUtils_linux.cpp b/src/frameworks/UBPlatformUtils_linux.cpp index 6dde5010b..62e804ebb 100644 --- a/src/frameworks/UBPlatformUtils_linux.cpp +++ b/src/frameworks/UBPlatformUtils_linux.cpp @@ -35,8 +35,8 @@ #include #include -#include +#include "frameworks/UBDesktopPortal.h" #include "frameworks/UBFileSystemUtils.h" #include "core/UBApplication.h" #include "core/UBDisplayManager.h" @@ -117,12 +117,17 @@ void UBPlatformUtils::fadeDisplayIn() bool UBPlatformUtils::hasSystemOnScreenKeyboard() { - QProcess oskTestProcess; - oskTestProcess.start("which", QStringList() << "onboard"); + if (sessionType() == X11) + { + QProcess oskTestProcess; + oskTestProcess.start("which", QStringList() << "onboard"); + + return oskTestProcess.waitForFinished() && + oskTestProcess.exitStatus() == QProcess::NormalExit && + oskTestProcess.readAll() != ""; + } - return oskTestProcess.waitForFinished() && - oskTestProcess.exitStatus() == QProcess::NormalExit && - oskTestProcess.readAll() != ""; + return false; } QStringList UBPlatformUtils::availableTranslations() @@ -182,12 +187,7 @@ void UBPlatformUtils::hideMenuBarAndDock() } #define KEYBTDECL(s1, s2, clSwitch, code) KEYBT(QChar(s1), QChar(s2), clSwitch, 0, 0, KEYCODE(s1, code, 0), KEYCODE(s2, code, 1)) - -#define KEYBTDECLEX1(s1, s2, clSwitch, code, cs1, cs2) KEYBT(QChar(s1), QChar(s2), clSwitch, 0, 0, KEYCODE(cs1, code, 0), KEYCODE(cs2, code, 1)) -#define KEYBTDECLEX2(s1, s2, clSwitch, code, cs1, cs2) KEYBT(QChar(s1), QChar(s2), clSwitch, 0, 0, KEYCODE(cs1, code, 2), KEYCODE(cs2, code, 3)) - #define KEYBTDECLEX6(s1, s2, clSwitch, code, cs1, cs2, cs3, cs4, cs5, cs6) KEYBT(QChar(s1), QChar(s2), clSwitch, 0, 0, KEYCODE(cs1, code, 0), KEYCODE(cs2, code, 1), KEYCODE(cs3, code, 2), KEYCODE(cs4, code, 3), KEYCODE(cs5, code, 4), KEYCODE(cs6, code, 5)) - #define KEYBTDECLEX8(s1, s2, clSwitch, code, cs1, cs2, cs3, cs4, cs5, cs6, cs7, cs8) KEYBT(QChar(s1), QChar(s2), clSwitch, 0, 0, KEYCODE(cs1, code, 0), KEYCODE(cs2, code, 1), KEYCODE(cs3, code, 2), KEYCODE(cs4, code, 3), KEYCODE(cs5, code, 4), KEYCODE(cs6, code, 5), KEYCODE(cs7, code, 6), KEYCODE(cs8, code, 7)) @@ -245,10 +245,10 @@ KEYBT ENGLISH_LOCALE[] = { KEYBT RUSSIAN_LOCALE [] = { - /* ё Ё */ KEYBTDECLEX2(0x451, 0x401, true, 41, XK_Cyrillic_io, XK_Cyrillic_IO), + /* ё Ё */ KEYBTDECL(0x451, 0x401, true, 41), /* 1 ! */ KEYBTDECL(0x31, 0x21, false, 2), /* 2 " */ KEYBTDECL(0x32, 0x5c, false, 3), - /* 3 № */ KEYBTDECLEX1(0x33, 0x2116, false, 4, 0x33, XK_numerosign), + /* 3 № */ KEYBTDECL(0x33, 0x2116, false, 4), /* 4 ; */ KEYBTDECL(0x34, 0x3b, false, 5), /* 5 % */ KEYBTDECL(0x35, 0x25, false, 6), /* 6 : */ KEYBTDECL(0x36, 0x3a, false, 7), @@ -259,41 +259,41 @@ KEYBT RUSSIAN_LOCALE [] = /* - _ */ KEYBTDECL(0x2d, 0x4f, false, 12), /* = + */ KEYBTDECL(0x3d, 0x2b, false, 13), - /* й Й */ KEYBTDECLEX2(0x439, 0x419, true, 16, XK_Cyrillic_shorti, XK_Cyrillic_SHORTI), - /* ц Ц */ KEYBTDECLEX2(0x446, 0x426, true, 17, XK_Cyrillic_tse, XK_Cyrillic_TSE), - /* у У */ KEYBTDECLEX2(0x443, 0x423, true, 18, XK_Cyrillic_u, XK_Cyrillic_U), - /* к К */ KEYBTDECLEX2(0x43a, 0x41a, true, 19, XK_Cyrillic_ka, XK_Cyrillic_KA), - /* е Е */ KEYBTDECLEX2(0x435, 0x415, true, 20, XK_Cyrillic_ie, XK_Cyrillic_IE), - /* н Н */ KEYBTDECLEX2(0x43d, 0x41d, true, 21, XK_Cyrillic_en, XK_Cyrillic_EN), - /* г Г */ KEYBTDECLEX2(0x433, 0x413, true, 22, XK_Cyrillic_ghe, XK_Cyrillic_GHE), - /* ш Ш */ KEYBTDECLEX2(0x448, 0x428, true, 23, XK_Cyrillic_sha, XK_Cyrillic_SHA), - /* щ Щ */ KEYBTDECLEX2(0x449, 0x429, true, 24, XK_Cyrillic_shcha, XK_Cyrillic_SHCHA), - /* з З */ KEYBTDECLEX2(0x437, 0x417, true, 25, XK_Cyrillic_ze, XK_Cyrillic_ZE), - /* х Х */ KEYBTDECLEX2(0x445, 0x425, true, 26, XK_Cyrillic_ha, XK_Cyrillic_HA), - /* ъ Ъ */ KEYBTDECLEX2(0x44a, 0x42a, true, 27, XK_Cyrillic_hardsign, XK_Cyrillic_HARDSIGN), - - /* ф Ф */ KEYBTDECLEX2(0x444, 0x424, true, 30, XK_Cyrillic_ef, XK_Cyrillic_EF), - /* ы Ы */ KEYBTDECLEX2(0x44b, 0x42b, true, 31, XK_Cyrillic_yeru, XK_Cyrillic_YERU), - /* в В */ KEYBTDECLEX2(0x432, 0x412, true, 32, XK_Cyrillic_ve, XK_Cyrillic_VE), - /* а А */ KEYBTDECLEX2(0x430, 0x410, true, 33, XK_Cyrillic_a, XK_Cyrillic_A), - /* п П */ KEYBTDECLEX2(0x43f, 0x41f, true, 34, XK_Cyrillic_pe, XK_Cyrillic_PE), - /* р Р */ KEYBTDECLEX2(0x440, 0x420, true, 35, XK_Cyrillic_er, XK_Cyrillic_ER), - /* о О */ KEYBTDECLEX2(0x43e, 0x41e, true, 36, XK_Cyrillic_o, XK_Cyrillic_O), - /* л Л */ KEYBTDECLEX2(0x43b, 0x41b, true, 37, XK_Cyrillic_el, XK_Cyrillic_EL), - /* д Д */ KEYBTDECLEX2(0x434, 0x414, true, 38, XK_Cyrillic_de, XK_Cyrillic_DE), - /* ж Ж */ KEYBTDECLEX2(0x436, 0x416, true, 39, XK_Cyrillic_zhe, XK_Cyrillic_ZHE), - /* э Э */ KEYBTDECLEX2(0x44d, 0x42d, true, 40, XK_Cyrillic_e, XK_Cyrillic_E), + /* й Й */ KEYBTDECL(0x439, 0x419, true, 16), + /* ц Ц */ KEYBTDECL(0x446, 0x426, true, 17), + /* у У */ KEYBTDECL(0x443, 0x423, true, 18), + /* к К */ KEYBTDECL(0x43a, 0x41a, true, 19), + /* е Е */ KEYBTDECL(0x435, 0x415, true, 20), + /* н Н */ KEYBTDECL(0x43d, 0x41d, true, 21), + /* г Г */ KEYBTDECL(0x433, 0x413, true, 22), + /* ш Ш */ KEYBTDECL(0x448, 0x428, true, 23), + /* щ Щ */ KEYBTDECL(0x449, 0x429, true, 24), + /* з З */ KEYBTDECL(0x437, 0x417, true, 25), + /* х Х */ KEYBTDECL(0x445, 0x425, true, 26), + /* ъ Ъ */ KEYBTDECL(0x44a, 0x42a, true, 27), + + /* ф Ф */ KEYBTDECL(0x444, 0x424, true, 30), + /* ы Ы */ KEYBTDECL(0x44b, 0x42b, true, 31), + /* в В */ KEYBTDECL(0x432, 0x412, true, 32), + /* а А */ KEYBTDECL(0x430, 0x410, true, 33), + /* п П */ KEYBTDECL(0x43f, 0x41f, true, 34), + /* р Р */ KEYBTDECL(0x440, 0x420, true, 35), + /* о О */ KEYBTDECL(0x43e, 0x41e, true, 36), + /* л Л */ KEYBTDECL(0x43b, 0x41b, true, 37), + /* д Д */ KEYBTDECL(0x434, 0x414, true, 38), + /* ж Ж */ KEYBTDECL(0x436, 0x416, true, 39), + /* э Э */ KEYBTDECL(0x44d, 0x42d, true, 40), /* \ / */ KEYBTDECL(0x5c, 0x2f, false, 43), - /* я Я */ KEYBTDECLEX2(0x44f, 0x42f, true, 44, XK_Cyrillic_ya, XK_Cyrillic_YA), - /* ч Ч */ KEYBTDECLEX2(0x447, 0x427, true, 45, XK_Cyrillic_che, XK_Cyrillic_CHE), - /* с С */ KEYBTDECLEX2(0x441, 0x421, true, 46, XK_Cyrillic_es, XK_Cyrillic_ES), - /* м М */ KEYBTDECLEX2(0x43c, 0x41c, true, 47, XK_Cyrillic_em, XK_Cyrillic_EM), - /* и И */ KEYBTDECLEX2(0x438, 0x418, true, 48, XK_Cyrillic_i, XK_Cyrillic_I), - /* т Т */ KEYBTDECLEX2(0x442, 0x422, true, 49, XK_Cyrillic_te, XK_Cyrillic_TE), - /* ь Ь */ KEYBTDECLEX2(0x44c, 0x42c, true, 50, XK_Cyrillic_softsign, XK_Cyrillic_SOFTSIGN), - /* б Б */ KEYBTDECLEX2(0x431, 0x411, true, 51, XK_Cyrillic_be, XK_Cyrillic_BE), - /* ю Ю */ KEYBTDECLEX2(0x44e, 0x42e, true, 52, XK_Cyrillic_yu, XK_Cyrillic_YU), + /* я Я */ KEYBTDECL(0x44f, 0x42f, true, 44), + /* ч Ч */ KEYBTDECL(0x447, 0x427, true, 45), + /* с С */ KEYBTDECL(0x441, 0x421, true, 46), + /* м М */ KEYBTDECL(0x43c, 0x41c, true, 47), + /* и И */ KEYBTDECL(0x438, 0x418, true, 48), + /* т Т */ KEYBTDECL(0x442, 0x422, true, 49), + /* ь Ь */ KEYBTDECL(0x44c, 0x42c, true, 50), + /* б Б */ KEYBTDECL(0x431, 0x411, true, 51), + /* ю Ю */ KEYBTDECL(0x44e, 0x42e, true, 52), /* . , */ KEYBTDECL(0x2e, 0x2c, false, 53)}; KEYBT GERMAN_LOCALE[] = { @@ -535,6 +535,76 @@ void UBPlatformUtils::showOSK(bool show) } } +void UBPlatformUtils::grabScreen(QScreen* screen, std::function callback, QRect rect) +{ + if (sessionType() == WAYLAND) + { + UBDesktopPortal* portal = new UBDesktopPortal; + + QObject::connect(portal, &UBDesktopPortal::screenGrabbed, portal, [portal,callback](QPixmap screenshot){ + callback(screenshot); + portal->deleteLater(); + }); + + portal->grabScreen(screen, rect); + } + else + { + // see https://doc.qt.io/qt-6.2/qtwidgets-desktop-screenshot-example.html + // for using window id 0 + QPixmap pixmap = screen->grabWindow(0, rect.x(), rect.y(), rect.width(), rect.height()); + callback(pixmap); + } +} + +UBPlatformUtils::SessionType UBPlatformUtils::sessionType() +{ + /* + * We use XDG_SESSION_TYPE here instead of QGuiApplication::platformName(), because + * the platformName just says what Qt plugin is used to access the platform, while + * XDG_SESSION_TYPE is the actual type of the session. E.g. if we're on Wayland, + * but use the xcb platform plugin using xwayland, then platform name returns "xdg" + * just as if we're running on X11. However we would still have to use the desktop + * portal to access screen content outside of our application. + */ + QString xdgSessionType = QProcessEnvironment::systemEnvironment().value("XDG_SESSION_TYPE", ""); + + if (xdgSessionType == "x11") + { + return X11; + } + else if (xdgSessionType == "wayland") + { + return WAYLAND; + } + + return UNKNOWN; +} + +void UBPlatformUtils::keepOnTop() +{ + if (QDBusConnection::sessionBus().interface()->isServiceRegistered("org.kde.KWin")) + { + QTimer::singleShot(500, [](){ + // keep desktop window on top for KDE environments + QDBusInterface scripting("org.kde.KWin", "/Scripting"); + auto result = scripting.callWithArgumentList(QDBus::Block, "loadScript", {applicationTemplateDirectory() + "/kwinKeepAbove.js"}); + + if (result.type() != QDBusMessage::ReplyMessage || result.arguments().empty() || !result.arguments().first().canConvert()) + { + qDebug() << "Failed loading KWin script"; + return; + } + + auto path = "/" + result.arguments().first().toString(); + + QDBusInterface script("org.kde.KWin", path); + script.call("run"); + script.call("stop"); + }); + } +} + OnboardListener::OnboardListener(const QDBusConnection& connection, QObject* parent) : QObject{parent} , mConnection{connection} diff --git a/src/frameworks/UBPlatformUtils_mac.mm b/src/frameworks/UBPlatformUtils_mac.mm index 92f89d090..513bc3b89 100644 --- a/src/frameworks/UBPlatformUtils_mac.mm +++ b/src/frameworks/UBPlatformUtils_mac.mm @@ -751,3 +751,9 @@ click menu item (nbItems - 4)\n\ } } } + +void UBPlatformUtils::grabScreen(QScreen* screen, std::function callback, QRect rect) +{ + QPixmap pixmap = screen->grabWindow(0, rect.x(), rect.y(), rect.width(), rect.height()); + callback(pixmap); +} diff --git a/src/frameworks/UBPlatformUtils_win.cpp b/src/frameworks/UBPlatformUtils_win.cpp index ba4416bf2..9c144b255 100644 --- a/src/frameworks/UBPlatformUtils_win.cpp +++ b/src/frameworks/UBPlatformUtils_win.cpp @@ -494,3 +494,9 @@ void UBPlatformUtils::showOSK(bool show) PostMessage(oskWindow, WM_SYSCOMMAND, SC_CLOSE, 0); } } + +void UBPlatformUtils::grabScreen(QScreen* screen, std::function callback, QRect rect) +{ + QPixmap pixmap = screen->grabWindow(0, rect.x(), rect.y(), rect.width(), rect.height()); + callback(pixmap); +} diff --git a/src/frameworks/UBStringUtils.cpp b/src/frameworks/UBStringUtils.cpp index 329ccd964..30e0def17 100644 --- a/src/frameworks/UBStringUtils.cpp +++ b/src/frameworks/UBStringUtils.cpp @@ -152,7 +152,7 @@ QDateTime UBStringUtils::fromUtcIsoDate(const QString& dateString) QString nonConstDateString = dateString; nonConstDateString.replace("ZZ", "Z"); QDateTime date = QDateTime::fromString(nonConstDateString, Qt::ISODate); - date.setTimeSpec(Qt::UTC); + date.setTimeZone(QTimeZone::utc()); return date.toLocalTime(); } diff --git a/src/frameworks/frameworks.pri b/src/frameworks/frameworks.pri index 728b8f8df..a14e74972 100644 --- a/src/frameworks/frameworks.pri +++ b/src/frameworks/frameworks.pri @@ -7,6 +7,7 @@ HEADERS += src/frameworks/UBGeometryUtils.h \ src/frameworks/UBCoreGraphicsScene.h \ src/frameworks/UBCryptoUtils.h \ src/frameworks/UBBackgroundLoader.h \ + src/frameworks/UBBlockingBuffer.h \ src/frameworks/UBBase32.h SOURCES += src/frameworks/UBGeometryUtils.cpp \ @@ -17,6 +18,7 @@ SOURCES += src/frameworks/UBGeometryUtils.cpp \ src/frameworks/UBCoreGraphicsScene.cpp \ src/frameworks/UBCryptoUtils.cpp \ src/frameworks/UBBackgroundLoader.cpp \ + src/frameworks/UBBlockingBuffer.cpp \ src/frameworks/UBBase32.cpp @@ -33,15 +35,13 @@ macx { } -linux-g++ { +linux-g++* { + HEADERS += src/frameworks/UBDesktopPortal.h \ + src/frameworks/UBPipewireSink.h + SOURCES += src/frameworks/UBPlatformUtils_linux.cpp \ + src/frameworks/UBDesktopPortal.cpp \ + src/frameworks/UBPipewireSink.cpp - SOURCES += src/frameworks/UBPlatformUtils_linux.cpp -} -linux-g++-32 { - - SOURCES += src/frameworks/UBPlatformUtils_linux.cpp -} -linux-g++-64 { - - SOURCES += src/frameworks/UBPlatformUtils_linux.cpp + CONFIG += link_pkgconfig + PKGCONFIG += libpipewire-0.3 } diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 7a9c3f12c..66d3ecd4e 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -1,8 +1,12 @@ target_sources(${PROJECT_NAME} PRIVATE UBActionPalette.cpp UBActionPalette.h + UBBackgroundManager.cpp + UBBackgroundManager.h UBBackgroundPalette.cpp UBBackgroundPalette.h + UBBackgroundRuling.cpp + UBBackgroundRuling.h UBBlackoutWidget.cpp UBBlackoutWidget.h UBBoardThumbnailsView.cpp @@ -31,6 +35,8 @@ target_sources(${PROJECT_NAME} PRIVATE UBFeaturesActionBar.h UBFeaturesWidget.cpp UBFeaturesWidget.h + UBFlowLayout.cpp + UBFlowLayout.h UBFloatingPalette.cpp UBFloatingPalette.h UBIconButton.cpp @@ -51,6 +57,8 @@ target_sources(${PROJECT_NAME} PRIVATE UBMousePressFilter.h UBPageNavigationWidget.cpp UBPageNavigationWidget.h + UBPreferredBackgroundWidget.cpp + UBPreferredBackgroundWidget.h UBPropertyPalette.cpp UBPropertyPalette.h UBResources.cpp diff --git a/src/gui/UBActionPalette.cpp b/src/gui/UBActionPalette.cpp index 6196a6b07..66d241a09 100644 --- a/src/gui/UBActionPalette.cpp +++ b/src/gui/UBActionPalette.cpp @@ -66,9 +66,8 @@ void UBActionPalette::init(Qt::Orientation orientation) m_customCloseProcessing = false; mButtonSize = QSize(32, 32); - mIsClosable = false; mAutoClose = false; - mActionGroup = 0; + mActionGroup = nullptr; mToolButtonStyle = Qt::ToolButtonIconOnly; mButtons.clear(); @@ -115,6 +114,27 @@ UBActionPaletteButton* UBActionPalette::createPaletteButton(QAction* action, QWi return button; } +QAction* UBActionPalette::removePaletteButton(UBActionPaletteButton* button) +{ + if (!button) + { + return nullptr; + } + + QAction* action = button->defaultAction(); + + mButtons.removeAll(button); + mActions.removeAll(action); + mMapActionToButton.remove(action); + + if (mActionGroup) + { + mActionGroup->removeAction(action); + } + + return action; +} + void UBActionPalette::addAction(QAction* action) { UBActionPaletteButton* button = createPaletteButton(action, this); @@ -165,10 +185,15 @@ void UBActionPalette::groupActions() action->setProperty("id", i); mActionGroup->addAction(action); ++i; + + connect(action, &QAction::triggered, this, [this,action](bool checked){ + if (checked) + { + emit buttonGroupClicked(action); + } + }); } } - - connect(mActionGroup, SIGNAL(triggered(QAction*)), this, SIGNAL(buttonGroupClicked(QAction*))); } @@ -202,7 +227,7 @@ void UBActionPalette::updateLayout() void UBActionPalette::setClosable(bool pClosable) { - mIsClosable = pClosable; + UBFloatingPalette::setClosable(pClosable); updateLayout(); } @@ -210,7 +235,7 @@ void UBActionPalette::setClosable(bool pClosable) int UBActionPalette::border() { - if (mIsClosable) + if (isClosable()) return 10; else return 5; @@ -220,12 +245,11 @@ int UBActionPalette::border() void UBActionPalette::paintEvent(QPaintEvent *event) { UBFloatingPalette::paintEvent(event); +} - if (mIsClosable) - { - QPainter painter(this); - painter.drawPixmap(0, 0, QPixmap(":/images/close.svg")); - } +void UBActionPalette::onCloseButtonClicked() +{ + close(); } @@ -240,13 +264,6 @@ void UBActionPalette::close() void UBActionPalette::mouseReleaseEvent(QMouseEvent * event) { - if (mIsClosable && event->pos().x() >= 0 && event->pos().x() < QPixmap(":/images/close.svg").width() - && event->pos().y() >= 0 && event->pos().y() < QPixmap(":/images/close.svg").height()) - { - event->accept(); - close(); - } - UBFloatingPalette::mouseReleaseEvent(event); } @@ -284,7 +301,7 @@ UBActionPaletteButton::UBActionPaletteButton(QAction* action, QWidget * parent) { setIconSize(QSize(32, 32)); setDefaultAction(action); - setStyleSheet(QString("QToolButton {color: white; font-weight: bold; font-family: Arial; background-color: transparent; border: none}")); + setStyleSheet(QString("QToolButton {color: palette(button-text); font-weight: bold; font-family: Arial; background-color: transparent; border: none}")); setFocusPolicy(Qt::NoFocus); diff --git a/src/gui/UBActionPalette.h b/src/gui/UBActionPalette.h index 9c1859be5..95c036cc2 100644 --- a/src/gui/UBActionPalette.h +++ b/src/gui/UBActionPalette.h @@ -87,6 +87,7 @@ class UBActionPalette : public UBFloatingPalette void customMouseReleased(); protected: + void onCloseButtonClicked() override; virtual void paintEvent(QPaintEvent *event); virtual void mouseReleaseEvent(QMouseEvent * event); virtual void init(Qt::Orientation orientation); @@ -98,12 +99,12 @@ class UBActionPalette : public UBFloatingPalette QList mActions; QMap mMapActionToButton; - bool mIsClosable; Qt::ToolButtonStyle mToolButtonStyle; bool mAutoClose; QSize mButtonSize; QPoint mMousePos; UBActionPaletteButton *createPaletteButton(QAction* action, QWidget *parent); + QAction* removePaletteButton(UBActionPaletteButton* button); protected slots: void buttonClicked(); diff --git a/src/gui/UBBackgroundManager.cpp b/src/gui/UBBackgroundManager.cpp new file mode 100644 index 000000000..bf3fb01d6 --- /dev/null +++ b/src/gui/UBBackgroundManager.cpp @@ -0,0 +1,345 @@ +/* + * Copyright (C) 2015-2024 Département de l'Instruction Publique (DIP-SEM) + * + * This file is part of OpenBoard. + * + * OpenBoard 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, + * 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 OpenBoard. If not, see . + */ + + + +#include "UBBackgroundManager.h" + +#include "core/UBSettings.h" +#include "frameworks/UBPlatformUtils.h" +#include "gui/UBBackgroundRuling.h" + +#include +#include + + +UBBackgroundManager::UBBackgroundManager(QObject *parent) + : QObject{parent} +{ + scan(); +} + +void UBBackgroundManager::scan() +{ + // scan application and user directories for background rules + const QString appDir{UBPlatformUtils::applicationTemplateDirectory() + "/background"}; + const QString usrDir{UBSettings::userDataDirectory() + "/background"}; + + scan(appDir, false); + scan(usrDir, true); +} + +void UBBackgroundManager::addBackground(UBBackgroundRuling &background) +{ + if (background.isValid()) + { + if (!this->background(background.uuid())) + { + mBackgrounds.append(background); + + // store in user background template directory + const QString usrDir{UBSettings::userDataDirectory() + "/background/"}; + QDir dir; + dir.mkpath(usrDir); + + QFile file{usrDir + background.uuid().toString() + ".xml"}; + + if (!file.open(QFile::WriteOnly)) + { + qWarning() << "Cannot open" << file.fileName(); + return; + } + + QXmlStreamWriter writer{&file}; + + writer.setAutoFormatting(true); + writer.setAutoFormattingIndent(2); + writer.writeStartDocument(); + background.toXml(writer); + writer.writeEndDocument(); + + file.close(); + + emit backgroundListChanged(); + } + } + else + { + qWarning() << "Invalid background ruling" << background.description(); + } +} + +void UBBackgroundManager::deleteBackground(const QUuid& uuid) +{ + const auto bg{background(uuid)}; + + if (bg->isValid() && bg->isUserProvided()) + { + mBackgrounds.removeAll(*bg); + const QString usrDir{UBSettings::userDataDirectory() + "/background/"}; + QFile::remove(usrDir + uuid.toString() + ".xml"); + + emit backgroundListChanged(); + } +} + +const QList UBBackgroundManager::backgrounds() const +{ + QList backgroundList; + + QStringList uuidList{UBSettings::settings()->value("Board/BackgroundRulingList").toStringList()}; + + // first add rulings from list + for (const auto& uuid : uuidList) + { + const auto ruling = background(QUuid::fromString(uuid)); + + if (ruling && ruling->isValid()) + { + backgroundList << ruling; + } + } + + for (const auto& ruling : mBackgrounds) + { + if (!uuidList.contains(ruling.uuid().toString())) + { + backgroundList << &ruling; + } + } + + return backgroundList; +} + +QAction *UBBackgroundManager::backgroundAction(const UBBackgroundRuling &background, bool dark) +{ + auto action = new QAction{this}; + action->setIcon(createButtonIcon(background, dark)); + const QString lang{QLocale().name().left(2)}; + const QString descr{background.description(lang)}; + action->setToolTip(descr); + action->setProperty("uuid", background.uuid()); + action->setCheckable(true); + return action; +} + +void UBBackgroundManager::updateAction(QAction *action, bool dark) const +{ + const auto uuid{action->property("uuid").toUuid()}; + const auto bg{background(uuid)}; + + if (bg) + { + action->setIcon(createButtonIcon(*bg, dark)); + } +} + +const UBBackgroundRuling* UBBackgroundManager::background(const QUuid &uuid) const +{ + for (auto& background : std::as_const(mBackgrounds)) + { + if (background.uuid() == uuid) + { + return &background; + } + } + + return nullptr; +} + +const UBBackgroundRuling* UBBackgroundManager::guessBackground(bool crossed, bool ruled, bool intermediateLines) const +{ + for (auto& background : std::as_const(mBackgrounds)) + { + if (background.isCrossed() == crossed + && background.isRuled() == ruled + && background.hasIntermediateLines() == intermediateLines) + { + return &background; + } + } + + return nullptr; +} + +QPixmap UBBackgroundManager::createButtonPixmap(const UBBackgroundRuling& background, bool dark, bool on) const +{ + QByteArray bgSvg{renderToSvg(background, dark)}; + QDomDocument bgDoc; + + if (!bgDoc.setContent(bgSvg)) + { + qWarning() << "Cannot load rendered ruling" << background.description(); + return {}; + } + + return createButtonPixmap(bgDoc, dark, on); +} + +QIcon UBBackgroundManager::createButtonIcon(const UBBackgroundRuling &background, bool dark) const +{ + QByteArray bgSvg{renderToSvg(background, dark)}; + QDomDocument bgDoc; + + if (!bgDoc.setContent(bgSvg)) + { + qWarning() << "Cannot load rendered ruling" << background.description(); + return {}; + } + + QIcon icon; + + QPixmap pix{createButtonPixmap(bgDoc, dark, false)}; + icon.addPixmap(pix, QIcon::Normal, QIcon::Off); + + pix = createButtonPixmap(bgDoc, dark, true); + icon.addPixmap(pix, QIcon::Normal, QIcon::On); + + return icon; +} + +void UBBackgroundManager::savePreferredBackgrounds(QList& uuidList) +{ + QStringList uuids; + + for (const auto& uuid : uuidList) + { + uuids << uuid.toString(); + } + + UBSettings::settings()->setValue("Board/BackgroundRulingList", uuids); + emit preferredBackgroundChanged(); +} + +void UBBackgroundManager::scan(const QString &dirname, bool userProvided) +{ + const QDir dir{dirname}; + + if (dir.exists()) + { + const auto list{dir.entryInfoList({"*.xml"}, QDir::Files, QDir::Name)}; + + for (const auto& entry : list) + { + UBBackgroundRuling bg; + QFile file{entry.absoluteFilePath()}; + + if (!file.open(QFile::ReadOnly)) + { + continue; + } + + QXmlStreamReader reader{&file}; + + if (reader.readNextStartElement()) + { + bg.parseXml(reader, userProvided); + } + + if (bg.isValid()) + { + mBackgrounds.append(bg); + } + else + { + qWarning() << "Error reading background definitions from" << file.fileName(); + } + } + } +} + +QByteArray UBBackgroundManager::renderToSvg(const UBBackgroundRuling &background, bool dark) const +{ + QSvgGenerator generator; + QBuffer buffer; + generator.setOutputDevice(&buffer); + generator.setSize(QSize(93, 75)); + generator.setViewBox(QRect(0, 0, 93, 75)); + + QPainter painter; + + painter.begin(&generator); + background.draw(&painter, {0, 0, 93, 75}, 0., {0, 0, 193, 75}, dark); + painter.end(); + + return buffer.buffer(); +} + +QPixmap UBBackgroundManager::createButtonPixmap(const QDomDocument& bgDoc, bool dark, bool on) const +{ + QString filename{"bgButtonTemplate"}; + + filename += dark ? "Dark" : "Light"; + filename += on ? "On" : "Off"; + filename += ".svg"; + + QFile templateFile{":/images/backgroundPalette/" + filename}; + + if (!templateFile.open(QFile::ReadOnly)) + { + qWarning() << "Cannot find template file" << templateFile.fileName(); + return {}; + } + + QDomDocument doc; +#if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)) + auto result = doc.setContent(&templateFile, QDomDocument::ParseOption::UseNamespaceProcessing); +#else + auto result = doc.setContent(&templateFile, true); +#endif + if (!result) + { + qWarning() << "Cannot load template file" << templateFile.fileName(); + return {}; + } + + // find element with id="placeholder" + auto plist{doc.documentElement().elementsByTagName("g")}; + + QDomElement placeholder; + + for (int i = 0; i < plist.size(); ++i) + { + const auto item{plist.at(i).toElement()}; + + if (item.attribute("id") == "placeholder") + { + placeholder = item; + break; + } + } + + // now process background SVG + // find first element + const auto bgElement{bgDoc.documentElement().firstChildElement("g")}; + + // replace element in original document + const auto bg{doc.importNode(bgElement, true)}; + placeholder.parentNode().replaceChild(bg, placeholder); + + QByteArray replaced{doc.toByteArray(2)}; + + // render SVG to pixmap by using an QImageReader + QBuffer buffer{&replaced}; + QImageReader reader{&buffer, "svg"}; + + return QPixmap::fromImageReader(&reader); +} diff --git a/src/gui/UBBackgroundManager.h b/src/gui/UBBackgroundManager.h new file mode 100644 index 000000000..db7d3872b --- /dev/null +++ b/src/gui/UBBackgroundManager.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2015-2024 Département de l'Instruction Publique (DIP-SEM) + * + * This file is part of OpenBoard. + * + * OpenBoard 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, + * 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 OpenBoard. If not, see . + */ + + + +#pragma once + +#include "gui/UBBackgroundRuling.h" + +#include + +// forward +class QAction; +class QDomDocument; + +class UBBackgroundManager : public QObject +{ + Q_OBJECT + +public: + explicit UBBackgroundManager(QObject* parent = nullptr); + + void scan(); + void addBackground(UBBackgroundRuling& background); + void deleteBackground(const QUuid& uuid); + const QList backgrounds() const; + QAction* backgroundAction(const UBBackgroundRuling& background, bool dark); + void updateAction(QAction* action, bool dark) const; + const UBBackgroundRuling* background(const QUuid& uuid) const; + const UBBackgroundRuling* guessBackground(bool crossed, bool ruled, bool intermediateLines) const; + QPixmap createButtonPixmap(const UBBackgroundRuling& background, bool dark, bool on) const; + QIcon createButtonIcon(const UBBackgroundRuling& background, bool dark) const; + void savePreferredBackgrounds(QList& uuidList); + +signals: + void preferredBackgroundChanged(); + void backgroundListChanged(); + +private: + void scan(const QString& dirname, bool userProvided); + QByteArray renderToSvg(const UBBackgroundRuling& background, bool dark) const; + QPixmap createButtonPixmap(const QDomDocument& bgDoc, bool dark, bool on) const; + +private: + QList mBackgrounds; +}; diff --git a/src/gui/UBBackgroundPalette.cpp b/src/gui/UBBackgroundPalette.cpp index 00cc44b5c..8e35f56dd 100644 --- a/src/gui/UBBackgroundPalette.cpp +++ b/src/gui/UBBackgroundPalette.cpp @@ -1,42 +1,52 @@ +#include + #include "UBBackgroundPalette.h" +#include "gui/UBBackgroundManager.h" +#include "gui/UBFlowLayout.h" #include "gui/UBMainWindow.h" -UBBackgroundPalette::UBBackgroundPalette(QList actions, QWidget * parent) - : UBActionPalette(parent) -{ - init(); - setActions(actions); -} - UBBackgroundPalette::UBBackgroundPalette(QWidget * parent) : UBActionPalette(parent) { init(); -} - + createActions(); + connect(UBApplication::boardController->backgroundManager(), &UBBackgroundManager::preferredBackgroundChanged, + this, &UBBackgroundPalette::createActions); +} void UBBackgroundPalette::init() { + setObjectName("BackgroundPalette"); + UBActionPalette::clearLayout(); delete layout(); - m_customCloseProcessing = false; mButtonSize = QSize(32, 32); - mIsClosable = false; mAutoClose = false; mToolButtonStyle = Qt::ToolButtonIconOnly; mButtons.clear(); mVLayout = new QVBoxLayout(this); - mTopLayout = new QHBoxLayout(); + mTopLayout = new UBFlowLayout{0, 0, 0}; + mTopLayout->setSizeConstraint(QLayout::SetMaximumSize); mBottomLayout = new QHBoxLayout(); - mVLayout->addLayout(mTopLayout); + mButtonScrollArea = new QScrollArea{this}; + mButtonScrollArea->setObjectName("BackgroundPaletteScrollArea"); + mButtonScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + mButtonScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + mButtonScrollArea->setWidgetResizable(true); + + QWidget* bgWidget = new QWidget{mButtonScrollArea}; + bgWidget->setLayout(mTopLayout); + mButtonScrollArea->setWidget(bgWidget); + + mVLayout->addWidget(mButtonScrollArea); mVLayout->addLayout(mBottomLayout); mSlider = new QSlider(Qt::Horizontal); @@ -47,20 +57,24 @@ void UBBackgroundPalette::init() mSlider->setTracking(true); // valueChanged() is emitted during movement and not just upon releasing the slider mSliderLabel = new QLabel(tr("Grid size")); - mIntermediateLinesLabel = new QLabel(tr("Draw intermediate grid lines")); + mSliderLabel->setObjectName("BackgroundPaletteLabel"); + mLightDarkModeLabel = new QLabel(tr("Switch background color")); + mLightDarkModeLabel->setObjectName("BackgroundPaletteLabel"); mResetDefaultGridSizeButton = createPaletteButton(UBApplication::mainWindow->actionDefaultGridSize, this); - mResetDefaultGridSizeButton->setFixedSize(24,24); + mResetDefaultGridSizeButton->setFixedSize(32,32); mActions << UBApplication::mainWindow->actionDefaultGridSize; + UBApplication::mainWindow->actionDefaultGridSize->setProperty("ungrouped", true); // don't add to action group - connect(UBApplication::mainWindow->actionDefaultGridSize, SIGNAL(triggered()), this, SLOT(defaultBackgroundGridSize())); + connect(UBApplication::mainWindow->actionDefaultGridSize, &QAction::triggered, this, &UBBackgroundPalette::defaultBackgroundGridSize); - mDrawIntermediateLinesCheckBox = createPaletteButton(UBApplication::mainWindow->actionDrawIntermediateGridLines, this); - mDrawIntermediateLinesCheckBox->setFixedSize(24,24); - mDrawIntermediateLinesCheckBox->setCheckable(true); - mActions << UBApplication::mainWindow->actionDrawIntermediateGridLines; - UBApplication::mainWindow->actionDrawIntermediateGridLines->setProperty("ungrouped", true); - connect(UBApplication::mainWindow->actionDrawIntermediateGridLines, SIGNAL(toggled(bool)), this, SLOT(toggleIntermediateLines(bool))); + mLightDarkModeSwitch = createPaletteButton(UBApplication::mainWindow->actionLightDarkMode, this); + mLightDarkModeSwitch->setFixedSize(64,32); + mLightDarkModeSwitch->setCheckable(true); + mActions << UBApplication::mainWindow->actionLightDarkMode; + UBApplication::mainWindow->actionLightDarkMode->setProperty("ungrouped", true); // don't add to action group + + connect(UBApplication::mainWindow->actionLightDarkMode, &QAction::triggered, this, &UBBackgroundPalette::toggleBackgroundColor); mBottomLayout->addSpacing(16); mBottomLayout->addWidget(mSliderLabel); @@ -68,11 +82,76 @@ void UBBackgroundPalette::init() mBottomLayout->addWidget(mResetDefaultGridSizeButton); mBottomLayout->addSpacing(16); - mBottomLayout->addWidget(mIntermediateLinesLabel); - mBottomLayout->addWidget(mDrawIntermediateLinesCheckBox); + mBottomLayout->addWidget(mLightDarkModeLabel); + mBottomLayout->addWidget(mLightDarkModeSwitch); mBottomLayout->addSpacing(16); - updateLayout(); + UBBackgroundPalette::updateLayout(); +} + +void UBBackgroundPalette::createActions() +{ + // first delete everything from the top layout + while(!mTopLayout->isEmpty()) + { + QLayoutItem* pItem = mTopLayout->itemAt(0); + QWidget* pW = pItem->widget(); + mTopLayout->removeItem(pItem); + delete pItem; + mTopLayout->removeWidget(pW); + + QAction* action = removePaletteButton(dynamic_cast(pW)); + + delete pW; + delete action; + } + + // then add actions for the backgrounds from the background manager + const QList backgrounds = UBApplication::boardController->backgroundManager()->backgrounds(); + const auto dark = false; // starting value, updated later with updateActions() + const auto bgManager = UBApplication::boardController->backgroundManager(); + + for (int i = 0; i < backgrounds.size(); ++i) + { + const auto background = backgrounds.at(i); + auto action = bgManager->backgroundAction(*background, dark); + UBBackgroundPalette::addAction(action); + + connect(action, &QAction::triggered, this, &UBBackgroundPalette::changeBackground); + } + + actionChanged(); + updateActions(); +} + +void UBBackgroundPalette::updateActions() +{ + const auto scene = UBApplication::boardController->activeScene(); + + if (!scene) + { + return; + } + + const auto dark = scene->isDarkBackground(); + UBApplication::mainWindow->actionLightDarkMode->setChecked(dark); + QUuid uuid; + + if (scene->background()) + { + uuid = scene->background()->uuid(); + } + + for (auto& action : mActions) + { + QUuid actionUuid = action->property("uuid").toUuid(); + + if (!actionUuid.isNull()) + { + UBApplication::boardController->backgroundManager()->updateAction(action, dark); + action->setChecked(uuid == actionUuid); + } + } } void UBBackgroundPalette::addAction(QAction* action) @@ -81,6 +160,16 @@ void UBBackgroundPalette::addAction(QAction* action) mTopLayout->addWidget(button); mActions << action; + + const auto size = action->icon().availableSizes().at(0); + + if (mButtonScrollArea->height() < size.height()) + { + // make part of the second row visible + mButtonScrollArea->setFixedHeight(size.height() * 3 / 2); + // account for the width of the scrollbar + mButtonScrollArea->setFixedWidth(6 * size.width() + 40); + } } void UBBackgroundPalette::setActions(QList actions) @@ -98,15 +187,20 @@ void UBBackgroundPalette::setActions(QList actions) void UBBackgroundPalette::updateLayout() { if (mToolButtonStyle == Qt::ToolButtonIconOnly) { - mVLayout->setContentsMargins (sLayoutContentMargin / 2 + border(), sLayoutContentMargin / 2 + border() - , sLayoutContentMargin / 2 + border(), sLayoutContentMargin / 2 + border()); + mVLayout->setContentsMargins(sLayoutContentMargin / 2 + border(), + sLayoutContentMargin / 2 + border(), + sLayoutContentMargin / 2 + border(), + sLayoutContentMargin / 2 + border()); } else { - mVLayout->setContentsMargins (sLayoutContentMargin + border(), sLayoutContentMargin + border() - , sLayoutContentMargin + border(), sLayoutContentMargin + border()); + mVLayout->setContentsMargins(sLayoutContentMargin + border(), + sLayoutContentMargin + border(), + sLayoutContentMargin + border(), + sLayoutContentMargin + border()); } + update(); } @@ -148,7 +242,10 @@ void UBBackgroundPalette::showEvent(QShowEvent* event) connect(mSlider, SIGNAL(valueChanged(int)), this, SLOT(sliderValueChanged(int))); - mDrawIntermediateLinesCheckBox->setChecked(UBApplication::boardController->activeScene()->intermediateLines()); + const auto scene = UBApplication::boardController->activeScene(); + mIsDark = scene->isDarkBackground(); + mLightDarkModeSwitch->setChecked(mIsDark); + mBackground = scene->background(); QWidget::showEvent(event); } @@ -167,28 +264,33 @@ void UBBackgroundPalette::defaultBackgroundGridSize() sliderValueChanged(UBSettings::settings()->defaultCrossSize); } -void UBBackgroundPalette::toggleIntermediateLines(bool checked) +void UBBackgroundPalette::toggleBackgroundColor(bool checked) { - UBApplication::boardController->activeScene()->setIntermediateLines(checked); - UBSettings::settings()->intermediateLines = checked; // since this function is called (indirectly, by refresh) when we switch scenes, the settings will always have the current scene's value. + mIsDark = checked; + UBApplication::boardController->setBackground(mIsDark, mBackground); + backgroundChanged(); } -void UBBackgroundPalette::backgroundChanged() +void UBBackgroundPalette::changeBackground(bool checked) { - bool dark = UBApplication::boardController->activeScene()->isDarkBackground(); - - if (dark) + if (checked) { - mSliderLabel->setStyleSheet("QLabel { color : white; }"); - mIntermediateLinesLabel->setStyleSheet("QLabel { color : white; }"); - } - else - { - mSliderLabel->setStyleSheet("QLabel { color : black; }"); - mIntermediateLinesLabel->setStyleSheet("QLabel { color : black; }"); + QAction* action = dynamic_cast(sender()); + + if (action) + { + const auto uuid = action->property("uuid").toUuid(); + mBackground = UBApplication::boardController->backgroundManager()->background(uuid); + UBApplication::boardController->setBackground(mIsDark, mBackground); + } } } +void UBBackgroundPalette::backgroundChanged() +{ + updateActions(); +} + void UBBackgroundPalette::refresh() { backgroundChanged(); diff --git a/src/gui/UBBackgroundPalette.h b/src/gui/UBBackgroundPalette.h index 5a149a0fe..7a56851b5 100644 --- a/src/gui/UBBackgroundPalette.h +++ b/src/gui/UBBackgroundPalette.h @@ -6,13 +6,15 @@ #include "board/UBBoardController.h" #include "domain/UBGraphicsScene.h" +// forward +class UBBackgroundManager; +class UBFlowLayout; + class UBBackgroundPalette : public UBActionPalette { Q_OBJECT public: - - UBBackgroundPalette(QList actions, QWidget* parent = 0); UBBackgroundPalette(QWidget* parent = 0); void addAction(QAction *action); @@ -28,23 +30,29 @@ class UBBackgroundPalette : public UBActionPalette protected slots: void sliderValueChanged(int value); void defaultBackgroundGridSize(); - void toggleIntermediateLines(bool checked); + void toggleBackgroundColor(bool checked); + void changeBackground(bool checked); protected: virtual void updateLayout(); void init(); + void createActions(); + void updateActions(); - + private: QVBoxLayout* mVLayout; - QHBoxLayout* mTopLayout; + UBFlowLayout* mTopLayout; QHBoxLayout* mBottomLayout; + QScrollArea* mButtonScrollArea; QSlider* mSlider; QLabel* mSliderLabel; - QLabel* mIntermediateLinesLabel; + QLabel* mLightDarkModeLabel; UBActionPaletteButton* mResetDefaultGridSizeButton; - UBActionPaletteButton* mDrawIntermediateLinesCheckBox; + UBActionPaletteButton* mLightDarkModeSwitch; + bool mIsDark{false}; + const UBBackgroundRuling* mBackground{nullptr}; }; #endif // UBBACKGROUNDPALETTE_H diff --git a/src/gui/UBBackgroundRuling.cpp b/src/gui/UBBackgroundRuling.cpp new file mode 100644 index 000000000..4ba451322 --- /dev/null +++ b/src/gui/UBBackgroundRuling.cpp @@ -0,0 +1,1245 @@ +/* + * Copyright (C) 2015-2024 Département de l'Instruction Publique (DIP-SEM) + * + * This file is part of OpenBoard. + * + * OpenBoard 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, + * 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 OpenBoard. If not, see . + */ + + + +#include "UBBackgroundRuling.h" + +#include "core/UBSettings.h" + +#include +#include +#include +#include +#include +#include + +// namespace and element names +static constexpr char const* namespaceURI{"http://openboard.org/template/background/"}; +static constexpr char const* namespacePrefix{"bg"}; + +static constexpr char const* attributeCrossed{"crossed"}; +static constexpr char const* attributeIntermediateLines{"intermediateLines"}; +static constexpr char const* attributeRuled{"ruled"}; +static constexpr char const* attributeXmlLang{"xml:lang"}; + +static constexpr char const* elementAngle{"angle"}; +static constexpr char const* elementAttributes{"attributes"}; +static constexpr char const* elementBackground{"background"}; +static constexpr char const* elementColor{"color"}; +static constexpr char const* elementDefaultColor{"defaultColor"}; +static constexpr char const* elementDescription{"description"}; +static constexpr char const* elementFrom{"from"}; +static constexpr char const* elementLinegroup{"linegroup"}; +static constexpr char const* elementLine{"line"}; +static constexpr char const* elementOffset{"offset"}; +static constexpr char const* elementOnDark{"onDark"}; +static constexpr char const* elementOnLight{"onLight"}; +static constexpr char const* elementOrigin{"origin"}; +static constexpr char const* elementSpacing{"spacing"}; +static constexpr char const* elementStack{"stack"}; +static constexpr char const* elementStretch{"stretch"}; +static constexpr char const* elementTo{"to"}; +static constexpr char const* elementUuid{"uuid"}; +static constexpr char const* elementWidth{"width"}; + +static constexpr char const* originBottomleft{"bottomleft"}; +static constexpr char const* originBottomright{"bottomright"}; +static constexpr char const* originCenter{"center"}; +static constexpr char const* originTopleft{"topleft"}; +static constexpr char const* originTopright{"topright"}; + +static constexpr char const* valueTrue{"true"}; + + +// helper functions +static bool getStringValue(QXmlStreamReader& reader, const QString& name, QString& value) +{ + if (reader.isStartElement() && reader.namespaceUri().toString() == namespaceURI && reader.name() == name) + { + const auto text = reader.readElementText(); + + if (!reader.hasError()) + { + value = text; + return true; + } + } + + return false; +} + +static bool getDecimalValue(QXmlStreamReader& reader, const QString& name, double& value) +{ + QString text; + + if (getStringValue(reader, name, text)) + { + bool ok{true}; + value = text.toDouble(&ok); + return ok; + } + + return false; +} + +static bool getColorValue(QXmlStreamReader& reader, const QString& name, QColor& color) +{ + QString colorValue; + + if (getStringValue(reader, name, colorValue)) + { + if (colorValue.startsWith("#")) + { + color = colorValue; + return true; + } + else + { + bool ok; + const auto alpha = colorValue.toDouble(&ok); + + if (ok) + { + color.setAlphaF(color.alphaF() * alpha); + return true; + } + else + { + // try to interpret as color name + color = colorValue; + return color.isValid(); + } + } + } + + return false; +} + +static double distance(const QLineF& line, const QPointF& point) +{ + if (line.isNull()) + { + return QLineF(line.p1(), point).length(); + } + + const auto a = line.y1() - line.y2(); + const auto b = line.x2() - line.x1(); + const auto c = line.x1() * line.y2() - line.x2() * line.y1(); + + return (a * point.x() + b * point.y() + c) / std::sqrt(a * a + b * b); +} + +// Liang—Barsky line-clipping algorithm +// inspired by https://en.wikipedia.org/wiki/Liang%E2%80%93Barsky_algorithm +bool clip(const QRectF& rect, QLineF& line) +{ + // defining variables + const auto p1 = -line.dx(); + const auto p3 = -line.dy(); + + const auto q1 = line.x1() - rect.left(); + const auto q2 = rect.right() - line.x1(); + const auto q3 = line.y1() - rect.top(); + const auto q4 = rect.bottom() - line.y1(); + + qreal posarr[5]{1}; + qreal negarr[5]{0}; + int posind = 1; + int negind = 1; + + if ((p1 == 0 && q1 < 0) || (p1 == 0 && q2 < 0) || (p3 == 0 && q3 < 0) || (p3 == 0 && q4 < 0)) + { + return false; + } + + if (p1 != 0) + { + const auto r1 = q1 / p1; + const auto r2 = q2 / -p1; + + if (p1 < 0) + { + negarr[negind++] = r1; // for negative p1, add it to negative array + posarr[posind++] = r2; // and add r2 to positive array + } + else + { + negarr[negind++] = r2; + posarr[posind++] = r1; + } + } + + if (p3 != 0) + { + const auto r3 = q3 / p3; + const auto r4 = q4 / -p3; + + if (p3 < 0) + { + negarr[negind++] = r3; + posarr[posind++] = r4; + } + else + { + negarr[negind++] = r4; + posarr[posind++] = r3; + } + } + + const auto rn1 = *std::max_element(negarr, negarr + negind); // maximum of negative array + const auto rn2 = *std::min_element(posarr, posarr + posind); // minimum of positive array + + if (rn1 > rn2) + { + // reject + return false; + } + + // computing new points + const auto xn1 = line.x1() - p1 * rn1; + const auto yn1 = line.y1() - p3 * rn1; + + const auto xn2 = line.x1() - p1 * rn2; + const auto yn2 = line.y1() - p3 * rn2; + + line.setLine(xn1, yn1, xn2, yn2); + + return true; +} + + +UBBackgroundRuling::UBBackgroundRuling() +{ +} + +bool UBBackgroundRuling::operator==(const UBBackgroundRuling& other) const +{ + return uuid() == other.uuid(); +} + +bool UBBackgroundRuling::parseXml(QXmlStreamReader& reader, bool userProvided) +{ + mRules = std::shared_ptr(Rules::fromXml(reader)); + mUserProvided = userProvided; + return mRules && !mRules->hasError(); +} + +bool UBBackgroundRuling::toXml(QXmlStreamWriter& writer) const +{ + if (mRules) + { + mRules->toXml(writer); + return true; + } + + return false; +} + +bool UBBackgroundRuling::isValid() const +{ + return mRules && !mRules->hasError(); +} + +QUuid UBBackgroundRuling::uuid() const +{ + if (!mRules) + { + return {}; + } + + return mRules->uuid(); +} + +QString UBBackgroundRuling::description(const QString& languagecode) const +{ + if (!mRules) + { + return {}; + } + + return mRules->description(languagecode); +} + +bool UBBackgroundRuling::isCrossed() const +{ + if (!mRules) + { + return false; + } + + return mRules->isCrossed(); +} + +bool UBBackgroundRuling::isRuled() const +{ + if (!mRules) + { + return false; + } + + return mRules->isRuled(); +} + +bool UBBackgroundRuling::hasIntermediateLines() const +{ + if (!mRules) + { + return false; + } + + return mRules->hasIntermediateLines(); +} + +bool UBBackgroundRuling::isUserProvided() const +{ + return mUserProvided; +} + +void UBBackgroundRuling::draw(QPainter* painter, const QRectF& rect, double gridSize, const QRectF& nominalScene, + bool onDark) const +{ + if (!mRules) + { + return; + } + + const auto dotsPerMm = gridSize / 10; + const QColor defaultOnDark{UBSettings::settings()->boardCrossColorDarkBackground->get().toString()}; + const QColor defaultOnLight{UBSettings::settings()->boardCrossColorLightBackground->get().toString()}; + painter->setRenderHint(QPainter::Antialiasing, true); + + auto paintGridLine = [this, painter, rect, onDark, defaultOnDark, defaultOnLight](Line line, QLineF gridLine){ + if (clip(rect, gridLine)) + { + setLinePainter(painter, line, onDark, defaultOnDark, defaultOnLight); + painter->drawLine(gridLine); + } + }; + + auto paintBorderLine = [this, painter, dotsPerMm, onDark, defaultOnDark, defaultOnLight](const QList& borderLines, QLineF line, Qt::Edge edge){ + drawBorderLines(painter, borderLines, line, edge, dotsPerMm, onDark, defaultOnDark, defaultOnLight); + }; + + determineGridLines(rect, gridSize, nominalScene, paintGridLine, paintBorderLine); +} + +QPointF UBBackgroundRuling::snap(const QPointF& point, double gridSize, const QRectF& nominalScene, double* force, std::optional proposedPoint, + QPointF* gridSnapPoint) const +{ + // algorithm: + // - determine the lines within one grid size around the point + // - if we have only one line group, snap orthogonally to the closest line + // - if we have more than one line group, compute all intersections of the lines + // - snap to the closest intersection + + QRectF rect{0, 0, 2 * gridSize, 2 * gridSize}; + rect.moveCenter(point); + QList lines; + determineGridLines(rect, gridSize, nominalScene, [&lines](const Line& line, QLineF gridLine){ + lines << gridLine; + }); + + if (lines.empty()) + { + return {}; + } + + QPointF snapPoint{point}; + bool haveSnapPoint{false}; + + if (mRules->linegroups().size() == 1) + { + // snap to closest line + std::vector distances; + distances.reserve(lines.size()); + + for (const auto& line : lines) + { + distances.push_back(std::fabs(distance(line, point))); + } + + const auto minElement = std::min_element(distances.cbegin(), distances.cend()); + const auto index = std::distance(distances.cbegin(), minElement); + + // now compute the base point of the normal vector + QLineF gridLine = lines.at(index); + QLineF normal = gridLine.normalVector(); + normal.translate(point - normal.p1()); + gridLine.intersects(normal, &snapPoint); + haveSnapPoint = true; + } + else + { + // we have multiple line groups; calculate all intersections + QList intersections; + + for (int i = 0; i < lines.size() - 1; ++i) + { + for (int j = i + 1; j < lines.size(); ++j) + { + QPointF intersection; + + if (lines.at(i).intersects(lines.at(j), &intersection) == QLineF::BoundedIntersection) + { + intersections << intersection; + } + } + } + + if (!intersections.isEmpty()) + { + // find the closest intersection + std::vector distances; + distances.reserve(intersections.size()); + + for (const auto& intersection : intersections) + { + distances.push_back(QLineF(point, intersection).length()); + } + + const auto closestIntersection = std::min_element(distances.cbegin(), distances.cend()); + const auto index = std::distance(distances.cbegin(), closestIntersection); + snapPoint = intersections.at(index); + haveSnapPoint = true; + } + } + + if (haveSnapPoint && gridSnapPoint) + { + *gridSnapPoint = snapPoint; + } + + // force is a number between 0 and 1 based on the manhattan distance of the snap point + // from the original point + const double relativeDist = (snapPoint - point).manhattanLength() / gridSize; + double snapForce = haveSnapPoint ? std::max(1. - relativeDist, 0.) : 0; + + if (proposedPoint) + { + // compute force for proposed point and take that if it has higher force + const double relativeDist = (proposedPoint.value() - point).manhattanLength() / gridSize; + double proposedForce = std::max(1. - relativeDist, 0.); + + if (proposedForce > snapForce) + { + snapForce = proposedForce; + snapPoint = proposedPoint.value(); + } + } + + if (force) + { + *force = snapForce; + } + + return snapPoint - point; +} + +void UBBackgroundRuling::determineGridLines(const QRectF& rect, double gridSize, const QRectF& nominalScene, + std::function handleGridLine, + std::function&, QLineF, Qt::Edge)> handleBorder) const +{ + if (!mRules) + { + return; + } + + if (gridSize == 0) + { + // guess reasonable grid size for buttons + // at least two spacings on rect, else 12. + double spacing{0}; + + if (!mRules->linegroups().empty()) + { + spacing = mRules->linegroups().at(0).spacing(); + } + + if (spacing > 0) + { + // factor 10: spacing is in mm + // factor 2.5: at least 2.5 repetitions in button + // limit 12: reasonable limit for small spacings, about 1/3 of grid size on screen + gridSize = std::min(rect.height() / (2.5 * spacing / 10.), 12.); + } + } + + const auto dotsPerMm = gridSize / 10; + + // loop through line groups + for (const auto& linegroup : mRules->linegroups()) + { + const auto angle = linegroup.angle(); + const auto spacing = linegroup.spacing() * dotsPerMm; + + auto origin{nominalScene.center()}; + + switch (linegroup.origin()) + { + case Linegroup::Origin::TopLeft: + origin = nominalScene.topLeft(); + break; + + case Linegroup::Origin::TopRight: + origin = nominalScene.topRight(); + break; + + case Linegroup::Origin::BottomLeft: + origin = nominalScene.bottomLeft(); + break; + + case Linegroup::Origin::BottomRight: + origin = nominalScene.bottomRight(); + break; + + default: + break; + } + + // determine minimum and maximum offset of line group + std::vector offsets; + offsets.reserve(linegroup.lines().count()); + + for (const auto& line : linegroup.lines()) + { + offsets.push_back(line.offset()); + } + + const auto minmax = std::minmax_element(offsets.cbegin(), offsets.cend()); + const double minOffset = *minmax.first * dotsPerMm; + const double maxOffset = *minmax.second * dotsPerMm; + + // stretch needs different calculation of start and end of line + double fromStretch{0}; + double toStretch{0}; + + bool hasFromStretch{false}; + bool hasToStretch{false}; + + QList borderLines; + + if (linegroup.stretch()) + { + const auto stretch = linegroup.stretch().value(); + + hasFromStretch = stretch.from().has_value(); + hasToStretch = stretch.to().has_value(); + + fromStretch = stretch.from().value_or(0) * dotsPerMm; + toStretch = stretch.to().value_or(0) * dotsPerMm; + + borderLines = stretch.lines(); + } + + // stack adds additional constraints to repetition of line group + double fromStack{0}; + double toStack{0}; + + bool hasFromStack{false}; + bool hasToStack{false}; + + if (linegroup.stack()) + { + const auto stack = linegroup.stack().value(); + + hasFromStack = stack.from().has_value(); + hasToStack = stack.to().has_value(); + + fromStack = stack.from().value_or(0) * dotsPerMm; + toStack = stack.to().value_or(0) * dotsPerMm; + } + + // determine relevant corners + QPointF stackFrom = nominalScene.topLeft(); + QPointF stackTo = nominalScene.bottomRight(); + QPointF stretchFrom = nominalScene.bottomLeft(); + QPointF stretchTo = nominalScene.topRight(); + QPointF rectFrom = rect.topLeft(); + QPointF rectTo = rect.bottomRight(); + + const bool descending = int(angle / 90.) % 2 != 0; + const bool flipped = int(angle / 180.) % 2 != 0; + + if (descending) + { + // swap circularly counter-clockwise + std::swap(stretchTo, stackTo); + std::swap(stackFrom, stretchTo); + std::swap(stretchFrom, stackFrom); + + rectFrom = rect.topRight(); + rectTo = rect.bottomLeft(); + } + + if (flipped) + { + // swap diagonal + std::swap(stretchFrom, stretchTo); + std::swap(stackFrom, stackTo); + + std::swap(rectFrom, rectTo); + } + + // create a line through origin + auto originLine = QLineF::fromPolar(5000, angle).translated(origin); + originLine.setP1(origin + origin - originLine.p2()); // extend in other direction + + // normal vector for translations + auto normal = originLine.normalVector(); + QPointF spaceShift{0, 0}; + + auto minIndex{0}; + auto maxIndex{0}; + + if (spacing > 0) + { + normal.setLength(spacing); + spaceShift = normal.p1() - normal.p2(); + + // calculate offsets for first and last lines + minIndex = int(distance(originLine, rectFrom) / spacing); + maxIndex = int(distance(originLine, rectTo) / spacing); + + if (minIndex > maxIndex) + { + std::swap(minIndex, maxIndex); + } + + --minIndex; + } + + const auto distFrom = distance(originLine, stackFrom); + const auto distTo = distance(originLine, stackTo); + + for (int index = minIndex; index <= maxIndex; ++index) + { + if (hasFromStack && distFrom - (index * spacing + minOffset) > fromStack) + { + continue; + } + + if (hasToStack && (index * spacing + maxOffset) - distTo > toStack) + { + break; + } + + // loop through lines + for (const auto& line : linegroup.lines()) + { + const auto offset = line.offset() * dotsPerMm; + const auto offsetShift = (normal.p1() - normal.p2()) / normal.length() * offset; + + auto gridLine = originLine.translated(index * spaceShift + offsetShift); + + if (hasFromStretch) + { + // calculate base point using normal through stretchFrom + const auto border = normal.translated(stretchFrom - normal.p1()); + QPointF basePoint; + gridLine.intersects(border, &basePoint); + + QLineF shift = gridLine.unitVector(); + shift.translate(basePoint - shift.p1()); + shift.setLength(-fromStretch); + gridLine.setP1(shift.p2()); + + if (handleBorder && &line == &linegroup.lines().last()) + { + // draw border lines on "from" side + QLineF borderLine{gridLine.p1() - offsetShift, gridLine.p1()}; + + if (clip(rect, borderLine)) + { + handleBorder(borderLines, borderLine, Qt::LeftEdge); + } + } + } + + if (hasToStretch) + { + // calculate base point using normal through stretchTo + const auto border = normal.translated(stretchTo - normal.p1()); + QPointF basePoint; + gridLine.intersects(border, &basePoint); + + QLineF shift = gridLine.unitVector(); + shift.translate(basePoint - shift.p1()); + shift.setLength(toStretch); + gridLine.setP2(shift.p2()); + + if (handleBorder && &line == &linegroup.lines().last()) + { + // draw border lines on "to" side + QLineF borderLine{gridLine.p2() - offsetShift, gridLine.p2()}; + + if (clip(rect, borderLine)) + { + handleBorder(borderLines, borderLine, Qt::RightEdge); + } + } + } + + handleGridLine(line, gridLine); + } + } + } +} + +void UBBackgroundRuling::setLinePainter(QPainter* painter, const Line& line, bool onDark, QColor defaultOnDark, + QColor defaultOnLight) +{ + const auto colorOnDark = line.color().onDark(); + const auto colorOnLight = line.color().onLight(); + + // apply alpha value to defaults + defaultOnDark.setAlphaF(colorOnDark.alphaF()); + defaultOnLight.setAlphaF(colorOnLight.alphaF()); + + auto pen = painter->pen(); + pen.setWidth(line.width()); + pen.setColor(onDark ? (colorOnDark.isValid() ? colorOnDark : defaultOnDark) + : (colorOnLight.isValid() ? colorOnLight : defaultOnLight)); + pen.setCapStyle(Qt::FlatCap); + painter->setPen(pen); +} + +void UBBackgroundRuling::drawBorderLines(QPainter* painter, const QList& borderLines, QLineF line, Qt::Edge edge, + double dotsPerMm, bool onDark, QColor defaultOnDark, + QColor defaultOnLight) +{ + painter->save(); + + for (const auto& borderLine : borderLines) + { + setLinePainter(painter, borderLine, onDark, defaultOnDark, defaultOnLight); + + auto offset = borderLine.offset(); + offset *= dotsPerMm; + + if (edge == Qt::RightEdge) + { + offset = -offset; + } + + painter->drawLine(line.translated({offset, 0})); + } + + painter->restore(); +} + +// ==================== +// private data classes +// ==================== + +UBBackgroundRuling::Data::Data(Rules& rules) + : mRules{&rules} +{ +} + +bool UBBackgroundRuling::Data::hasError() const +{ + return mError; +} + +void UBBackgroundRuling::Data::setError(bool error) +{ + mError = error; +} + +const UBBackgroundRuling::Rules& UBBackgroundRuling::Data::rules() const +{ + return *mRules; +} + +UBBackgroundRuling::LineColor::LineColor(const Rules& rules, QXmlStreamReader& reader) + : Data{rules} + , mOnDark{rules.defaultColor().onDark()} + , mOnLight{rules.defaultColor().onLight()} +{ + while (reader.readNextStartElement() && reader.namespaceUri().toString() == namespaceURI) + { + if (getColorValue(reader, elementOnDark, mOnDark)) + { + } + else if (getColorValue(reader, elementOnLight, mOnLight)) + { + } + else + { + setError(); + } + } +} + +UBBackgroundRuling::LineColor::LineColor(const UBBackgroundRuling::Rules& rules, const QColor& colorOnDark, + const QColor& colorOnLight) + : Data{rules} + , mOnDark{colorOnDark} + , mOnLight{colorOnLight} +{ +} + +void UBBackgroundRuling::LineColor::toXml(QXmlStreamWriter& writer) const +{ + if (mOnDark.isValid()) + { + writer.writeTextElement(namespaceURI, elementOnDark, mOnDark.name(QColor::HexArgb)); + } + + if (mOnLight.isValid()) + { + writer.writeTextElement(namespaceURI, elementOnLight, mOnLight.name(QColor::HexArgb)); + } +} + +const QColor& UBBackgroundRuling::LineColor::onDark() const +{ + return mOnDark; +} + +const QColor& UBBackgroundRuling::LineColor::onLight() const +{ + return mOnLight; +} + +UBBackgroundRuling::Line::Line(const Rules& rules, QXmlStreamReader& reader) + : Data{rules} + , mColor{rules.defaultColor()} +{ + while (reader.readNextStartElement() && reader.namespaceUri().toString() == namespaceURI) + { + if (getDecimalValue(reader, elementOffset, mOffset)) + { + } + else if (getDecimalValue(reader, elementWidth, mWidth)) + { + } + else if (reader.name().toString() == elementColor) + { + mColor = LineColor{rules, reader}; + } + else + { + setError(); + } + } +} + +void UBBackgroundRuling::Line::toXml(QXmlStreamWriter& writer) const +{ + writer.writeTextElement(namespaceURI, elementOffset, QString::number(mOffset)); + writer.writeTextElement(namespaceURI, elementWidth, QString::number(mWidth)); + + if (mColor.onDark().isValid() || mColor.onLight().isValid()) + { + writer.writeStartElement(namespaceURI, elementColor); + mColor.toXml(writer); + writer.writeEndElement(); + } +} + +double UBBackgroundRuling::Line::offset() const +{ + return mOffset; +} + +double UBBackgroundRuling::Line::width() const +{ + return mWidth; +} + +const UBBackgroundRuling::LineColor& UBBackgroundRuling::Line::color() const +{ + return mColor; +} + +UBBackgroundRuling::Limit::Limit(const Rules& rules, QXmlStreamReader& reader) + : Data{rules} +{ + while (reader.readNextStartElement() && reader.namespaceUri().toString() == namespaceURI) + { + double value; + + if (getDecimalValue(reader, elementFrom, value)) + { + mFrom.emplace(value); + } + else if (getDecimalValue(reader, elementTo, value)) + { + mTo.emplace(value); + } + else if (reader.name().toString() == elementLine) + { + const Line bgLine{rules, reader}; + + if (bgLine.hasError()) + { + setError(); + } + else + { + mLines.append(bgLine); + } + } + } +} + +void UBBackgroundRuling::Limit::toXml(QXmlStreamWriter& writer) const +{ + if (mFrom) + { + writer.writeTextElement(namespaceURI, elementFrom, QString::number(mFrom.value())); + } + + if (mTo) + { + writer.writeTextElement(namespaceURI, elementTo, QString::number(mTo.value())); + } + + for (const auto& line : mLines) + { + writer.writeStartElement(namespaceURI, elementLine); + line.toXml(writer); + writer.writeEndElement(); + } +} + +std::optional UBBackgroundRuling::Limit::from() const +{ + return mFrom; +} + +std::optional UBBackgroundRuling::Limit::to() const +{ + return mTo; +} + +const QList& UBBackgroundRuling::Limit::lines() const +{ + return mLines; +} + +UBBackgroundRuling::Linegroup::Linegroup(const Rules& rules, QXmlStreamReader& reader) + : Data{rules} +{ + QString lineGroupOrigin; + + while (reader.readNextStartElement() && reader.namespaceUri().toString() == namespaceURI) + { + if (getDecimalValue(reader, elementAngle, mAngle)) + { + } + else if (getDecimalValue(reader, elementSpacing, mSpacing)) + { + } + else if (getStringValue(reader, elementOrigin, lineGroupOrigin)) + { + if (lineGroupOrigin == originTopleft) + { + mOrigin = Origin::TopLeft; + } + else if (lineGroupOrigin == originTopright) + { + mOrigin = Origin::TopRight; + } + else if (lineGroupOrigin == originBottomleft) + { + mOrigin = Origin::BottomLeft; + } + else if (lineGroupOrigin == originBottomright) + { + mOrigin = Origin::BottomRight; + } + else if (lineGroupOrigin == originCenter) + { + mOrigin = Origin::Center; + } + else + { + setError(); + } + } + else if (reader.name().toString() == elementLine) + { + const Line bgLine{rules, reader}; + + if (bgLine.hasError()) + { + setError(); + } + else + { + mLines.append(bgLine); + } + } + else if (reader.name().toString() == elementStretch) + { + mStretch.emplace(rules, reader); + } + else if (reader.name().toString() == elementStack) + { + mStack.emplace(rules, reader); + } + else + { + setError(); + } + } +} + +void UBBackgroundRuling::Linegroup::toXml(QXmlStreamWriter& writer) const +{ + writer.writeTextElement(namespaceURI, elementAngle, QString::number(mAngle)); + + if (mSpacing != 0) + { + writer.writeTextElement(namespaceURI, elementSpacing, QString::number(mSpacing)); + } + + QString origin; + + switch (mOrigin) + { + case Origin::Center: + origin = originCenter; + break; + case Origin::TopLeft: + origin = originTopleft; + break; + case Origin::TopRight: + origin = originTopright; + break; + case Origin::BottomLeft: + origin = originBottomleft; + break; + case Origin::BottomRight: + origin = originBottomright; + break; + } + + writer.writeTextElement(namespaceURI, elementOrigin, origin); + + for (const auto& line : mLines) + { + writer.writeStartElement(namespaceURI, elementLine); + line.toXml(writer); + writer.writeEndElement(); + } + + if (mStretch) + { + writer.writeStartElement(namespaceURI, elementStretch); + mStretch.value().toXml(writer); + writer.writeEndElement(); + } + + if (mStack) + { + writer.writeStartElement(namespaceURI, elementStack); + mStack.value().toXml(writer); + writer.writeEndElement(); + } +} + +double UBBackgroundRuling::Linegroup::angle() const +{ + return mAngle; +} + +double UBBackgroundRuling::Linegroup::spacing() const +{ + return mSpacing; +} + +UBBackgroundRuling::Linegroup::Origin UBBackgroundRuling::Linegroup::origin() const +{ + return mOrigin; +} + +const QList& UBBackgroundRuling::Linegroup::lines() const +{ + return mLines; +} + +std::optional UBBackgroundRuling::Linegroup::stretch() const +{ + return mStretch; +} + +std::optional UBBackgroundRuling::Linegroup::stack() const +{ + return mStack; +} + +UBBackgroundRuling::Rules::Rules(QXmlStreamReader& reader) + : Data{*this} + , mDefaultColor{*this, {}, {}} +{ + // current position of reader should be at the element + while (!reader.isEndElement()) + { + while (reader.readNextStartElement() && reader.namespaceUri().toString() == namespaceURI) + { + const auto name = reader.name(); + + QString text; + + if (getStringValue(reader, elementUuid, text)) + { + mUuid = QUuid(text); + } + else if (name.toString() == elementDescription) + { + const auto attributes = reader.attributes(); + const auto lang = attributes.value(attributeXmlLang).toString(); + + mDescriptions[lang.isEmpty() ? "en" : lang] = reader.readElementText(); + } + else if (name.toString() == elementAttributes) + { + const auto attributes = reader.attributes(); + mCrossed = attributes.value(attributeCrossed).toString() == valueTrue; + mRuled = attributes.value(attributeRuled).toString() == valueTrue; + mIntermediateLines = attributes.value(attributeIntermediateLines).toString() == valueTrue; + reader.skipCurrentElement(); + } + else if (name.toString() == elementDefaultColor) + { + mDefaultColor = LineColor{*this, reader}; + } + else if (name.toString() == elementLinegroup) + { + const Linegroup bgLinegroup{*this, reader}; + + if (bgLinegroup.hasError()) + { + setError(); + } + else + { + mLinegroups.append(bgLinegroup); + } + } + else + { + setError(); + } + } + } +} + +UBBackgroundRuling::Rules* UBBackgroundRuling::Rules::fromXml(QXmlStreamReader& reader) +{ + if (reader.namespaceUri().toString() == namespaceURI && reader.name().toString() == elementBackground) + { + return new Rules(reader); + } + + return nullptr; +} + +void UBBackgroundRuling::Rules::toXml(QXmlStreamWriter& writer) const +{ + writer.writeNamespace(namespaceURI, namespacePrefix); + writer.writeStartElement(namespaceURI, elementBackground); + writer.writeTextElement(namespaceURI, elementUuid, mUuid.toString(QUuid::WithoutBraces)); + + for (auto description = mDescriptions.constKeyValueBegin(); description != mDescriptions.constKeyValueEnd(); + ++description) + { + writer.writeStartElement(namespaceURI, elementDescription); + writer.writeAttribute(attributeXmlLang, description->first); + writer.writeCharacters(description->second); + writer.writeEndElement(); + } + + if (mCrossed || mRuled) + { + writer.writeEmptyElement(namespaceURI, elementAttributes); + + if (mCrossed) + { + writer.writeAttribute(attributeCrossed, valueTrue); + } + + if (mRuled) + { + writer.writeAttribute(attributeRuled, valueTrue); + } + + if (mIntermediateLines) + { + writer.writeAttribute(attributeIntermediateLines, valueTrue); + } + } + + if (mDefaultColor.onDark().isValid() || mDefaultColor.onLight().isValid()) + { + writer.writeStartElement(namespaceURI, elementDefaultColor); + mDefaultColor.toXml(writer); + writer.writeEndElement(); + } + + for (const auto& linegroup : mLinegroups) + { + writer.writeStartElement(namespaceURI, elementLinegroup); + linegroup.toXml(writer); + writer.writeEndElement(); + } + + writer.writeEndElement(); +} + +QUuid UBBackgroundRuling::Rules::uuid() const +{ + return mUuid; +} + +QString UBBackgroundRuling::Rules::description(const QString& languagecode) const +{ + return mDescriptions.value(languagecode, mDescriptions.value("en")); +} + +bool UBBackgroundRuling::Rules::isCrossed() const +{ + return mCrossed; +} + +bool UBBackgroundRuling::Rules::isRuled() const +{ + return mRuled; +} + +bool UBBackgroundRuling::Rules::hasIntermediateLines() const +{ + return mIntermediateLines; +} + +UBBackgroundRuling::LineColor UBBackgroundRuling::Rules::defaultColor() const +{ + return mDefaultColor; +} + +const QList& UBBackgroundRuling::Rules::linegroups() const +{ + return mLinegroups; +} diff --git a/src/gui/UBBackgroundRuling.h b/src/gui/UBBackgroundRuling.h new file mode 100644 index 000000000..7a26e49fb --- /dev/null +++ b/src/gui/UBBackgroundRuling.h @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2015-2024 Département de l'Instruction Publique (DIP-SEM) + * + * This file is part of OpenBoard. + * + * OpenBoard 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, + * 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 OpenBoard. If not, see . + */ + + + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +// forward +class QPainter; + + +class UBBackgroundRuling +{ +public: + UBBackgroundRuling(); + + bool operator==(const UBBackgroundRuling& other) const; + + bool parseXml(QXmlStreamReader& reader, bool userProvided = true); + bool toXml(QXmlStreamWriter& writer) const; + + bool isValid() const; + QUuid uuid() const; + QString description(const QString& languagecode = "en") const; + bool isCrossed() const; + bool isRuled() const; + bool hasIntermediateLines() const; + bool isUserProvided() const; + + void draw(QPainter* painter, const QRectF& rect, double gridSize, const QRectF& nominalScene, bool onDark) const; + QPointF snap(const QPointF& point, double gridSize, const QRectF& nominalScene, double* force = nullptr, + std::optional proposedPoint = {}, QPointF* gridSnapPoint = nullptr) const; + +private: + class Line; // forward + void determineGridLines(const QRectF& rect, double gridSize, const QRectF& nominalScene, + std::function handleGridLine = nullptr, + std::function&, QLineF, Qt::Edge)> handleBorder = nullptr) const; + static void setLinePainter(QPainter* painter, const Line& line, bool onDark, QColor defaultOnDark, + QColor defaultOnLight); + static void drawBorderLines(QPainter* painter, const QList& borderLines, QLineF line, Qt::Edge edge, double dotsPerMm, + bool onDark, QColor defaultOnDark, QColor defaultOnLight); + +private: + // data classes + class Rules; + + class Data + { + public: + explicit Data(Rules& rules); + + bool hasError() const; + virtual void toXml(QXmlStreamWriter& writer) const = 0; + + protected: + void setError(bool error = true); + const Rules& rules() const; + + private: + bool mError{false}; + Rules* mRules{nullptr}; + }; + + class LineColor : public Data + { + public: + LineColor(const Rules& rules, QXmlStreamReader& reader); + LineColor(const Rules& rules, const QColor& colorOnDark, const QColor& colorOnLight); + + virtual void toXml(QXmlStreamWriter& writer) const override; + + const QColor& onDark() const; + const QColor& onLight() const; + + private: + QColor mOnDark{}; + QColor mOnLight{}; + }; + + class Line : public Data + { + public: + Line(const Rules& rules, QXmlStreamReader& reader); + + virtual void toXml(QXmlStreamWriter& writer) const override; + + double offset() const; + double width() const; + const LineColor& color() const; + + private: + double mOffset{0.}; + double mWidth{1.}; + LineColor mColor; + }; + + class Limit : public Data + { + public: + Limit(const Rules& rules, QXmlStreamReader& reader); + + virtual void toXml(QXmlStreamWriter& writer) const override; + + std::optional from() const; + std::optional to() const; + const QList& lines() const; + + private: + std::optional mFrom{}; + std::optional mTo{}; + QList mLines{}; + }; + + class Linegroup : public Data + { + public: + enum class Origin + { + Center, + TopLeft, + TopRight, + BottomLeft, + BottomRight + }; + + Linegroup(const Rules& rules, QXmlStreamReader& reader); + + virtual void toXml(QXmlStreamWriter& writer) const override; + + double angle() const; + double spacing() const; + Origin origin() const; + const QList& lines() const; + std::optional stretch() const; + std::optional stack() const; + + private: + double mAngle{0.}; + double mSpacing{0.}; + Origin mOrigin{Origin::Center}; + QList mLines{}; + std::optional mStretch{}; + std::optional mStack{}; + }; + + class Rules : public Data + { + public: + explicit Rules(QXmlStreamReader& reader); + + static Rules* fromXml(QXmlStreamReader& reader); + + virtual void toXml(QXmlStreamWriter& writer) const override; + + QUuid uuid() const; + QString description(const QString& languagecode = QString{}) const; + bool isCrossed() const; + bool isRuled() const; + bool hasIntermediateLines() const; + LineColor defaultColor() const; + const QList& linegroups() const; + + private: + QUuid mUuid{}; + QMap mDescriptions{}; + bool mCrossed{false}; + bool mRuled{false}; + bool mIntermediateLines{false}; + LineColor mDefaultColor; + QList mLinegroups{}; + }; + +private: + std::shared_ptr mRules{nullptr}; + bool mUserProvided{false}; +}; diff --git a/src/gui/UBBoardThumbnailsView.cpp b/src/gui/UBBoardThumbnailsView.cpp index 2dfa1011a..718a05756 100644 --- a/src/gui/UBBoardThumbnailsView.cpp +++ b/src/gui/UBBoardThumbnailsView.cpp @@ -140,7 +140,7 @@ void UBBoardThumbnailsView::ensureVisibleThumbnail(int index) if (currentThumbnail) { - std::shared_ptr pageScene = UBPersistenceManager::persistenceManager()->getDocumentScene(mDocument->proxy(), index); + std::shared_ptr pageScene = mDocument->getScene(index); currentThumbnail->setPageScene(pageScene); ensureVisible(currentThumbnail); } @@ -160,7 +160,7 @@ void UBBoardThumbnailsView::resizeEvent(QResizeEvent *event) bool scrollbarWasHidden = mScrollbarVisible && !verticalScrollBar()->isVisible(); // Refresh the scene, except if resizing because scrollbar was hidden - if (event->size().width() > 0 && !scrollbarWasHidden && mDocument) + if (event->oldSize().width() == 0 || (event->size().width() > 0 && !scrollbarWasHidden && mDocument)) { mDocument->thumbnailScene()->arrangeThumbnails(); ensureVisibleThumbnail(UBApplication::boardController->activeSceneIndex()); diff --git a/src/gui/UBCachePropertiesWidget.cpp b/src/gui/UBCachePropertiesWidget.cpp index da06b9313..aa5808df8 100644 --- a/src/gui/UBCachePropertiesWidget.cpp +++ b/src/gui/UBCachePropertiesWidget.cpp @@ -69,8 +69,7 @@ UBCachePropertiesWidget::UBCachePropertiesWidget(QWidget *parent, const char *na mName = "CachePropWidget"; mVisibleState = false; - mIconToLeft = QPixmap(":images/cache_open.png"); - mIconToRight = QPixmap(":images/cache_close.png"); + mTabIcon = QPixmap(":/images/toolPalette/cacheTool.png").scaled(18, 18, Qt::KeepAspectRatio, Qt::SmoothTransformation); setContentsMargins(10, 10, 10, 10); // Build the UI diff --git a/src/gui/UBDockDownloadWidget.cpp b/src/gui/UBDockDownloadWidget.cpp index 60c03f74b..03165b2ef 100644 --- a/src/gui/UBDockDownloadWidget.cpp +++ b/src/gui/UBDockDownloadWidget.cpp @@ -33,6 +33,8 @@ #include "globals/UBGlobals.h" #include "core/memcheck.h" +#include +#include UBDockDownloadWidget::UBDockDownloadWidget(QWidget *parent, const char *name):UBDockPaletteWidget(parent, name) , mpLayout(NULL) @@ -43,8 +45,7 @@ UBDockDownloadWidget::UBDockDownloadWidget(QWidget *parent, const char *name):UB SET_STYLE_SHEET(); - mIconToLeft = QPixmap(":images/download_open.png"); - mIconToRight = QPixmap(":images/download_close.png"); + mTabIcon = QApplication::style()->standardIcon(QStyle::SP_DriveHDIcon).pixmap(18, 18); mpLayout = new QVBoxLayout(this); setLayout(mpLayout); diff --git a/src/gui/UBDockPalette.cpp b/src/gui/UBDockPalette.cpp index cc37b5de8..31ce6450d 100644 --- a/src/gui/UBDockPalette.cpp +++ b/src/gui/UBDockPalette.cpp @@ -65,6 +65,7 @@ UBDockPalette::UBDockPalette(eUBDockPaletteType paletteType, QWidget *parent, co , mCurrentTab(0) , mPaletteType(paletteType) , mTabPalette(new UBTabDockPalette(this, parent)) +, mAdditionalLeftOffset(0) { setObjectName(name); @@ -160,6 +161,15 @@ void UBDockPalette::setOrientation(eUBDockOrientation orientation) } } +void UBDockPalette::setAdditionalLeftOffset(int offset) +{ + if (mAdditionalLeftOffset == offset) + return; + + mAdditionalLeftOffset = offset; + onResizeRequest(); +} + /** * \brief Handle the resize event * @param event as the resize event @@ -185,6 +195,9 @@ void UBDockPalette::resizeEvent(QResizeEvent *event) case eUBDockOrientation_Top: // Not supported yet case eUBDockOrientation_Left: + origin.setX(mAdditionalLeftOffset); + origin.setY(0); + break; default: origin.setX(0); origin.setY(0); @@ -505,7 +518,7 @@ void UBDockPalette::onAllDownloadsFinished() void UBDockPalette::moveTabs() { - int x = width(); + int x = this->x() + width(); if (mOrientation == eUBDockOrientation_Right) { if (parentWidget()) @@ -545,6 +558,14 @@ void UBDockPalette::assignParent(QWidget *widget) { setParent(widget); mTabPalette->setParent(widget); + + // Update size constraints from new parent to avoid an issue in dark mode where the library dock palette would no longer be correctly sized + if (widget) + { + setMinimumHeight(widget->height()); + setMaximumHeight(widget->height()); + resize(width(), widget->height()); + } } void UBDockPalette::setVisible(bool visible) { @@ -616,7 +637,8 @@ void UBTabDockPalette::paintEvent(QPaintEvent *) UBDockPaletteWidget* pCrntWidget = dock->mTabWidgets.at(i); QPainterPath path; path.setFillRule(Qt::WindingFill); - QPixmap iconPixmap; + Qt::ArrowType arrow = Qt::RightArrow; + QPixmap tabIcon; switch (dock->mOrientation) { case eUBDockOrientation_Left: @@ -624,13 +646,11 @@ void UBTabDockPalette::paintEvent(QPaintEvent *) path.addRoundedRect(0, yFrom, width(), TABSIZE, dock->radius(), dock->radius()); if (pCrntWidget) { if(dock->mCollapseWidth >= dock->width()) { - // Get the collapsed icon - iconPixmap = pCrntWidget->iconToRight(); + arrow = Qt::RightArrow; } else { - // Get the expanded icon - iconPixmap = pCrntWidget->iconToLeft(); + arrow = Qt::LeftArrow; } - + tabIcon = pCrntWidget->tabIcon(); } break; @@ -639,12 +659,11 @@ void UBTabDockPalette::paintEvent(QPaintEvent *) path.addRoundedRect(0, yFrom, width(), TABSIZE, dock->radius(), dock->radius()); if (pCrntWidget) { if(dock->mCollapseWidth >= dock->width()) { - // Get the collapsed icon - iconPixmap = pCrntWidget->iconToLeft(); + arrow = Qt::LeftArrow; } else { - // Get the expanded icon - iconPixmap = pCrntWidget->iconToRight(); + arrow = Qt::RightArrow; } + tabIcon = pCrntWidget->tabIcon(); } break; @@ -663,7 +682,37 @@ void UBTabDockPalette::paintEvent(QPaintEvent *) } painter.drawPath(path); - painter.drawPixmap(2, yFrom + 2, width() - 4, TABSIZE - 4, iconPixmap); + + // Optional tab icon + if (!tabIcon.isNull()) { + qreal iconTarget = 18.0; + QPixmap iconScaled = tabIcon.scaled(iconTarget, iconTarget, Qt::KeepAspectRatio, Qt::SmoothTransformation); + QPointF iconPos((width() - iconScaled.width()) / 2.0, yFrom + 4); + painter.drawPixmap(iconPos, iconScaled); + } + + const qreal arrowSize = 18.0; + QRectF arrowRect((width() - arrowSize) / 2.0, + yFrom + TABSIZE - arrowSize - 4, + arrowSize, + arrowSize); + QPainterPath arrowPath; + // right-pointing triangle within arrowRect + arrowPath.moveTo(arrowRect.left() + arrowRect.width() * 0.70, arrowRect.center().y()); + arrowPath.lineTo(arrowRect.left() + arrowRect.width() * 0.35, arrowRect.top() + arrowRect.height() * 0.20); + arrowPath.lineTo(arrowRect.left() + arrowRect.width() * 0.35, arrowRect.bottom() - arrowRect.height() * 0.20); + arrowPath.closeSubpath(); + if (arrow == Qt::LeftArrow) { + QTransform mirror; + mirror.scale(-1, 1); + mirror.translate(-(arrowRect.left() + arrowRect.right()), 0); + arrowPath = mirror.map(arrowPath); + } + QColor arrowColor = dock->palette().color(QPalette::ButtonText); + if (dock->mCurrentTab != i) { + arrowColor.setAlphaF(0.65); + } + painter.fillPath(arrowPath, arrowColor); yFrom += (TABSIZE + dock->tabSpacing()); painter.restore(); } diff --git a/src/gui/UBDockPalette.h b/src/gui/UBDockPalette.h index aa70a640d..5f9044ddd 100644 --- a/src/gui/UBDockPalette.h +++ b/src/gui/UBDockPalette.h @@ -143,6 +143,7 @@ class UBDockPalette : public QWidget void setTabFlotable(bool newFlotable) {mTabPalette->mFlotable = newFlotable;} int getAdditionalVOffset() const {return mTabPalette->mVerticalOffset;} void setAdditionalVOffset(int newOffset) {mTabPalette->mVerticalOffset = newOffset;} + void setAdditionalLeftOffset(int offset); eUBDockPaletteType paletteType(){return mPaletteType;} @@ -217,6 +218,7 @@ private slots: private: eUBDockPaletteType mPaletteType; UBTabDockPalette *mTabPalette; + int mAdditionalLeftOffset; }; #endif // UBDOCKPALETTE_H diff --git a/src/gui/UBDockPaletteWidget.cpp b/src/gui/UBDockPaletteWidget.cpp index 406d533a5..3526a38e2 100644 --- a/src/gui/UBDockPaletteWidget.cpp +++ b/src/gui/UBDockPaletteWidget.cpp @@ -41,16 +41,6 @@ UBDockPaletteWidget::~UBDockPaletteWidget() } -QPixmap UBDockPaletteWidget::iconToRight() -{ - return mIconToRight; -} - -QPixmap UBDockPaletteWidget::iconToLeft() -{ - return mIconToLeft; -} - QString UBDockPaletteWidget::name() { return mName; diff --git a/src/gui/UBDockPaletteWidget.h b/src/gui/UBDockPaletteWidget.h index 2f5c4a38d..dffd52671 100644 --- a/src/gui/UBDockPaletteWidget.h +++ b/src/gui/UBDockPaletteWidget.h @@ -54,8 +54,7 @@ class UBDockPaletteWidget : public QWidget UBDockPaletteWidget(QWidget* parent=0, const char* name="UBDockPaletteWidget"); ~UBDockPaletteWidget(); - QPixmap iconToRight(); - QPixmap iconToLeft(); + QPixmap tabIcon() const { return mTabIcon; } QString name(); virtual bool visibleInMode(eUBDockPaletteWidgetMode mode) = 0; @@ -74,8 +73,7 @@ public slots: protected: - QPixmap mIconToRight; // arrow like this: > - QPixmap mIconToLeft; // arrow like this: < + QPixmap mTabIcon; // optional pictogram shown on tab QString mName; /* The current widget available mode list */ diff --git a/src/gui/UBDocumentThumbnailWidget.cpp b/src/gui/UBDocumentThumbnailWidget.cpp index f0c857b0d..ea344580e 100644 --- a/src/gui/UBDocumentThumbnailWidget.cpp +++ b/src/gui/UBDocumentThumbnailWidget.cpp @@ -107,7 +107,7 @@ void UBDocumentThumbnailWidget::mouseMoveEvent(QMouseEvent *event) drag->setPixmap(sceneItem->pixmap().scaledToWidth(100)); drag->setHotSpot(QPoint(drag->pixmap().width()/2, drag->pixmap().height() / 2)); - drag->exec(Qt::MoveAction); + drag->exec({Qt::MoveAction, Qt::CopyAction}); } UBDocumentThumbnailsView::mouseMoveEvent(event); diff --git a/src/gui/UBDocumentThumbnailsView.cpp b/src/gui/UBDocumentThumbnailsView.cpp index 3c77048d0..89404b123 100644 --- a/src/gui/UBDocumentThumbnailsView.cpp +++ b/src/gui/UBDocumentThumbnailsView.cpp @@ -62,6 +62,7 @@ UBDocumentThumbnailsView::UBDocumentThumbnailsView(QWidget* parent) setFrameShape(QFrame::NoFrame); setAlignment(Qt::AlignLeft | Qt::AlignTop); + QGraphicsView::setBackgroundBrush(QBrush(UBSettings::documentThumbnailsViewColor)); } @@ -134,15 +135,6 @@ void UBDocumentThumbnailsView::clearSelection() } } -void UBDocumentThumbnailsView::setBackgroundBrush(const QBrush& brush) -{ - if (mDocument) - { - mDocument->thumbnailScene()->setBackgroundBrush(brush); - } -} - - void UBDocumentThumbnailsView::mousePressEvent(QMouseEvent *event) { mClickTime.restart(); @@ -168,6 +160,12 @@ void UBDocumentThumbnailsView::mousePressEvent(QMouseEvent *event) mSelectionSpan = index2 - index1; selectItems(qMin(index1, index2), mSelectionSpan < 0 ? - mSelectionSpan + 1 : mSelectionSpan + 1); } + else + { + int index = sceneItem->sceneIndex(); + selectItemAt(index, Qt::ControlModifier & event->modifiers()); + mSelectionSpan = 0; + } } else { @@ -178,7 +176,7 @@ void UBDocumentThumbnailsView::mousePressEvent(QMouseEvent *event) } else { - thumbnailScene->hightlightItem(sceneItem->sceneIndex(), true); + thumbnailScene->hightlightItem(sceneItem->sceneIndex()); } mSelectionSpan = 0; @@ -249,6 +247,25 @@ void UBDocumentThumbnailsView::mouseReleaseEvent(QMouseEvent *event) emit mouseClick(itemAt(event->pos()), 0); } + // clicking on the same thumbnail with no modifiers should make it the only one selected + // Not done in the mousePressEvent function to let the drag and drop feature work as intented + if (event->modifiers() == Qt::NoModifier) + { + UBThumbnail* sceneItem = dynamic_cast(itemAt(event->pos())); + + if (sceneItem) + { + if (sceneItem->isSelected() && mDocument) + { + auto thumbnailScene = mDocument->thumbnailScene(); + if (thumbnailScene) + { + thumbnailScene->hightlightItem(sceneItem->sceneIndex(), true); + } + } + } + } + // do not forward event to parent class as this affects selection } diff --git a/src/gui/UBDocumentThumbnailsView.h b/src/gui/UBDocumentThumbnailsView.h index fd616ec77..a88c228dd 100644 --- a/src/gui/UBDocumentThumbnailsView.h +++ b/src/gui/UBDocumentThumbnailsView.h @@ -65,8 +65,6 @@ class UBDocumentThumbnailsView : public UBThumbnailsView return mThumbnailWidth; } - void setBackgroundBrush(const QBrush& brush); - public slots: void setThumbnailWidth(qreal pThumbnailWidth); void sceneSelectionChanged(); diff --git a/src/gui/UBDownloadWidget.cpp b/src/gui/UBDownloadWidget.cpp index d4120f076..132743380 100644 --- a/src/gui/UBDownloadWidget.cpp +++ b/src/gui/UBDownloadWidget.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include "UBDownloadWidget.h" @@ -135,7 +136,7 @@ void UBDownloadWidget::addCurrentDownloads() mpItem = new QTreeWidgetItem(mpTree); mpItem->setText(eItemColumn_Desc, actualDL.at(i).name); mpItem->setData(eItemColumn_Desc, Qt::UserRole, QVariant(actualDL.at(i).id)); - mpItem->setIcon(eItemColumn_Close, QIcon(":images/close.svg")); + mpItem->setIcon(eItemColumn_Close, style()->standardIcon(QStyle::SP_TitleBarCloseButton, nullptr, this)); mpTree->addTopLevelItem(mpItem); mpItem = new QTreeWidgetItem(mpTree); mpItem->setData(eItemColumn_Desc, Qt::UserRole, actualDL.at(i).currentSize); diff --git a/src/gui/UBFeaturesActionBar.cpp b/src/gui/UBFeaturesActionBar.cpp index 6f450a0a9..e9b9969ca 100644 --- a/src/gui/UBFeaturesActionBar.cpp +++ b/src/gui/UBFeaturesActionBar.cpp @@ -54,13 +54,12 @@ UBFeaturesActionBar::UBFeaturesActionBar( UBFeaturesController *controller, QWid , mpNewFolderBtn(NULL) { setObjectName(name); - setStyleSheet(QString("background: #EEEEEE; border-radius : 10px; border : 2px solid #999999;")); setAcceptDrops(true); mButtonGroup = new QButtonGroup(this); mSearchBar = new QLineEdit(this); - mSearchBar->setStyleSheet(QString("background-color:white; border-radius : 10px; padding : 2px;")); + mSearchBar->setObjectName("FeaturesSearchBar"); mLayout = new QHBoxLayout(); setLayout(mLayout); @@ -331,7 +330,7 @@ UBActionButton::UBActionButton(QWidget *parent, QAction* action, const char *nam setDefaultAction(action); setIconSize(QSize(BUTTON_SIZE, BUTTON_SIZE)); setToolButtonStyle(Qt::ToolButtonIconOnly); - setStyleSheet(QString("QToolButton {color: white; font-weight: bold; font-family: Arial; background-color: transparent; border: none}")); + setStyleSheet(QString("QToolButton {color: palette(button-text); font-weight: bold; font-family: Arial; background-color: transparent; border: none}")); setFocusPolicy(Qt::NoFocus); } diff --git a/src/gui/UBFeaturesWidget.cpp b/src/gui/UBFeaturesWidget.cpp index 0fb015851..ed680cb1f 100644 --- a/src/gui/UBFeaturesWidget.cpp +++ b/src/gui/UBFeaturesWidget.cpp @@ -35,6 +35,7 @@ #include "frameworks/UBFileSystemUtils.h" #include "core/UBApplication.h" #include "core/UBDownloadManager.h" +#include "core/UBPersistenceManager.h" #include "globals/UBGlobals.h" #include "board/UBBoardController.h" #include "document/UBDocumentController.h" @@ -58,8 +59,7 @@ UBFeaturesWidget::UBFeaturesWidget(QWidget *parent, const char *name) SET_STYLE_SHEET(); - mIconToLeft = QPixmap(":images/library_open.png"); - mIconToRight = QPixmap(":images/library_close.png"); + mTabIcon = QPixmap(":/images/libpalette/ApplicationsCategory.svg").scaled(18, 18, Qt::KeepAspectRatio, Qt::SmoothTransformation); setAcceptDrops(true); //Main UBFeature functionality @@ -290,10 +290,6 @@ void UBFeaturesWidget::onDisplayMetadata( QMap metadata ) previewImageUrl = ":images/libpalette/movieIcon.svg"; break; - case UBMimeType::Flash: - previewImageUrl = ":images/libpalette/FlashIcon.svg"; - break; - case UBMimeType::RasterImage: case UBMimeType::VectorImage: previewImageUrl = widgetsUrl; @@ -723,11 +719,15 @@ void UBFeaturesNewFolderDialog::reactOnTextChanged(const QString &pStr) { if (validString(pStr)) { acceptButton->setEnabled(true); - mLineEdit->setStyleSheet("background:white;"); + mLineEdit->setProperty("invalid", false); } else { acceptButton->setEnabled(false); - mLineEdit->setStyleSheet("background:#FFB3C8;"); + mLineEdit->setProperty("invalid", true); } + + // Force style update after property change + mLineEdit->style()->unpolish(mLineEdit); + mLineEdit->style()->polish(mLineEdit); } UBFeaturesProgressInfo::UBFeaturesProgressInfo(QWidget *parent) : @@ -909,7 +909,7 @@ UBFeatureProperties::UBFeatureProperties( QWidget *parent, const char *name ) : mpButtonLayout->addStretch(1); mpObjInfoLabel = new QLabel(tr("Object informations")); - mpObjInfoLabel->setStyleSheet(QString("color: #888888; font-size : 18px; font-weight:bold;")); + mpObjInfoLabel->setObjectName("UBFeatureInfoLabel"); mpLayout->addWidget(mpObjInfoLabel, 0); mpObjInfos = new QTreeWidget(this); @@ -918,7 +918,6 @@ UBFeatureProperties::UBFeatureProperties( QWidget *parent, const char *name ) : mpObjInfos->setAlternatingRowColors(true); mpObjInfos->setRootIsDecorated(false); mpObjInfos->setObjectName("DockPaletteWidgetBox"); - mpObjInfos->setStyleSheet("background:white;"); mpLayout->addWidget(mpObjInfos, 1); mpLayout->setContentsMargins(0, 0, 0, 0); @@ -1111,7 +1110,6 @@ void UBFeatureProperties::onSetAsBackground() UBFeatureItemButton::UBFeatureItemButton(QWidget *parent, const char *name):QPushButton(parent) { setObjectName(name); - setStyleSheet(QString("background-color : #DDDDDD; color : #555555; border-radius : 6px; padding : 5px; font-weight : bold; font-size : 12px;")); } UBFeatureItemButton::~UBFeatureItemButton() @@ -1423,7 +1421,6 @@ Qt::ItemFlags UBFeaturesModel::flags( const QModelIndex &index ) const || item.getType() == FEATURE_AUDIO || item.getType() == FEATURE_VIDEO || item.getType() == FEATURE_IMAGE - || item.getType() == FEATURE_FLASH || item.getType() == FEATURE_INTERNAL || item.getType() == FEATURE_FOLDER || item.getType() == FEATURE_DOCUMENT) diff --git a/src/gui/UBFloatingPalette.cpp b/src/gui/UBFloatingPalette.cpp index d5381a7f9..5690f3282 100644 --- a/src/gui/UBFloatingPalette.cpp +++ b/src/gui/UBFloatingPalette.cpp @@ -29,6 +29,7 @@ #include #include +#include #include "UBFloatingPalette.h" @@ -43,8 +44,10 @@ UBFloatingPalette::UBFloatingPalette(Qt::Corner position, QWidget *parent) , mCustomPosition(false) , mIsMoving(false) , mCanBeMinimized(false) + , mIsClosable(false) , mMinimizedLocation(eMinimizedLocation_None) , mDefaultPosition(position) + , mCloseButton(nullptr) { setCursor(Qt::ArrowCursor); @@ -70,9 +73,35 @@ UBFloatingPalette::UBFloatingPalette(Qt::Corner position, QWidget *parent) } mBackgroundBrush = QBrush(UBSettings::paletteColor); + QPalette pal = palette(); + pal.setBrush(QPalette::Window, mBackgroundBrush); + setPalette(pal); mbGrip = true; } +void UBFloatingPalette::setClosable(bool closable) +{ + mIsClosable = closable; + + if (mIsClosable) + { + buildCloseButton(); + mCloseButton->show(); + mCloseButton->raise(); + } + else if (mCloseButton) + { + mCloseButton->hide(); + } + + update(); +} + +bool UBFloatingPalette::isClosable() const +{ + return mIsClosable; +} + void UBFloatingPalette::setGrip(bool newGrip) { mbGrip = newGrip; @@ -85,6 +114,9 @@ void UBFloatingPalette::setBackgroundBrush(const QBrush& brush) if (mBackgroundBrush != brush) { mBackgroundBrush = brush; + QPalette pal = palette(); + pal.setBrush(QPalette::Window, mBackgroundBrush); + setPalette(pal); update(); } } @@ -110,6 +142,15 @@ int UBFloatingPalette::border() return 0; } +int UBFloatingPalette::closeButtonMargin() const +{ + return 10; +} + +void UBFloatingPalette::onCloseButtonClicked() +{ + hide(); +} void UBFloatingPalette::enterEvent(UB::EnterEvent *event) { @@ -262,30 +303,28 @@ void UBFloatingPalette::removeAllAssociatedPalette() } } -void UBFloatingPalette::showEvent(QShowEvent *) -{ - moveInsideParent(pos()); -} - void UBFloatingPalette::paintEvent(QPaintEvent *) { QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); painter.setPen(Qt::NoPen); - painter.setBrush(mBackgroundBrush); + painter.setBrush(palette().brush(QPalette::Window)); if(mbGrip) { - painter.setBrush(QBrush(QColor(170, 170 ,170))); + QColor borderColor = palette().color(QPalette::Mid); + painter.setBrush(QBrush(borderColor)); QPainterPath borderPath; borderPath.addRoundedRect(0, 0, width(), height(), radius(), radius()); borderPath.addRoundedRect(border(), border(), width() - 2 * border(), height() - 2 * border(), radius(), radius()); painter.drawPath(borderPath); - painter.setBrush(mBackgroundBrush); + painter.setBrush(palette().brush(QPalette::Window)); painter.drawRoundedRect(border(), border(), width() - 2 * border(), height() - 2 * border(), radius(), radius()); } else { + painter.setBrush(palette().brush(QPalette::Window)); painter.drawRoundedRect(border(), border(), width() - 2 * border(), height() - 2 * border(), radius(), radius()); } } @@ -295,6 +334,29 @@ int UBFloatingPalette::gripSize() return 5; } +void UBFloatingPalette::buildCloseButton() +{ + if (mCloseButton) + return; + + mCloseButton = new QToolButton(this); + mCloseButton->setObjectName("closeButton"); + mCloseButton->setAutoRaise(true); + mCloseButton->setToolButtonStyle(Qt::ToolButtonIconOnly); + mCloseButton->setText(QString()); + mCloseButton->setFixedSize(28, 28); + mCloseButton->setIconSize(QSize(12, 12)); + mCloseButton->setIcon(QIcon(":/images/close-no-bg.png")); + mCloseButton->setToolTip(tr("Close")); + const int margin = closeButtonMargin(); + mCloseButton->move(margin, margin); + mCloseButton->hide(); + + connect(mCloseButton, &QToolButton::clicked, this, [this]() { + onCloseButtonClicked(); + }); +} + void UBFloatingPalette::minimizePalette(const QPoint& pos) { if(!mCanBeMinimized) diff --git a/src/gui/UBFloatingPalette.h b/src/gui/UBFloatingPalette.h index 6f2118903..54b64da79 100644 --- a/src/gui/UBFloatingPalette.h +++ b/src/gui/UBFloatingPalette.h @@ -32,6 +32,7 @@ #include #include +#include #include "core/UB.h" @@ -71,18 +72,20 @@ class UBFloatingPalette : public QWidget void setBackgroundBrush(const QBrush& brush); void setGrip(bool newGrip); + void setClosable(bool closable); + bool isClosable() const; void setMinimizePermission(bool permission); protected: - virtual void enterEvent(UB::EnterEvent *event) override; - virtual void showEvent(QShowEvent *event) override; virtual void paintEvent(QPaintEvent *event) override; virtual int radius(); virtual int border(); virtual int gripSize(); + virtual int closeButtonMargin() const; + virtual void onCloseButtonClicked(); QBrush mBackgroundBrush; bool mbGrip; @@ -97,14 +100,17 @@ class UBFloatingPalette : public QWidget eMinimizedLocation minimizedLocation(){return mMinimizedLocation;} private: + void buildCloseButton(); void removeAllAssociatedPalette(); void minimizePalette(const QPoint& pos); QList mAssociatedPalette; QPoint mDragPosition; bool mCanBeMinimized; + bool mIsClosable; eMinimizedLocation mMinimizedLocation; Qt::Corner mDefaultPosition; + QToolButton* mCloseButton; signals: void mouseEntered(); diff --git a/src/gui/UBFlowLayout.cpp b/src/gui/UBFlowLayout.cpp new file mode 100644 index 000000000..b896be257 --- /dev/null +++ b/src/gui/UBFlowLayout.cpp @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2015-2024 Département de l'Instruction Publique (DIP-SEM) + * + * This file is part of OpenBoard. + * + * OpenBoard 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, + * 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 OpenBoard. If not, see . + */ + + + +#include "UBFlowLayout.h" + +#include + +// inspired by https://doc.qt.io/qt-5/qtwidgets-layouts-flowlayout-example.html + +UBFlowLayout::UBFlowLayout(QWidget* parent, int margin, int hSpacing, int vSpacing) + : QLayout{parent} + , mHSpace{hSpacing} + , mVSpace{vSpacing} +{ + setContentsMargins(margin, margin, margin, margin); +} + +UBFlowLayout::UBFlowLayout(int margin, int hSpacing, int vSpacing) + : mHSpace{hSpacing} + , mVSpace{vSpacing} +{ + setContentsMargins(margin, margin, margin, margin); +} + +UBFlowLayout::~UBFlowLayout() +{ + QLayoutItem* item; + + while ((item = UBFlowLayout::takeAt(0))) + { + delete item; + } +} + +void UBFlowLayout::addItem(QLayoutItem* item) +{ + mItemList.append(item); +} + +void UBFlowLayout::insertItem(int index, QLayoutItem* item) +{ + mItemList.insert(index, item); +} + +int UBFlowLayout::horizontalSpacing() const +{ + if (mHSpace >= 0) + { + return mHSpace; + } + else + { + return smartSpacing(QStyle::PM_LayoutHorizontalSpacing); + } +} + +int UBFlowLayout::verticalSpacing() const +{ + if (mVSpace >= 0) + { + return mVSpace; + } + else + { + return smartSpacing(QStyle::PM_LayoutVerticalSpacing); + } +} + +int UBFlowLayout::count() const +{ + return mItemList.size(); +} + +QLayoutItem* UBFlowLayout::itemAt(int index) const +{ + return mItemList.value(index); +} + +QLayoutItem* UBFlowLayout::takeAt(int index) +{ + if (index >= 0 && index < mItemList.size()) + { + return mItemList.takeAt(index); + } + + return nullptr; +} + +QLayoutItem* UBFlowLayout::replaceAt(int index, QLayoutItem* item) +{ + if (!item) + { + return nullptr; + } + + auto b = mItemList.value(index); + + if (!b) + { + return nullptr; + } + + mItemList.replace(index, item); + invalidate(); + return b; +} + +Qt::Orientations UBFlowLayout::expandingDirections() const +{ + return {}; +} + +bool UBFlowLayout::hasHeightForWidth() const +{ + return true; +} + +int UBFlowLayout::heightForWidth(int width) const +{ + auto height = doLayout({0, 0, width, 0}, true); + return height; +} + +void UBFlowLayout::setGeometry(const QRect& rect) +{ + QLayout::setGeometry(rect); + doLayout(rect, false); +} + +QSize UBFlowLayout::sizeHint() const +{ + return minimumSize(); +} + +QSize UBFlowLayout::minimumSize() const +{ + QSize size; + + for (const auto item : std::as_const(mItemList)) + { + size = size.expandedTo(item->minimumSize()); + } + + const auto margins = contentsMargins(); + size += QSize{margins.left() + margins.right(), margins.top() + margins.bottom()}; + return size; +} + +int UBFlowLayout::doLayout(const QRect& rect, bool testOnly) const +{ + int left, top, right, bottom; + getContentsMargins(&left, &top, &right, &bottom); + const auto effectiveRect = rect.adjusted(+left, +top, -right, -bottom); + auto x = effectiveRect.x(); + auto y = effectiveRect.y(); + int lineHeight = 0; + + for (auto item : std::as_const(mItemList)) + { + const auto wid = item->widget(); + auto spaceX = horizontalSpacing(); + + if (spaceX == -1) + { + spaceX = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal); + } + + auto spaceY = verticalSpacing(); + + if (spaceY == -1) + { + spaceY = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical); + } + + auto nextX = x + item->sizeHint().width() + spaceX; + + if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) + { + x = effectiveRect.x(); + y = y + lineHeight + spaceY; + nextX = x + item->sizeHint().width() + spaceX; + lineHeight = 0; + } + + if (!testOnly) + { + item->setGeometry(QRect(QPoint(x, y), item->sizeHint())); + } + + x = nextX; + lineHeight = qMax(lineHeight, item->sizeHint().height()); + } + + return y + lineHeight - rect.y() + bottom; +} + +int UBFlowLayout::smartSpacing(QStyle::PixelMetric pm) const +{ + auto parent = this->parent(); + + if (!parent) + { + return -1; + } + else if (parent->isWidgetType()) + { + const auto pw = static_cast(parent); + return pw->style()->pixelMetric(pm, nullptr, pw); + } + else + { + return static_cast(parent)->spacing(); + } +} diff --git a/src/gui/UBFlowLayout.h b/src/gui/UBFlowLayout.h new file mode 100644 index 000000000..77320a0a5 --- /dev/null +++ b/src/gui/UBFlowLayout.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2015-2024 Département de l'Instruction Publique (DIP-SEM) + * + * This file is part of OpenBoard. + * + * OpenBoard 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, + * 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 OpenBoard. If not, see . + */ + + +#pragma once + + +#include +#include + +class UBFlowLayout : public QLayout +{ +public: + explicit UBFlowLayout(QWidget* parent, int margin = -1, int hSpacing = -1, int vSpacing = -1); + explicit UBFlowLayout(int margin = -1, int hSpacing = -1, int vSpacing = -1); + ~UBFlowLayout(); + + void addItem(QLayoutItem* item) override; + void insertItem(int index, QLayoutItem* item); + int horizontalSpacing() const; + int verticalSpacing() const; + Qt::Orientations expandingDirections() const override; + bool hasHeightForWidth() const override; + int heightForWidth(int) const override; + int count() const override; + QLayoutItem* itemAt(int index) const override; + QSize minimumSize() const override; + void setGeometry(const QRect& rect) override; + QSize sizeHint() const override; + QLayoutItem* takeAt(int index) override; + QLayoutItem* replaceAt(int index, QLayoutItem* item); + +private: + int doLayout(const QRect& rect, bool testOnly) const; + int smartSpacing(QStyle::PixelMetric pm) const; + +private: + QList mItemList; + int mHSpace; + int mVSpace; +}; diff --git a/src/gui/UBKeyboardPalette_linux.cpp b/src/gui/UBKeyboardPalette_linux.cpp index 5a92b551d..5f0f8495e 100644 --- a/src/gui/UBKeyboardPalette_linux.cpp +++ b/src/gui/UBKeyboardPalette_linux.cpp @@ -29,152 +29,47 @@ #include "UBKeyboardPalette.h" -#include -#include - - -// The key code to be sent. -// A full list of available codes can be found in /usr/include/X11/keysymdef.h -//#define KEYCODE XK_Down - -// Function to create a keyboard event -XKeyEvent createKeyEvent(Display *display, Window &win, - Window &winRoot, bool press, - int keycode, int modifiers) -{ - XKeyEvent event; - - event.display = display; - event.window = win; - event.root = winRoot; - event.subwindow = None; - event.time = CurrentTime; - event.x = 1; - event.y = 1; - event.x_root = 1; - event.y_root = 1; - event.same_screen = True; - event.keycode = keycode; - event.state = modifiers; - - if(press) - event.type = KeyPress; - else - event.type = KeyRelease; - - return event; -} - -void x11SendKey(Display *display, int keyCode, int modifiers) -{ - -// Get the root window for the current display. - Window winRoot = XDefaultRootWindow(display); - if (winRoot==0) - return; - -// Find the window which has the current keyboard focus. - Window winFocus; - int revert; - XGetInputFocus(display, &winFocus, &revert); - -// Send a fake key press event to the window. - XKeyEvent event = createKeyEvent(display, winFocus, winRoot, true, keyCode, modifiers); - XSendEvent(event.display, event.window, True, KeyPressMask, (XEvent *)&event); - -// Send a fake key release event to the window. - event = createKeyEvent(display, winFocus, winRoot, false, keyCode, modifiers); - XSendEvent(event.display, event.window, True, KeyPressMask, (XEvent *)&event); - -} - -/* -void traceKeyCodes() -{ - Display *display = XOpenDisplay(0); - if(display == NULL) - return; - - int min_keycodes, max_keycodes, byte_per_code; - - XDisplayKeycodes(display, &min_keycodes, &max_keycodes); - KeySym* keySyms = XGetKeyboardMapping(display, min_keycodes, - max_keycodes - min_keycodes, &byte_per_code); - - qDebug() << "Codes table, min_keycodes: " << min_keycodes << ", max_keycodes: " - << max_keycodes << ", bytes_per_code:" << byte_per_code; - for(int i=0; imin_keycodes , modifier); - } - - XCloseDisplay(display); + auto focusObject = QGuiApplication::focusObject(); + auto key = keycode.symbol; + QString keyStr = QChar(key); + QKeyEvent pressEvent = QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier, keyStr); + QKeyEvent releaseEvent = QKeyEvent(QEvent::KeyRelease, key, Qt::NoModifier); + QCoreApplication::sendEvent(focusObject, &pressEvent); + QCoreApplication::sendEvent(focusObject, &releaseEvent); } void UBKeyboardButton::sendControlSymbol(int nSymbol) { - // Obtain the X11 display. - Display *display = XOpenDisplay(0); - if(display == NULL) - return; - + auto focusObject = QGuiApplication::focusObject(); + QString keyStr; - KeyCode keyCode = XKeysymToKeycode(display, nSymbol); - - if (keyCode != NoSymbol) + if (nSymbol == Qt::Key_Space) { - x11SendKey(display, keyCode, 0); + keyStr = " "; } - XCloseDisplay(display); + QKeyEvent pressEvent = QKeyEvent(QEvent::KeyPress, nSymbol, Qt::NoModifier, keyStr); + QKeyEvent releaseEvent = QKeyEvent(QEvent::KeyRelease, nSymbol, Qt::NoModifier); + QCoreApplication::sendEvent(focusObject, &pressEvent); + QCoreApplication::sendEvent(focusObject, &releaseEvent); } void UBKeyboardPalette::createCtrlButtons() { ctrlButtons = new UBKeyboardButton*[9]; - ctrlButtons[0] = new UBCntrlButton(this, XK_BackSpace, "backspace"); - ctrlButtons[1] = new UBCntrlButton(this, XK_Tab, "tab"); + ctrlButtons[0] = new UBCntrlButton(this, Qt::Key_Backspace, "backspace"); + ctrlButtons[1] = new UBCntrlButton(this, Qt::Key_Tab, "tab"); ctrlButtons[2] = new UBCapsLockButton(this, "capslock"); - ctrlButtons[3] = new UBCntrlButton(this, tr("Enter"), XK_Return); + ctrlButtons[3] = new UBCntrlButton(this, tr("Enter"), Qt::Key_Return); ctrlButtons[4] = new UBShiftButton(this, "shift"); ctrlButtons[5] = new UBShiftButton(this, "shift"); ctrlButtons[6] = new UBLocaleButton(this); - ctrlButtons[7] = new UBCntrlButton(this, "", XK_space); + ctrlButtons[7] = new UBCntrlButton(this, "", Qt::Key_Space); ctrlButtons[8] = new UBLocaleButton(this); } @@ -184,110 +79,8 @@ void UBKeyboardPalette::checkLayout() void UBKeyboardPalette::onActivated(bool activated) { - if (activated) - { - if (storage) - { - qDebug() << "Keybard already activated...."; - return; - } - - Display *display = XOpenDisplay(0); - if(display == NULL) - return; - - XDisplayKeycodes(display, &this->min_keycodes, &this->max_keycodes); - KeySym* keySyms = XGetKeyboardMapping(display, min_keycodes, - max_keycodes - min_keycodes, &byte_per_code); - - storage = keySyms; - - XCloseDisplay(display); - - onLocaleChanged(locales[nCurrentLocale]); - - } - else - { - Display *display = XOpenDisplay(0); - if(display == NULL) - { - qDebug() << "Keybard not activated...."; - return; - } - - KeySym* keySyms = (KeySym*)storage; - if (keySyms!=NULL) - { - qDebug() << "Default key table restored....."; - - XChangeKeyboardMapping(display, min_keycodes, byte_per_code, - keySyms, max_keycodes - min_keycodes); - - XFree(keySyms); - storage = NULL; - } - - XCloseDisplay(display); - } } void UBKeyboardPalette::onLocaleChanged(UBKeyboardLocale* locale) { - const int maxMapOffset = 3; //Suppose to have at least 2 keysym groups due to X11 xlib specification - - Display *display = XOpenDisplay(0); - if(display == NULL) - return; - - int byte_per_code; - KeySym* keySyms = XGetKeyboardMapping(display, min_keycodes, - max_keycodes - min_keycodes, &byte_per_code); - - for(int i=0; icodes[j]; - if (!kc.empty()) - { - if (kc.modifier <= maxMapOffset) - keySyms[kc.code * byte_per_code + kc.modifier] = kc.symbol; - - } - } - } - - //Now look for modifiers > 5 and reassign them to free places - for(int i=0; icodes[j]; - if (!kc.empty()) - { - if (kc.modifier > maxMapOffset) - { - for(int i1=0; i1setWindowModality(Qt::ApplicationModal); - //Setting tooltip colors staticly, since they look not quite well on different color themes - QPalette toolTipPalette; - toolTipPalette.setColor(QPalette::ToolTipBase, QColor("#FFFFDC")); - toolTipPalette.setColor(QPalette::ToolTipText, Qt::black); - QToolTip::setPalette(toolTipPalette); QWidget* centralWidget = new QWidget(this); mStackedLayout = new QStackedLayout(centralWidget); diff --git a/src/gui/UBMessageWindow.cpp b/src/gui/UBMessageWindow.cpp index 99390bd26..08fa88b35 100644 --- a/src/gui/UBMessageWindow.cpp +++ b/src/gui/UBMessageWindow.cpp @@ -37,14 +37,17 @@ UBMessageWindow::UBMessageWindow(QWidget *parent) : UBFloatingPalette(Qt::BottomLeftCorner, parent) , mTimerID(-1) { + setObjectName("UBMessageWindow"); mLayout = new QHBoxLayout(this); - mSpinningWheel = new UBSpinningWheel(parent); - mLabel = new QLabel(parent); - mLabel->setStyleSheet(QString("QLabel { color: white; background-color: transparent; border: none; font-family: Arial; font-size: 14px }")); + mSpinningWheel = new UBSpinningWheel(this); + mSpinningWheel->setObjectName("UBMessageWindowSpinner"); + mLabel = new QLabel(this); + mLabel->setObjectName("UBMessageWindowLabel"); mOriginalAlpha = 255; mLayout->setContentsMargins(radius() + 15, 4, radius() + 15, 4); + mLayout->setSpacing(10); #ifdef Q_OS_OSX mLayout->setContentsMargins(radius() + 15, 8, radius() + 15, 10); diff --git a/src/gui/UBPageNavigationWidget.cpp b/src/gui/UBPageNavigationWidget.cpp index 765f377bf..42ee586e1 100644 --- a/src/gui/UBPageNavigationWidget.cpp +++ b/src/gui/UBPageNavigationWidget.cpp @@ -48,13 +48,13 @@ UBPageNavigationWidget::UBPageNavigationWidget(QWidget *parent, const char *name , mClock(NULL) { setObjectName(name); + setAttribute(Qt::WA_StyledBackground, true); mName = "PageNavigator"; mVisibleState = true; SET_STYLE_SHEET(); - mIconToRight = QPixmap(":images/pages_open.png"); - mIconToLeft = QPixmap(":images/pages_close.png"); + mTabIcon = QPixmap(":/images/toolbar/board.png").scaled(18, 18, Qt::KeepAspectRatio, Qt::SmoothTransformation); // Build the gui mLayout = new QVBoxLayout(this); @@ -71,13 +71,9 @@ UBPageNavigationWidget::UBPageNavigationWidget(QWidget *parent, const char *name mHLayout->addWidget(mPageNbr); mHLayout->addWidget(mClock); - // Configure the page number indicator - mPageNbr->setStyleSheet(QString("QLabel { color: white; background-color: transparent; border: none; font-family: Arial; font-weight: bold; font-size: 20px }")); setPageNumber(0, 0); mPageNbr->setAlignment(Qt::AlignHCenter); - // Configure the clock - mClock->setStyleSheet(QString("QLabel {color: white; background-color: transparent; text-align: center; font-family: Arial; font-weight: bold; font-size: 20px}")); mTimeFormat = QLocale::system().timeFormat(QLocale::ShortFormat); mClock->setAlignment(Qt::AlignHCenter); diff --git a/src/gui/UBPreferredBackgroundWidget.cpp b/src/gui/UBPreferredBackgroundWidget.cpp new file mode 100644 index 000000000..f8fb82b6a --- /dev/null +++ b/src/gui/UBPreferredBackgroundWidget.cpp @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2015-2024 Département de l'Instruction Publique (DIP-SEM) + * + * This file is part of OpenBoard. + * + * OpenBoard 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, + * 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 OpenBoard. If not, see . + */ + + + +#include "UBPreferredBackgroundWidget.h" + +#include "board/UBBoardController.h" +#include "core/UBApplication.h" +#include "gui/UBBackgroundManager.h" +#include "gui/UBFlowLayout.h" + + +UBPreferredBackgroundWidget::UBPreferredBackgroundWidget(QWidget* parent) + : QWidget{parent} +{ + setAcceptDrops(true); + + mLayout = new UBFlowLayout{this, -1, 0, 0}; + mLayout->setSizeConstraint(QLayout::SetMaximumSize); + setLayout(mLayout); + + mPlaceholder = new QLabel{this}; + QPixmap pixmap{QSize{128, 128}}; + pixmap.fill(QColor{Qt::transparent}); + + QPainter painter{&pixmap}; + QPen pen = painter.pen(); + pen.setWidth(4); + pen.setColor(QColor{0xbbbbbb}); + pen.setStyle(Qt::DashLine); + painter.setPen(pen); + painter.drawRoundedRect(6, 20, 115, 88, 15, 15); + + mPlaceholder->setPixmap(pixmap); + mPlaceholder->setVisible(false); + + QPixmap trash{":/images/trash-document-page.png"}; + painter.drawPixmap({26, 30, 68, 68}, trash); + + mTrash = new QLabel{this}; + mTrash->setPixmap(pixmap); + mTrash->setVisible(false); + + updateBackgroundButtons(); + + connect(UBApplication::boardController->backgroundManager(), &UBBackgroundManager::backgroundListChanged, this, + &UBPreferredBackgroundWidget::updateBackgroundButtons); +} + +void UBPreferredBackgroundWidget::dragEnterEvent(QDragEnterEvent* event) +{ + event->accept(); + QWidget::dragEnterEvent(event); +} + +void UBPreferredBackgroundWidget::dragMoveEvent(QDragMoveEvent* event) +{ +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + const auto p = event->position().toPoint(); +#else + const auto p = event->pos(); +#endif + + // auto-scroll if widget is in a scroll area + auto scrollArea = dynamic_cast(parentWidget()->parentWidget()); + + if (scrollArea) + { + scrollArea->ensureVisible(p.x(), p.y(), 15); + } + + auto child = childAt(p); + + // walk up to a direct child + while (child && child->parentWidget() != this) + { + child = child->parentWidget(); + } + + if (!child) + { + return; + } + + // is the child the placeholder or trash label? + if (child == mPlaceholder || child == mTrash) + { + return; + } + + // move the placeholder to the appropiate place + const auto pl = mLayout->indexOf(mPlaceholder); + const auto ix = mLayout->indexOf(child); + + if (pl >= 0 && ix >= 0) + { + auto plItem = mLayout->takeAt(pl); + mLayout->insertItem(ix, plItem); + mLayout->update(); + } +} + +void UBPreferredBackgroundWidget::dropEvent(QDropEvent* event) +{ +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + const auto p = event->position().toPoint(); +#else + const auto p = event->pos(); +#endif + + auto child = childAt(p); + + // is the child the trash? + if (child == mTrash) + { + // ask user whether to delete the background ruling + const auto uuid = mDraggedItem->widget()->property("uuid").toUuid(); + auto bgManager = UBApplication::boardController->backgroundManager(); + const auto background = bgManager->background(uuid); + const auto lang = QLocale().name().left(2); + + const auto answer = + QMessageBox::question(this, tr("Delete background"), + tr("Do you want to delete the background") + " " + background->description(lang)); + + if (answer == QMessageBox::Yes) + { + bgManager->deleteBackground(uuid); + delete mDraggedItem->widget(); + delete mDraggedItem; + mDraggedItem = nullptr; + mLayout->removeWidget(mPlaceholder); + mPlaceholder->setVisible(false); + mTrash->setVisible(false); + } + + return; + } + + // child must be placeholder + if (child != mPlaceholder) + { + return; + } + + auto ix = mLayout->indexOf(child); + + if (ix < 0) + { + return; + } + + mLayout->replaceAt(ix, mDraggedItem); + mLayout->removeWidget(mPlaceholder); + mPlaceholder->setVisible(false); + mTrash->setVisible(false); + + event->acceptProposedAction(); +} + +void UBPreferredBackgroundWidget::mousePressEvent(QMouseEvent* event) +{ + auto child = childAt(event->pos()); + + // walk up to a direct child + while (child && child->parentWidget() != this) + { + child = child->parentWidget(); + } + + auto backgroundWidget = dynamic_cast(child); + + if (!backgroundWidget) + { + return; + } + + mDraggedItemPos = mLayout->indexOf(backgroundWidget); + auto item = new QWidgetItem{mPlaceholder}; + mDraggedItem = mLayout->replaceAt(mDraggedItemPos, item); + mPlaceholder->setVisible(true); + mDraggedItem->widget()->setVisible(false); + + const auto uuid = mDraggedItem->widget()->property("uuid").toUuid(); + const auto background = UBApplication::boardController->backgroundManager()->background(uuid); + + mTrash->setVisible(background && background->isUserProvided()); + + auto pixmap = backgroundWidget->pixmap(); + + auto mimeData = new QMimeData; + auto drag = new QDrag{this}; + drag->setMimeData(mimeData); + drag->setPixmap(pixmap); + drag->setHotSpot(event->pos() - backgroundWidget->pos()); + const auto dropAction = drag->exec(Qt::MoveAction); + + if (dropAction != Qt::MoveAction) + { + // abort DnD and put dragged item at original position + if (mDraggedItem && mDraggedItemPos >= 0) + { + const auto ix = mLayout->indexOf(mPlaceholder); + + if (ix >= 0) + { + mLayout->takeAt(ix); + mLayout->insertItem(mDraggedItemPos, mDraggedItem); + mLayout->removeWidget(mPlaceholder); + mPlaceholder->setVisible(false); + mTrash->setVisible(false); + } + } + } + else + { + savePreferrdBackgrounds(); + } + + if (mDraggedItem && mDraggedItem->widget()) + { + mDraggedItem->widget()->setVisible(true); + } +} + +void UBPreferredBackgroundWidget::savePreferrdBackgrounds() const +{ + QList uuidList; + + for (int i = 0; i < mLayout->count() && i < 6; ++i) + { + uuidList << mLayout->itemAt(i)->widget()->property("uuid").toUuid(); + } + + UBApplication::boardController->backgroundManager()->savePreferredBackgrounds(uuidList); +} + +void UBPreferredBackgroundWidget::updateBackgroundButtons() +{ + while (!mLayout->isEmpty()) + { + auto item = mLayout->takeAt(0); + auto widget = item->widget(); + + if (widget != mPlaceholder && widget != mTrash) + { + delete widget; + } + + delete item; + } + + auto* bgManager = UBApplication::boardController->backgroundManager(); + const auto backgrounds = bgManager->backgrounds(); + + for (const auto& background : backgrounds) + { + auto pixmap = bgManager->createButtonPixmap(*background, false, false); + auto text = background->description(QLocale().name().left(2)); + auto widget = new BackgroundWidget{pixmap, text, this}; + widget->setProperty("uuid", background->uuid()); + widget->setToolTip(text); + + mLayout->addWidget(widget); + } + + mLayout->addWidget(mTrash); +} + +UBPreferredBackgroundWidget::BackgroundWidget::BackgroundWidget(const QPixmap& pixmap, const QString& text, + QWidget* parent) + : QWidget{parent} + , mPixmap{pixmap} +{ + auto icon = new QLabel{this}; + icon->setPixmap(pixmap); + icon->setAlignment(Qt::AlignCenter); + icon->setObjectName("backgroundIcon"); + + auto label = new QLabel{this}; + label->setText(text); + label->setAlignment(Qt::AlignCenter); + label->setWordWrap(true); + label->setMaximumWidth(pixmap.width()); + + auto layout = new QVBoxLayout{this}; + layout->setSpacing(0); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(icon); + layout->addWidget(label); + setLayout(layout); +} + +QPixmap UBPreferredBackgroundWidget::BackgroundWidget::pixmap() const +{ + return mPixmap; +} diff --git a/src/gui/UBPreferredBackgroundWidget.h b/src/gui/UBPreferredBackgroundWidget.h new file mode 100644 index 000000000..69cb38561 --- /dev/null +++ b/src/gui/UBPreferredBackgroundWidget.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2015-2024 Département de l'Instruction Publique (DIP-SEM) + * + * This file is part of OpenBoard. + * + * OpenBoard 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, + * 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 OpenBoard. If not, see . + */ + + + +#pragma once + +#include + +// forward +class UBFlowLayout; +class QLabel; +class QLayoutItem; + +class UBPreferredBackgroundWidget : public QWidget +{ + Q_OBJECT + +public: + explicit UBPreferredBackgroundWidget(QWidget* parent = nullptr); + +protected: + void dragEnterEvent(QDragEnterEvent* event) override; + void dragMoveEvent(QDragMoveEvent* event) override; + void dropEvent(QDropEvent* event) override; + void mousePressEvent(QMouseEvent* event) override; + +private: + void savePreferrdBackgrounds() const; + +private slots: + void updateBackgroundButtons(); + +private: + UBFlowLayout* mLayout{nullptr}; + QLabel* mPlaceholder{nullptr}; + QLabel* mTrash{nullptr}; + QLayoutItem* mDraggedItem{nullptr}; + int mDraggedItemPos{-1}; + + class BackgroundWidget : public QWidget + { + public: + explicit BackgroundWidget(const QPixmap& pixmap, const QString& text, QWidget* parent = nullptr); + + QPixmap pixmap() const; + + private: + QPixmap mPixmap; + }; +}; diff --git a/src/gui/UBScreenMirror.cpp b/src/gui/UBScreenMirror.cpp index ff9810862..04b699094 100644 --- a/src/gui/UBScreenMirror.cpp +++ b/src/gui/UBScreenMirror.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2022 Département de l'Instruction Publique (DIP-SEM) + * Copyright (C) 2015-2025 Département de l'Instruction Publique (DIP-SEM) * * Copyright (C) 2013 Open Education Foundation * @@ -34,6 +34,12 @@ #include "core/UBDisplayManager.h" #include "board/UBBoardController.h" +#ifdef Q_OS_LINUX +#include "frameworks/UBDesktopPortal.h" +#include "frameworks/UBPipewireSink.h" +#include "frameworks/UBPlatformUtils.h" +#endif + #if defined(Q_OS_OSX) #include #endif @@ -90,21 +96,79 @@ void UBScreenMirror::grabPixmap() if (mSourceWidget) { mLastPixmap = mSourceWidget->grab(); + + if (!mLastPixmap.isNull()) + mLastPixmap = mLastPixmap.scaled(width(), height(), Qt::KeepAspectRatio, Qt::SmoothTransformation); } else { - mLastPixmap = UBApplication::displayManager->grab(ScreenRole::Control); + UBApplication::displayManager->grab(ScreenRole::Control, [this](QPixmap pixmap){ + if (!pixmap.isNull()) + mLastPixmap = pixmap.scaled(width(), height(), Qt::KeepAspectRatio, Qt::SmoothTransformation); + }); } +} - if (!mLastPixmap.isNull()) - mLastPixmap = mLastPixmap.scaled(size() * mLastPixmap.devicePixelRatioF(), Qt::KeepAspectRatio, Qt::SmoothTransformation); +#ifdef Q_OS_LINUX +void UBScreenMirror::startScreenCast() +{ + // use UBDesktopPortal + if (!mPortal) + { + mPortal = new UBDesktopPortal(this); + + connect(mPortal, &UBDesktopPortal::streamStarted, this, &UBScreenMirror::playStream); + connect(mPortal, &UBDesktopPortal::screenCastAborted, this, [](){ + UBApplication::applicationController->mirroringEnabled(false); + }); + } + + mPortal->startScreenCast(true); } +void UBScreenMirror::playStream(int fd, int nodeId) +{ + qDebug() << "Start stream player" << fd << nodeId; + auto sink = new UBPipewireSink(this); + + connect(sink, &UBPipewireSink::gotImage, this, [this](QImage image){ + mLastPixmap = QPixmap::fromImage(image).scaled(width(), height(), Qt::KeepAspectRatio, Qt::SmoothTransformation); + update(); + }); + + connect(sink, &UBPipewireSink::streamingInterrupted, sink, &QObject::deleteLater); + + sink->start(fd, nodeId); +} +#endif void UBScreenMirror::setSourceWidget(QWidget *sourceWidget) { + if (mSourceWidget == sourceWidget) + { + return; + } + + qDebug() << "Mirror: setSourceWidget" << sourceWidget; mSourceWidget = sourceWidget; +#ifdef Q_OS_LINUX + if (mSourceWidget == nullptr && mIsStarted && UBPlatformUtils::sessionType() == UBPlatformUtils::WAYLAND) + { + // stop timer + if (mTimerID != 0) + { + killTimer(mTimerID); + mTimerID = 0; + } + + // use desktop portal to start a screencast + mLastPixmap = {}; + startScreenCast(); + return; + } +#endif + grabPixmap(); update(); @@ -113,8 +177,25 @@ void UBScreenMirror::setSourceWidget(QWidget *sourceWidget) void UBScreenMirror::start() { + if (mIsStarted) + { + return; + } + qDebug() << "mirroring START"; + mIsStarted = true; UBApplication::boardController->freezeW3CWidgets(true); + +#ifdef Q_OS_LINUX + if (mSourceWidget == nullptr && UBPlatformUtils::sessionType() == UBPlatformUtils::WAYLAND) + { + // use desktop portal to start a screencast + mLastPixmap = {}; + startScreenCast(); + return; + } +#endif + if (mTimerID == 0) { int ms = 125; @@ -138,11 +219,24 @@ void UBScreenMirror::start() void UBScreenMirror::stop() { + if (!mIsStarted) + { + return; + } + qDebug() << "mirroring STOP"; + mIsStarted = false; UBApplication::boardController->freezeW3CWidgets(false); if (mTimerID != 0) { killTimer(mTimerID); mTimerID = 0; } + +#ifdef Q_OS_LINUX + if (mPortal) + { + mPortal->stopScreenCast(); + } +#endif } diff --git a/src/gui/UBScreenMirror.h b/src/gui/UBScreenMirror.h index 59b918d68..92923c9b6 100644 --- a/src/gui/UBScreenMirror.h +++ b/src/gui/UBScreenMirror.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2022 Département de l'Instruction Publique (DIP-SEM) + * Copyright (C) 2015-2025 Département de l'Instruction Publique (DIP-SEM) * * Copyright (C) 2013 Open Education Foundation * @@ -33,9 +33,13 @@ #include #include +// forward +class UBDesktopPortal; + + class UBScreenMirror : public QWidget { - Q_OBJECT; + Q_OBJECT public: UBScreenMirror(QWidget* parent = 0); @@ -55,13 +59,19 @@ class UBScreenMirror : public QWidget private: void grabPixmap(); +#ifdef Q_OS_LINUX + void startScreenCast(); + void playStream(int fd, int nodeId); +#endif QWidget* mSourceWidget; - QPixmap mLastPixmap; - long mTimerID; + bool mIsStarted{false}; +#ifdef Q_OS_LINUX + UBDesktopPortal* mPortal{nullptr}; +#endif }; #endif /* UBSCREENMIRROR_H_ */ diff --git a/src/gui/UBSpinningWheel.cpp b/src/gui/UBSpinningWheel.cpp index bb7fecbc4..310841db5 100644 --- a/src/gui/UBSpinningWheel.cpp +++ b/src/gui/UBSpinningWheel.cpp @@ -62,6 +62,7 @@ void UBSpinningWheel::paintEvent(QPaintEvent *event) painter.translate(width() / 2, height() / 2); QPen pen; + pen.setColor(palette().color(QPalette::WindowText)); pen.setWidthF(side / 6.5); pen.setCapStyle(Qt::RoundCap); diff --git a/src/gui/UBStartupHintsPalette.cpp b/src/gui/UBStartupHintsPalette.cpp index e4da13f46..ecfce4e3e 100644 --- a/src/gui/UBStartupHintsPalette.cpp +++ b/src/gui/UBStartupHintsPalette.cpp @@ -35,6 +35,7 @@ #include #include "UBStartupHintsPalette.h" #include "core/UBApplication.h" +#include "core/UBThemeManager.h" #include "web/UBWebController.h" #include "globals/UBGlobals.h" @@ -43,12 +44,13 @@ - UBStartupHintsPalette::UBStartupHintsPalette(QWidget *parent) : UBFloatingPalette(Qt::BottomRightCorner,parent) { setObjectName("UBStartupHintsPalette"); + setClosable(true); setFixedSize(800,600); + mLayout = new QVBoxLayout(); mLayout->setContentsMargins(10,28,10,10); setLayout(mLayout); @@ -67,8 +69,12 @@ UBStartupHintsPalette::UBStartupHintsPalette(QWidget *parent) : mShowNextTime = new QCheckBox(tr("Visible next time"),this); mShowNextTime->setChecked(UBSettings::settings()->appStartupHintsEnabled->get().toBool()); connect(mShowNextTime,SIGNAL(stateChanged(int)),this,SLOT(onShowNextTimeStateChanged(int))); + + connect(UBThemeManager::instance(), &UBThemeManager::themeChanged, mpSankoreAPI, &UBWidgetUniboardAPI::themeChanged); + mButtonLayout->addStretch(); mButtonLayout->addWidget(mShowNextTime); + hide(); } @@ -81,28 +87,6 @@ UBStartupHintsPalette::~UBStartupHintsPalette() void UBStartupHintsPalette::paintEvent(QPaintEvent *event) { UBFloatingPalette::paintEvent(event); - - QPainter painter(this); - painter.drawPixmap(0, 0, QPixmap(":/images/close.svg")); -} - - -void UBStartupHintsPalette::close() -{ - hide(); -} - - -void UBStartupHintsPalette::mouseReleaseEvent(QMouseEvent * event) -{ - if (event->pos().x() >= 0 && event->pos().x() < QPixmap(":/images/close.svg").width() - && event->pos().y() >= 0 && event->pos().y() < QPixmap(":/images/close.svg").height()) - { - event->accept(); - close(); - } - - UBFloatingPalette::mouseReleaseEvent(event); } void UBStartupHintsPalette::onShowNextTimeStateChanged(int state) diff --git a/src/gui/UBStartupHintsPalette.h b/src/gui/UBStartupHintsPalette.h index 892ded60e..7f1da6a6b 100644 --- a/src/gui/UBStartupHintsPalette.h +++ b/src/gui/UBStartupHintsPalette.h @@ -53,11 +53,9 @@ public slots: protected: virtual void paintEvent(QPaintEvent *event); - virtual void mouseReleaseEvent(QMouseEvent * event); virtual void showEvent(QShowEvent *event); private: - void close(); int border(); QCheckBox* mShowNextTime; QVBoxLayout* mLayout; diff --git a/src/gui/UBThumbnail.cpp b/src/gui/UBThumbnail.cpp index 3b7b3870b..c26e1a6b9 100644 --- a/src/gui/UBThumbnail.cpp +++ b/src/gui/UBThumbnail.cpp @@ -56,13 +56,28 @@ UBThumbnail::UBThumbnail() // smooth transformation mPixmapItem->setTransformationMode(Qt::SmoothTransformation); + // create a reasonably sized empty pixmap, which is later replaced + const auto pageSize = UBSettings::settings()->pageSize->get().toSize(); + const auto pixmapSize = pageSize.scaled(UBSettings::maxThumbnailWidth, 0, Qt::KeepAspectRatioByExpanding); + QPixmap pixmap{pixmapSize}; + pixmap.fill(qApp->palette().color(QPalette::Mid)); + mPixmapItem->setPixmap(pixmap); + // do not draw anything setPen(QPen(QColor(Qt::transparent))); } void UBThumbnail::setPixmap(const QPixmap& pixmap) { + const auto needsAdjust = pixmap.size() != mPixmapItem->pixmap().size(); + mPixmapItem->setPixmap(pixmap); + mLoaded = true; + + if (needsAdjust) + { + adjustThumbnail(); + } } QPixmap UBThumbnail::pixmap() const @@ -70,6 +85,11 @@ QPixmap UBThumbnail::pixmap() const return mPixmapItem->pixmap(); } +bool UBThumbnail::isLoaded() const +{ + return mLoaded; +} + void UBThumbnail::setSceneIndex(int sceneIndex) { mIndex = sceneIndex; @@ -239,8 +259,7 @@ QVariant UBThumbnail::itemChange(GraphicsItemChange change, const QVariant& valu { if (change == QGraphicsItem::ItemSelectedHasChanged) { - const auto penColor = isSelected() ? UBSettings::treeViewBackgroundColor : Qt::transparent; - setPen({penColor, cSelectionWidth}); + update(); } return QGraphicsRectItem::itemChange(change, value); @@ -296,7 +315,7 @@ void UBThumbnail::paint(QPainter* painter, const QStyleOptionGraphicsItem* optio if (isSelected()) { const auto selectionRect = mapRectFromItem(mPixmapItem, mPixmapItem->boundingRect()) + cSelectionMargins; - painter->setPen(pen()); + painter->setPen(QPen(qApp->palette().color(QPalette::Highlight), cSelectionWidth)); painter->drawRect(selectionRect); } diff --git a/src/gui/UBThumbnail.h b/src/gui/UBThumbnail.h index c8acd8923..7cf252cf6 100644 --- a/src/gui/UBThumbnail.h +++ b/src/gui/UBThumbnail.h @@ -44,6 +44,8 @@ class UBThumbnail : public QGraphicsRectItem void setPixmap(const QPixmap& pixmap); QPixmap pixmap() const; + bool isLoaded() const; + void setSceneIndex(int sceneIndex); int sceneIndex() const; void setThumbnailSize(QSizeF size); @@ -75,6 +77,7 @@ class UBThumbnail : public QGraphicsRectItem private: QGraphicsPixmapItem* mPixmapItem{nullptr}; UBThumbnailTextItem* mTextItem{nullptr}; + bool mLoaded{false}; int mIndex{0}; int mColumn{0}; int mRow{0}; diff --git a/src/gui/UBThumbnailScene.cpp b/src/gui/UBThumbnailScene.cpp index bd2e1403d..6b2275472 100644 --- a/src/gui/UBThumbnailScene.cpp +++ b/src/gui/UBThumbnailScene.cpp @@ -23,9 +23,7 @@ #include "UBThumbnailScene.h" #include "adaptors/UBThumbnailAdaptor.h" -#include "core/UBApplication.h" #include "document/UBDocument.h" -#include "document/UBDocumentProxy.h" #include "frameworks/UBBackgroundLoader.h" #include "gui/UBThumbnail.h" #include "gui/UBThumbnailArranger.h" @@ -33,7 +31,7 @@ UBThumbnailScene::UBThumbnailScene(UBDocument* document) : mDocument{document} - , mThumbnailItems{document->proxy()->pageCount()} + , mThumbnailItems{} { } @@ -156,7 +154,7 @@ UBThumbnail* UBThumbnailScene::thumbnailAt(int index) // create the missing thumbnail auto thumbnailItem = new UBThumbnail; - thumbnailItem->setPixmap(UBThumbnailAdaptor::get(mDocument->proxy(), index)); + thumbnailItem->setPixmap(UBThumbnailAdaptor::get(mDocument, index)); thumbnailItem->setSceneIndex(index); mThumbnailItems[index] = thumbnailItem; @@ -190,8 +188,15 @@ UBThumbnail* UBThumbnailScene::lastSelectedThumbnail() const */ void UBThumbnailScene::createThumbnails(int startIndex) { + // adjust number of thumbnail items and create empty thumbnails in new items + for (int index = mThumbnailItems.count(); index < mDocument->pageCount(); ++index) + { + insertThumbnail(index, false); + } + // skip already loaded thumbnails - while (startIndex < mThumbnailItems.count() && mThumbnailItems.at(startIndex)) + while (startIndex < mThumbnailItems.count() && mThumbnailItems.at(startIndex) + && mThumbnailItems.at(startIndex)->isLoaded()) { ++startIndex; } @@ -199,9 +204,10 @@ void UBThumbnailScene::createThumbnails(int startIndex) // create the list of all thumbnail paths QList> paths; - for (int index = startIndex; index < mDocument->proxy()->pageCount(); ++index) + for (int index = startIndex; index < mDocument->pageCount(); ++index) { - paths << std::pair{index, UBThumbnailAdaptor::thumbnailUrl(mDocument->proxy(), index).toLocalFile()}; + const auto thumbnailPath = UBThumbnailAdaptor::thumbnailUrl(mDocument, index).toLocalFile(); + paths << std::pair{index, thumbnailPath}; } // start background loading of files @@ -218,31 +224,49 @@ void UBThumbnailScene::createThumbnails(int startIndex) return; } - mLoader = new UBBackgroundLoader{paths, this}; - mLoader->start(); + mLoader = new UBBackgroundLoader{UBBackgroundLoader::Pixmap, this}; + + connect(mLoader, &UBBackgroundLoader::resultAvailable, this, [this](int index, const QVariant& data){ + // now generate all missing thumbnails for document as they arrive from the loader + loadThumbnail(index, data); + + if (mLoader) + { + // delay next thumbnail processing after a specified number of thumbnails + // to improve user experience when simultaneously interacting with the board + if (index % 10 == 0) + { + QTimer::singleShot(5, this, [this](){ + mLoader->resultProcessed(); + }); + } + else + { + mLoader->resultProcessed(); + } + } + }); + + connect(mLoader, &UBBackgroundLoader::finished, this, [this](){ + // delete loader + mLoader->deleteLater(); + mLoader = nullptr; + }); - // now create all missing thumbnails for document as they arrive from the loader - loadNextThumbnail(); + mLoader->load(paths); } /** * @brief Insert a thumbnail. * - * Insert a thumbnail at the given index. This function assumes that the thumbnail pixmap files - * are already moved to provide the necessary space. The pixmap for the new thumbnail can either - * be provided in the corresponding pixmap file or it will be created from the scene. + * Insert a thumbnail at the given index. * * @param pageIndex Page index of the scene. - * @param pageScene Optional scene. If this parameter is provided, then the tumbnail pixmap is - * created from the scene. Otherwise it is assumed that the pixmap file already exists. + * @param loadThumbnail If true, load the thumbnail from the file (default). + * If false, only an empty thumbnail is created which will be filled later. */ -void UBThumbnailScene::insertThumbnail(int pageIndex, std::shared_ptr pageScene) +void UBThumbnailScene::insertThumbnail(int pageIndex, bool loadThumbnail) { - if (pageScene) - { - UBThumbnailAdaptor::persistScene(mDocument->proxy(), pageScene, pageIndex); - } - if (pageIndex <= mThumbnailItems.size()) { if (mThumbnailItems.size() == 1) @@ -252,7 +276,12 @@ void UBThumbnailScene::insertThumbnail(int pageIndex, std::shared_ptrsetPixmap(UBThumbnailAdaptor::get(mDocument->proxy(), pageIndex)); + if (loadThumbnail) + { + const auto pixmap = UBThumbnailAdaptor::get(mDocument, pageIndex); + thumbnailItem->setPixmap(pixmap); + } + thumbnailItem->setSceneIndex(pageIndex); mThumbnailItems.insert(pageIndex, thumbnailItem); @@ -262,10 +291,10 @@ void UBThumbnailScene::insertThumbnail(int pageIndex, std::shared_ptrsetPixmap(UBThumbnailAdaptor::get(mDocument->proxy(), pageIndex)); + thumbnail->setPixmap(UBThumbnailAdaptor::get(mDocument, pageIndex)); + arrangeThumbnails(pageIndex, pageIndex + 1); + } + } +} + +void UBThumbnailScene::ensureThumbnail(int pageIndex, UBGraphicsScene* scene) +{ + if (pageIndex < mThumbnailItems.size()) + { + auto thumbnailItem = mThumbnailItems.at(pageIndex); + + if (!thumbnailItem || !thumbnailItem->isLoaded()) + { + QPixmap pixmap = UBThumbnailAdaptor::generateMissingThumbnail(mDocument, pageIndex, scene); + + if (!thumbnailItem) + { + thumbnailItem = new UBThumbnail; + } + + thumbnailItem->setPixmap(pixmap); + thumbnailItem->setSceneIndex(pageIndex); + + if (!mThumbnailItems.at(pageIndex)) + { + mThumbnailItems[pageIndex] = thumbnailItem; + addItem(thumbnailItem); + } + arrangeThumbnails(pageIndex, pageIndex + 1); } } @@ -369,87 +427,26 @@ UBThumbnailArranger* UBThumbnailScene::currentThumbnailArranger() return thumbnailArranger; } -void UBThumbnailScene::loadNextThumbnail() +void UBThumbnailScene::loadThumbnail(int index, const QVariant& data) { - // max number of thumbnails to load in one pass - constexpr int bulkSize{10}; + auto thumbnailItem = mThumbnailItems.at(index); - if (!mLoader->isIdle()) + if (!thumbnailItem) { - if (UBApplication::isClosing) - { - return; - } - - if (!mLoader->isResultAvailable()) - { - // no data available, defer next execution - QTimer::singleShot(50, mLoader, [this]() { loadNextThumbnail(); }); - return; - } - - int firstIndex = -1; - - for (int i = 0; i < bulkSize; ++i) - { - if (!mLoader->isResultAvailable()) - { - break; - } - - // take and process next result - const auto result = mLoader->takeResult(); - const auto index = result.first; - auto thumbnailItem = mThumbnailItems.at(index); - - if (!thumbnailItem) - { - QPixmap pixmap; - - if (result.second.isEmpty()) - { - pixmap = UBThumbnailAdaptor::generateMissingThumbnail(mDocument->proxy(), index); - } - else - { - pixmap.loadFromData(result.second); - } - - thumbnailItem = new UBThumbnail; - - thumbnailItem->setPixmap(pixmap); - thumbnailItem->setSceneIndex(index); - - mThumbnailItems[index] = thumbnailItem; - addItem(thumbnailItem); - - if (firstIndex < 0) - { - firstIndex = index; - } - } - } - - if (firstIndex >= 0) - { - arrangeThumbnails(firstIndex); - } - - // load next thumbnails in a deferred task executed on the main thread when it is idle. - QTimer::singleShot(1, mLoader, [this]() { loadNextThumbnail(); }); + thumbnailItem = new UBThumbnail; + mThumbnailItems[index] = thumbnailItem; + addItem(thumbnailItem); } - else - { - // finished. set undeletable if only one page - if (mThumbnailItems.size() == 1) - { - thumbnailAt(0)->setDeletable(false); - } - // delete background loader - delete mLoader; - mLoader = nullptr; + if (!data.isNull()) + { + QPixmap pixmap = data.value(); + thumbnailItem->setPixmap(pixmap); } + + thumbnailItem->setSceneIndex(index); + + arrangeThumbnails(index, index + 1); } void UBThumbnailScene::renumberThumbnails(int fromIndex, int toIndex) const diff --git a/src/gui/UBThumbnailScene.h b/src/gui/UBThumbnailScene.h index 942aeb320..4a453702f 100644 --- a/src/gui/UBThumbnailScene.h +++ b/src/gui/UBThumbnailScene.h @@ -63,15 +63,16 @@ class UBThumbnailScene : public QGraphicsScene protected: // only to be called from UBDocument friend class UBDocument; - void insertThumbnail(int pageIndex, std::shared_ptr pageScene = nullptr); + void insertThumbnail(int pageIndex, bool loadThumbnail = true); void deleteThumbnail(int pageIndex, bool rearrange = true); void moveThumbnail(int fromIndex, int toIndex); void reloadThumbnail(int pageIndex); + void ensureThumbnail(int pageIndex, UBGraphicsScene* scene); private: friend class UBThumbnail; UBThumbnailArranger* currentThumbnailArranger(); - void loadNextThumbnail(); + void loadThumbnail(int index, const QVariant& data); void renumberThumbnails(int fromIndex = 0, int toIndex = -1) const; private: diff --git a/src/gui/UBToolbarButtonGroup.cpp b/src/gui/UBToolbarButtonGroup.cpp index 2c2e25233..1daba1e3b 100644 --- a/src/gui/UBToolbarButtonGroup.cpp +++ b/src/gui/UBToolbarButtonGroup.cpp @@ -40,10 +40,11 @@ #include "core/memcheck.h" -UBToolbarButtonGroup::UBToolbarButtonGroup(QToolBar *toolBar, const QList &actions, QString objectNameprefix) +UBToolbarButtonGroup::UBToolbarButtonGroup(QToolBar *toolBar, const QList &actions, QString objectNameprefix, int selectableCount) : QWidget(toolBar) , mActions(actions) , mCurrentIndex(-1) + , mSelectableCount(selectableCount <= 0 ? actions.size() : qMin(selectableCount, actions.size())) , mDisplayLabel(true) , mActionGroup(0) { @@ -68,12 +69,17 @@ UBToolbarButtonGroup::UBToolbarButtonGroup(QToolBar *toolBar, const QListaddAction(action); + if (action->isCheckable()) + mActionGroup->addAction(action); QToolButton *button = new QToolButton(this); mButtons.append(button); button->setDefaultAction(action); - button->setCheckable(true); + button->setCheckable(action->isCheckable()); + if (action->objectName() == QLatin1String("actionColorPreferences")) + { + button->setProperty("colorPaletteConfigButton", true); + } if(i == 0) { @@ -95,13 +101,15 @@ UBToolbarButtonGroup::UBToolbarButtonGroup(QToolBar *toolBar, const QListsetObjectName("ubButtonGroupCenter"); } - connect(button, SIGNAL(triggered(QAction*)), this, SLOT(selected(QAction*))); + connect(button, SIGNAL(triggered(QAction*)), this, SLOT(setSelected(QAction*))); horizontalLayout->addWidget(button); mLabel = action->text(); buttonSize = button->sizeHint(); i++; } + + setSelectableCount(mSelectableCount); } UBToolbarButtonGroup::~UBToolbarButtonGroup() @@ -114,6 +122,33 @@ void UBToolbarButtonGroup::setLabel(const QString& label) mLabel = label; } +void UBToolbarButtonGroup::setSelectableCount(int count) +{ + mSelectableCount = qBound(0, count, mActions.size()); + + for (int i = 0; i < mButtons.size(); ++i) + { + QAction* action = mActions.at(i); + bool selectable = action->isCheckable() && i < mSelectableCount; + + if (action->isCheckable()) + { + action->setEnabled(selectable); + mButtons.at(i)->setVisible(selectable); + } + else + { + action->setEnabled(true); + mButtons.at(i)->setVisible(true); + } + } + + if (mCurrentIndex >= mSelectableCount) + { + mCurrentIndex = -1; + } +} + void UBToolbarButtonGroup::setIcon(const QIcon &icon, int index) { Q_ASSERT(index < mActions.size()); @@ -144,30 +179,17 @@ void UBToolbarButtonGroup::setColor(const QColor &color, int index) setIcon(icon, index); } -void UBToolbarButtonGroup::selected(QAction *action) +void UBToolbarButtonGroup::setSelected(QAction *action) { -#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) - foreach(QObject *widget, action->associatedObjects()) -#else - foreach(QWidget *widget, action->associatedWidgets()) -#endif - { - QToolButton *button = qobject_cast(widget); - if (button) - { - int i = 0; - foreach(QAction *eachAction, mActions) - { - if (eachAction == action) - { - setCurrentIndex(i); - emit activated(i); - break; - } - i++; - } - } - } + int index = mActions.indexOf(action); + if (index < 0 || index >= mSelectableCount) + return; + + if (!action->isCheckable()) + return; + + setCurrentIndex(index); + emit activated(index); } int UBToolbarButtonGroup::currentIndex() const @@ -177,7 +199,10 @@ int UBToolbarButtonGroup::currentIndex() const void UBToolbarButtonGroup::setCurrentIndex(int index) { - Q_ASSERT(index < mButtons.size()); + Q_ASSERT(index < mSelectableCount || mSelectableCount == 0); + + if (mSelectableCount == 0) + return; if (index != mCurrentIndex) { @@ -224,6 +249,8 @@ void UBToolbarButtonGroup::paintEvent(QPaintEvent *) void UBToolbarButtonGroup::colorPaletteChanged() { + setSelectableCount(qMin(UBSettings::settings()->colorPaletteSize, mActions.size())); + bool isDarkBackground = UBSettings::settings()->isDarkBackground(); QList colors; @@ -238,7 +265,7 @@ void UBToolbarButtonGroup::colorPaletteChanged() colors = UBSettings::settings()->markerColors(isDarkBackground); } - for (int i = 0; i < mButtons.size() && i < colors.size(); i++) + for (int i = 0; i < mSelectableCount && i < colors.size(); i++) { setColor(colors.at(i), i); } diff --git a/src/gui/UBToolbarButtonGroup.h b/src/gui/UBToolbarButtonGroup.h index 6ab0d785a..25e419839 100644 --- a/src/gui/UBToolbarButtonGroup.h +++ b/src/gui/UBToolbarButtonGroup.h @@ -42,13 +42,15 @@ class UBToolbarButtonGroup : public QWidget Q_OBJECT; public: - UBToolbarButtonGroup(QToolBar *toolbar, const QList &actions = QList(), QString objectNameprefix = ""); + UBToolbarButtonGroup(QToolBar *toolbar, const QList &actions = QList(), QString objectNameprefix = "", int selectableCount = -1); virtual ~UBToolbarButtonGroup(); void setIcon(const QIcon &icon, int index); void setColor(const QColor &color, int index); int currentIndex() const; void setLabel(const QString& label); + void setSelectableCount(int count); + int selectableCount() const { return mSelectableCount; } protected: void paintEvent(QPaintEvent *); @@ -59,6 +61,7 @@ class UBToolbarButtonGroup : public QWidget QList mActions; QList mButtons; int mCurrentIndex; + int mSelectableCount; bool mDisplayLabel; QActionGroup* mActionGroup; @@ -68,7 +71,7 @@ class UBToolbarButtonGroup : public QWidget void displayText(QVariant display); private slots: - void selected(QAction *action); + void setSelected(QAction *action); signals: void activated(int index); diff --git a/src/gui/gui.pri b/src/gui/gui.pri index 831d06d18..5d9614ec1 100644 --- a/src/gui/gui.pri +++ b/src/gui/gui.pri @@ -45,6 +45,10 @@ HEADERS += \ src/gui/UBFeaturesActionBar.h \ src/gui/UBMessagesDialog.h \ src/gui/UBBackgroundPalette.h \ + src/gui/UBBackgroundManager.h \ + src/gui/UBBackgroundRuling.h \ + src/gui/UBFlowLayout.h \ + src/gui/UBPreferredBackgroundWidget.h \ src/gui/UBBoardThumbnailsView.h \ src/gui/UBSnapIndicator.h SOURCES += \ @@ -94,8 +98,13 @@ SOURCES += \ src/gui/UBFeaturesActionBar.cpp \ src/gui/UBMessagesDialog.cpp \ src/gui/UBBackgroundPalette.cpp \ + src/gui/UBBackgroundManager.cpp \ + src/gui/UBBackgroundRuling.cpp \ + src/gui/UBFlowLayout.cpp \ + src/gui/UBPreferredBackgroundWidget.cpp \ src/gui/UBBoardThumbnailsView.cpp \ src/gui/UBSnapIndicator.cpp + win32:SOURCES += src/gui/UBKeyboardPalette_win.cpp macx:OBJECTIVE_SOURCES += src/gui/UBKeyboardPalette_mac.mm linux-g++:SOURCES += src/gui/UBKeyboardPalette_linux.cpp diff --git a/src/pdf-merger/OverlayDocumentParser.cpp b/src/pdf-merger/OverlayDocumentParser.cpp index e885688b2..98cdfc124 100644 --- a/src/pdf-merger/OverlayDocumentParser.cpp +++ b/src/pdf-merger/OverlayDocumentParser.cpp @@ -39,7 +39,7 @@ using namespace merge_lib; using namespace std; -int OverlayDocumentParser::DOC_PART_WITH_START_OF_XREF = 30; +int OverlayDocumentParser::DOC_PART_WITH_START_OF_XREF = 1024; unsigned int partSize = 10485760; // = 10 Mb Document * OverlayDocumentParser::parseDocument(const char * fileName) diff --git a/src/pdf/GraphicsPDFItem.cpp b/src/pdf/GraphicsPDFItem.cpp index 729f8b75f..6901d5a03 100644 --- a/src/pdf/GraphicsPDFItem.cpp +++ b/src/pdf/GraphicsPDFItem.cpp @@ -85,6 +85,17 @@ void GraphicsPDFItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *o } +void GraphicsPDFItem::switchRenderer(PDFRenderer* newRenderer) +{ + disconnect(mRenderer, SIGNAL(signalUpdateParent()), this, SLOT(OnRequireUpdate())); + mRenderer->detach(); + + mRenderer = newRenderer; + + mRenderer->attach(); + connect(mRenderer, SIGNAL(signalUpdateParent()), this, SLOT(OnRequireUpdate())); +} + void GraphicsPDFItem::OnRequireUpdate() { updateChild(); diff --git a/src/pdf/GraphicsPDFItem.h b/src/pdf/GraphicsPDFItem.h index 0eb9ed5cd..5ae1d3b02 100644 --- a/src/pdf/GraphicsPDFItem.h +++ b/src/pdf/GraphicsPDFItem.h @@ -54,6 +54,10 @@ class GraphicsPDFItem : public QObject, public QGraphicsItem void setCacheAllowed(bool const value) { mIsCacheAllowed = value; } QSizeF pageSize() const { return mRenderer->pointSizeF(mPageNumber); } virtual void updateChild() = 0; + + protected: + void switchRenderer(PDFRenderer* newRenderer); + protected: PDFRenderer *mRenderer; int mPageNumber; diff --git a/src/pdf/PDFRenderer.cpp b/src/pdf/PDFRenderer.cpp index 37bc4989e..460de4faa 100644 --- a/src/pdf/PDFRenderer.cpp +++ b/src/pdf/PDFRenderer.cpp @@ -63,9 +63,15 @@ PDFRenderer* PDFRenderer::rendererForUuid(const QUuid &uuid, const QString &file newRenderer->setFileUuid(uuid); QFile file(filename); - file.open(QIODevice::ReadOnly); - newRenderer->setFileData(file.readAll()); - file.close(); + if (file.open(QIODevice::ReadOnly)) + { + newRenderer->setFileData(file.readAll()); + file.close(); + } + else + { + qDebug() << "Could not open PDF at " << file.fileName(); + } sRenderers.insert(newRenderer->fileUuid(), newRenderer); diff --git a/src/pdf/XPDFRenderer.cpp b/src/pdf/XPDFRenderer.cpp index 4d6c98f8f..8e361430c 100644 --- a/src/pdf/XPDFRenderer.cpp +++ b/src/pdf/XPDFRenderer.cpp @@ -235,7 +235,11 @@ QImage* XPDFRenderer::createPDFImageUncached(int pageNumber, qreal xscale, qreal if(mSplashUncached) delete mSplashUncached; +#if POPPLER_VERSION_MAJOR > 26 || (POPPLER_VERSION_MAJOR == 26 && POPPLER_VERSION_MINOR >= 2) + mSplashUncached = new SplashOutputDev(splashModeRGB8, 1, constants::paperColor); +#else mSplashUncached = new SplashOutputDev(splashModeRGB8, 1, false, constants::paperColor); +#endif mSplashUncached->startDoc(mDocument); int rotation = 0; // in degrees (get it from the worldTransform if we want to support rotation) diff --git a/src/pdf/XPDFRenderer.h b/src/pdf/XPDFRenderer.h index 0e8de81a2..6264ae1b0 100644 --- a/src/pdf/XPDFRenderer.h +++ b/src/pdf/XPDFRenderer.h @@ -42,6 +42,7 @@ #include #include #include +#include class PDFDoc; @@ -105,7 +106,11 @@ class XPDFRenderer : public PDFRenderer cachedImage = QImage(); delete splash; } +#if POPPLER_VERSION_MAJOR > 26 || (POPPLER_VERSION_MAJOR == 26 && POPPLER_VERSION_MINOR >= 2) + splash = new SplashOutputDev(splashModeRGB8, 1, paperColor); +#else splash = new SplashOutputDev(splashModeRGB8, 1, false, paperColor); +#endif cachedPageNumber = pageNumber; } diff --git a/src/podcast/UBPodcastController.cpp b/src/podcast/UBPodcastController.cpp index 442972898..b15caa706 100644 --- a/src/podcast/UBPodcastController.cpp +++ b/src/podcast/UBPodcastController.cpp @@ -30,7 +30,6 @@ #include "UBPodcastController.h" #include "frameworks/UBFileSystemUtils.h" -#include "frameworks/UBStringUtils.h" #include "frameworks/UBPlatformUtils.h" #include "core/UBApplication.h" @@ -65,6 +64,8 @@ #include "ffmpeg/UBFFmpegVideoEncoder.h" #include "ffmpeg/UBMicrophoneInput.h" #elif defined(Q_OS_LINUX) + #include "frameworks/UBDesktopPortal.h" + #include "frameworks/UBPipewireSink.h" #include "ffmpeg/UBFFmpegVideoEncoder.h" #include "ffmpeg/UBMicrophoneInput.h" #endif @@ -110,15 +111,19 @@ UBPodcastController::UBPodcastController(QObject* pParent) connect(UBApplication::webController, SIGNAL(activeWebPageChanged(WebView*)), this, SLOT(webActiveWebPageChanged(WebView*))); - connect(UBApplication::app(), SIGNAL(lastWindowClosed()), - this, SLOT(applicationAboutToQuit())); - + connect(UBApplication::mainWindow, &UBMainWindow::closeEvent_Signal, this, &UBPodcastController::applicationAboutToQuit); } UBPodcastController::~UBPodcastController() { - // NOOP + QElapsedTimer elapsed; + elapsed.start(); + + while (mRecordingState != Stopped && elapsed.elapsed() < 1000) + { + QCoreApplication::processEvents(QEventLoop::AllEvents, 100); + } } @@ -246,7 +251,9 @@ void UBPodcastController::setSourceWidget(QWidget* pWidget) startNextChapter(); - if (mIsDesktopMode || UBApplication::applicationController->displayMode() == UBApplicationController::Internet) + // no timer for wayland desktop mode + if ((mIsDesktopMode && UBPlatformUtils::sessionType() != UBPlatformUtils::WAYLAND) || + UBApplication::applicationController->displayMode() == UBApplicationController::Internet) { mScreenGrabingTimerEventID = startTimer(1000 / mVideoFramesPerSecondAtStart); } @@ -450,6 +457,17 @@ void UBPodcastController::stop() if (mRecordingProgressTimerEventID != 0) killTimer(mRecordingProgressTimerEventID); +#ifdef Q_OS_LINUX + if (mPortal) + { + delete mPortal; + delete mPipewireSink; + + mPortal = nullptr; + mPipewireSink = nullptr; + } +#endif + sendLatestPixmapToEncoder(); setRecordingState(Stopping); @@ -627,6 +645,19 @@ void UBPodcastController::applicationMainModeChanged(UBApplicationController::Ma { mIsDesktopMode = false; +#ifdef Q_OS_LINUX + if (mPortal) + { + qDebug() << "Stop ScreenCast"; + mPortal->stopScreenCast(); + + delete mPipewireSink; + mPipewireSink = nullptr; + + mInitialized = false; + } +#endif + if (pMode == UBApplicationController::Internet) { setSourceWidget(UBApplication::webController->controlView()); @@ -645,6 +676,44 @@ void UBPodcastController::applicationDesktopMode(bool displayed) if (displayed) { setSourceWidget(UBApplication::displayManager->widget(ScreenRole::Desktop)); + +#ifdef Q_OS_LINUX + if (UBPlatformUtils::sessionType() == UBPlatformUtils::WAYLAND && !mPipewireSink) + { + // create a ScreenCast session + if (!mPortal) + { + mPortal = new UBDesktopPortal{this}; + + // delete portal if screencast was aborted + connect(mPortal, &UBDesktopPortal::screenCastAborted, this, [this](){ + mPortal->deleteLater(); + mPortal = nullptr; + }); + } + + if (mRecordingState == Recording) + { + mPipewireSink = new UBPipewireSink{this}; + + disconnect(mPortal, &UBDesktopPortal::streamStarted, mPortal, &UBDesktopPortal::stopScreenCast); + connect(mPortal, &UBDesktopPortal::streamStarted, mPipewireSink, &UBPipewireSink::start); + connect(mPipewireSink, &UBPipewireSink::gotImage, this, [this](QImage image){ + if (mPipewireSink) + { + encodeWidgetContent(QPixmap::fromImage(image)); + } + }); + } + else + { + // stop stream, we just selected the screen + connect(mPortal, &UBDesktopPortal::streamStarted, mPortal, &UBDesktopPortal::stopScreenCast); + } + + mPortal->startScreenCast(true); + } +#endif } else { @@ -744,7 +813,10 @@ void UBPodcastController::processScreenGrabingTimerEvent() if (mIsDesktopMode) { - widgetContent = UBApplication::displayManager->grab(ScreenRole::Control); + // TODO implement screencast based on screencast portal + UBApplication::displayManager->grab(ScreenRole::Control, [this](QPixmap pixmap){ + encodeWidgetContent(pixmap); + }); } else { @@ -752,8 +824,12 @@ void UBPodcastController::processScreenGrabingTimerEvent() widgetContent = QPixmap(mSourceWidget->size()); QPainter p(&widgetContent); mSourceWidget->render(&p); + encodeWidgetContent(widgetContent); } +} +void UBPodcastController::encodeWidgetContent(QPixmap pixmap) +{ QPainter p(&mLatestCapture); if (!mInitialized) @@ -762,11 +838,11 @@ void UBPodcastController::processScreenGrabingTimerEvent() mInitialized = true; } - QRectF targetRect = mViewToVideoTransform.mapRect(QRectF(0, 0, widgetContent.width(), widgetContent.height())); + QRectF targetRect = mViewToVideoTransform.mapRect(QRectF(0, 0, pixmap.width(), pixmap.height())); p.setRenderHints(QPainter::Antialiasing); p.setRenderHints(QPainter::SmoothPixmapTransform); - p.drawPixmap(targetRect.left(), targetRect.top(), widgetContent.scaled(targetRect.width(), targetRect.height(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); + p.drawPixmap(targetRect.left(), targetRect.top(), pixmap.scaled(targetRect.width(), targetRect.height(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); sendLatestPixmapToEncoder(); } diff --git a/src/podcast/UBPodcastController.h b/src/podcast/UBPodcastController.h index 975857670..89143892b 100644 --- a/src/podcast/UBPodcastController.h +++ b/src/podcast/UBPodcastController.h @@ -40,6 +40,9 @@ class UBGraphicsScene; class WebView; class UBPodcastRecordingPalette; +class UBDesktopPortal; +class UBPipewireSink; + class UBPodcastController : public QObject { @@ -94,6 +97,7 @@ class UBPodcastController : public QObject private slots: void processScreenGrabingTimerEvent(); + void encodeWidgetContent(QPixmap pixmap); void processScenePaintEvent(); @@ -187,6 +191,10 @@ class UBPodcastController : public QObject QString mPodcastRecordingPath; +#ifdef Q_OS_LINUX + UBDesktopPortal* mPortal{nullptr}; + UBPipewireSink* mPipewireSink{nullptr}; +#endif }; #endif /* UBPODCASTCONTROLLER_H_ */ diff --git a/src/podcast/UBPodcastRecordingPalette.cpp b/src/podcast/UBPodcastRecordingPalette.cpp index a5cfe2176..8082f27b2 100644 --- a/src/podcast/UBPodcastRecordingPalette.cpp +++ b/src/podcast/UBPodcastRecordingPalette.cpp @@ -200,6 +200,6 @@ void UBVuMeter::paintEvent(QPaintEvent* e) int h = (height() - 8) * mVolume / 255; QRectF rect(0, height() - 4 - h, width(), h); - painter.fillRect(rect, UBSettings::documentViewLightColor); + painter.fillRect(rect, UBSettings::documentThumbnailsViewColor); } diff --git a/src/podcast/ffmpeg/UBFFmpegVideoEncoder.cpp b/src/podcast/ffmpeg/UBFFmpegVideoEncoder.cpp index 24451f27a..b946ec210 100644 --- a/src/podcast/ffmpeg/UBFFmpegVideoEncoder.cpp +++ b/src/podcast/ffmpeg/UBFFmpegVideoEncoder.cpp @@ -270,10 +270,17 @@ UBFFmpegVideoEncoder::UBFFmpegVideoEncoder(QObject* parent) UBFFmpegVideoEncoder::~UBFFmpegVideoEncoder() { if (mVideoWorker) - delete mVideoWorker; + { + UBFFmpegVideoEncoder::stop(); + mVideoWorker->deleteLater(); + } if (mVideoEncoderThread) + { + mVideoEncoderThread->quit(); + mVideoEncoderThread->wait(); delete mVideoEncoderThread; + } if (mAudioInput) delete mAudioInput; @@ -448,7 +455,19 @@ bool UBFFmpegVideoEncoder::init() } c->bit_rate = 96000; - c->sample_fmt = audioCodec->sample_fmts ? audioCodec->sample_fmts[0] : AV_SAMPLE_FMT_FLTP;// FLTP by default for AAC + + const AVSampleFormat* sample_fmts; +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(61, 19, 100) + sample_fmts = audioCodec->sample_fmts; +#else + int err = avcodec_get_supported_config(nullptr, audioCodec, AV_CODEC_CONFIG_SAMPLE_FORMAT, 0, (const void**)&sample_fmts, nullptr); + if (err) { + setLastErrorMessage("Could not get supported sample formats"); + return false; + } +#endif + c->sample_fmt = sample_fmts ? sample_fmts[0] : AV_SAMPLE_FMT_FLTP;// FLTP by default for AAC + c->sample_rate = mAudioSampleRate; #if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 25, 100) diff --git a/src/podcast/ffmpeg/UBMicrophoneInput.cpp b/src/podcast/ffmpeg/UBMicrophoneInput.cpp index 25b485ba9..026120db8 100644 --- a/src/podcast/ffmpeg/UBMicrophoneInput.cpp +++ b/src/podcast/ffmpeg/UBMicrophoneInput.cpp @@ -291,6 +291,11 @@ void UBMicrophoneInput::onAudioInputStateChanged(QAudio::State state) } break; +#if (QT_VERSION >= QT_VERSION_CHECK(6, 11, 0)) + case QAudio::IdleState: + qWarning() << "Audio buffer full. Further input might not get processed."; + break; +#endif // handle other states? default: @@ -385,8 +390,10 @@ QString UBMicrophoneInput::getErrorString(QAudio::Error errorCode) case QAudio::IOError : return "Error reading from audio device"; +#if (QT_VERSION < QT_VERSION_CHECK(6, 11, 0)) case QAudio::UnderrunError : return "Underrun error"; +#endif case QAudio::FatalError : return "Fatal error; audio device unusable"; diff --git a/src/podcast/podcast.pri b/src/podcast/podcast.pri index 556d5d6b8..22bab613e 100644 --- a/src/podcast/podcast.pri +++ b/src/podcast/podcast.pri @@ -50,7 +50,7 @@ linux-g++* { -lxcb-xfixes \ -lxcb-render -lxcb-shape -lxcb -lX11 -lasound -lSDL -lx264 -lpthread -lvpx -lvorbisenc -lvorbis -ltheoraenc -ltheoradec -logg -lopus -lmp3lame -lfreetype -lfdk-aac -lass -llzma -lbz2 -lz -ldl -lswresample -lswscale -lavutil -lm - FFMPEG_VERSION = $$system(ffmpeg --version|& grep -oP "version.*?\K[0-9]\.[0-9]") + FFMPEG_VERSION = $$system(ffmpeg --version 2>&1 | grep -oP "version.*?\K[0-9]\.[0-9]") equals(FFMPEG_VERSION, 2.8) { LIBS -= -lswresample LIBS += -lavresample diff --git a/src/tools/UBGraphicsCurtainItem.cpp b/src/tools/UBGraphicsCurtainItem.cpp index 36fa5d688..5d7f6d0ac 100644 --- a/src/tools/UBGraphicsCurtainItem.cpp +++ b/src/tools/UBGraphicsCurtainItem.cpp @@ -86,12 +86,6 @@ QVariant UBGraphicsCurtainItem::itemChange(GraphicsItemChange change, const QVar return QGraphicsRectItem::itemChange(change, newValue); } -void UBGraphicsCurtainItem::setUuid(const QUuid &pUuid) -{ - UBItem::setUuid(pUuid); - setData(UBGraphicsItemData::ItemUuid, QVariant(pUuid)); //store item uuid inside the QGraphicsItem to fast operations with Items on the scene -} - void UBGraphicsCurtainItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { if (Delegate()->mousePressEvent(event)) diff --git a/src/tools/UBGraphicsCurtainItem.h b/src/tools/UBGraphicsCurtainItem.h index fcc68b8f6..6c8b6873b 100644 --- a/src/tools/UBGraphicsCurtainItem.h +++ b/src/tools/UBGraphicsCurtainItem.h @@ -60,9 +60,6 @@ class UBGraphicsCurtainItem : public QObject, public QGraphicsRectItem, public U //TODO UB 4.x not nice ... void triggerRemovedSignal(); - virtual void clearSource(){;} - - virtual void setUuid(const QUuid &pUuid); signals: diff --git a/src/web/UBEmbedController.cpp b/src/web/UBEmbedController.cpp index 114a986ab..7edc7d9f6 100644 --- a/src/web/UBEmbedController.cpp +++ b/src/web/UBEmbedController.cpp @@ -36,7 +36,7 @@ #include "core/UBApplication.h" -#include "domain/UBGraphicsScene.h" +#include "domain/UBMediaAssetItem.h" #include "frameworks/UBFileSystemUtils.h" @@ -198,15 +198,19 @@ void UBEmbedController::createWidget() widgetDir.mkpath(widgetDir.path()); UBEmbedContent content; + QString widgetId; if (selectedIndex == 0) { generateFullPageHtml(mUrl, widgetDir.path(), true); + widgetId = mUrl.toString(); } else { content = mAvailableContent.at(selectedIndex - 1); - generateHtml(content, widgetDir.path(), true); + const auto data = generateHtml(content, widgetDir.path(), true); + const auto mediaAssertUuid = UBMediaAssetItem::createMediaAssetUuid(data); + widgetId = "urn:uuid:" + mediaAssertUuid.toString(QUuid::WithoutBraces); } generateIcon(widgetDir.path()); @@ -216,11 +220,11 @@ void UBEmbedController::createWidget() if (heigth && width) { - generateConfig(width+20, heigth+20, widgetDir.path()); + generateConfig(widgetId, width+20, heigth+20, widgetDir.path()); } else { - generateConfig(800, 600, widgetDir.path()); + generateConfig(widgetId, 800, 600, widgetDir.path()); } importWidgetInLibrary(widgetDir); @@ -301,7 +305,7 @@ QString UBEmbedController::generateIcon(const QString& pDirPath) const } -void UBEmbedController::generateConfig(int pWidth, int pHeight, const QString& pDestinationPath) const +void UBEmbedController::generateConfig(QString widgetId, int pWidth, int pHeight, const QString& pDestinationPath) const { QFile configFile(pDestinationPath + "/" + "config.xml"); @@ -323,7 +327,7 @@ void UBEmbedController::generateConfig(int pWidth, int pHeight, const QString& p out << "" << '\n'; out << "widgetNameLineEdit->text() << "\"" <<'\n'; + out << " id=\"" << widgetId << "\"" <<'\n'; out << " version=\"2.0\"" << '\n'; out << " width=\"" << pWidth << "\"" << '\n'; diff --git a/src/web/UBEmbedController.h b/src/web/UBEmbedController.h index 7177cc8e4..e64b3a38f 100644 --- a/src/web/UBEmbedController.h +++ b/src/web/UBEmbedController.h @@ -126,7 +126,7 @@ private slots: QString generateFullPageHtml(const QUrl& url, const QString& pDirPath = QString(), bool pGenerateFile = false) const; QString generateHtml(const UBEmbedContent& pObject, const QString& pDirPath = QString(), bool pGenerateFile = false) const; QString generateIcon(const QString& pDirPath) const; - void generateConfig(int pWidth, int pHeight, const QString& pDestinationPath) const; + void generateConfig(QString widgetId, int pWidth, int pHeight, const QString& pDestinationPath) const; QString widgetNameForObject(const UBEmbedContent& pObject) const; diff --git a/src/web/UBWebController.cpp b/src/web/UBWebController.cpp index 731aad7f8..aad2d210a 100644 --- a/src/web/UBWebController.cpp +++ b/src/web/UBWebController.cpp @@ -95,7 +95,7 @@ UBWebController::UBWebController(UBMainWindow* mainWindow) } else { - mWebProfile = QWebEngineProfile::defaultProfile(); + mWebProfile = new QWebEngineProfile("Default"); mWebProfile->setHttpCacheType(QWebEngineProfile::DiskHttpCache); } @@ -408,6 +408,7 @@ BrowserWindow* UBWebController::browserWindow() const return mCurrentWebBrowser; } +#if (QT_VERSION < QT_VERSION_CHECK(6, 8, 0)) QWebEnginePage::PermissionPolicy UBWebController::hasFeaturePermission(const QUrl &securityOrigin, QWebEnginePage::Feature feature) { QPair featurePermission(securityOrigin, feature); @@ -425,6 +426,7 @@ void UBWebController::setFeaturePermission(const QUrl &securityOrigin, QWebEngin QPair featurePermission(securityOrigin, feature); mFeaturePermissions[featurePermission] = policy; } +#endif // QT_VERSION < QT_VERSION_CHECK(6, 8, 0) void UBWebController::injectScripts(QWebEngineView *view) { @@ -553,17 +555,20 @@ void UBWebController::customCapture() mToolsCurrentPalette->setVisible(false); qApp->processEvents(); - UBCustomCaptureWindow customCaptureWindow(mCurrentWebBrowser); + UBApplication::displayManager->grab(ScreenRole::Control, [this](QPixmap pixmap){ + UBCustomCaptureWindow customCaptureWindow(mCurrentWebBrowser); + customCaptureWindow.show(); - customCaptureWindow.show(); - - if (customCaptureWindow.execute(getScreenPixmap()) == QDialog::Accepted) - { - QPixmap selectedPixmap = customCaptureWindow.getSelectedPixmap(); - emit imageCaptured(selectedPixmap, false, mCurrentWebBrowser->currentTab()->url()); - } + qDebug() << "before execute"; + if (customCaptureWindow.execute(pixmap) == QDialog::Accepted) + { + qDebug() << "after execute"; + QPixmap selectedPixmap = customCaptureWindow.getSelectedPixmap(); + emit imageCaptured(selectedPixmap, false, mCurrentWebBrowser->currentTab()->url()); + } - mToolsCurrentPalette->setVisible(true); + mToolsCurrentPalette->setVisible(true); + }); } @@ -575,7 +580,9 @@ void UBWebController::toogleMirroring(bool checked) QPixmap UBWebController::getScreenPixmap() { - return UBApplication::displayManager->grab(ScreenRole::Control); + // FIXME implement + return {}; +// return UBApplication::displayManager->grab(ScreenRole::Control); } diff --git a/src/web/UBWebController.h b/src/web/UBWebController.h index 4cabc9c95..ff671dd4b 100644 --- a/src/web/UBWebController.h +++ b/src/web/UBWebController.h @@ -92,8 +92,10 @@ class UBWebController : public QObject QWebEngineProfile* webProfile() const; QList getEmbeddedContent(const QWebEngineView* view) const; BrowserWindow* browserWindow() const; +#if (QT_VERSION < QT_VERSION_CHECK(6, 8, 0)) QWebEnginePage::PermissionPolicy hasFeaturePermission(const QUrl &securityOrigin, QWebEnginePage::Feature feature); void setFeaturePermission(const QUrl &securityOrigin, QWebEnginePage::Feature feature, QWebEnginePage::PermissionPolicy policy); +#endif static void injectScripts(QWebEngineView* view); @@ -163,7 +165,9 @@ class UBWebController : public QObject QMenu* mHistoryBackMenu; QMenu* mHistoryForwardMenu; +#if (QT_VERSION < QT_VERSION_CHECK(6, 8, 0)) QMap,QWebEnginePage::PermissionPolicy> mFeaturePermissions; +#endif bool cookieAutoDelete; QStringList cookieKeepDomains; diff --git a/src/web/simplebrowser/browserwindow.cpp b/src/web/simplebrowser/browserwindow.cpp index c9cb01d1e..4e6022c56 100644 --- a/src/web/simplebrowser/browserwindow.cpp +++ b/src/web/simplebrowser/browserwindow.cpp @@ -67,6 +67,8 @@ #include #include +#include "frameworks/UBPlatformUtils.h" + #include "core/UBApplication.h" #include "core/UBApplicationController.h" #include "core/UBDisplayManager.h" @@ -106,7 +108,12 @@ BrowserWindow::BrowserWindow(QWidget *parent, QWebEngineProfile *profile, bool f void BrowserWindow::init() { m_statusBar = new QStatusBar(this); - m_statusBar->setWindowFlag(Qt::ToolTip); + + // On Wayland, popup-like tooltips without an input-grabbed parent can trigger a protocol error. + if (UBPlatformUtils::sessionType() != UBPlatformUtils::WAYLAND) + { + m_statusBar->setWindowFlag(Qt::ToolTip); + } m_statusBar->setVisible(true); connect(m_tabWidget, &TabWidget::titleChanged, this, &BrowserWindow::handleWebViewTitleChanged); diff --git a/src/web/simplebrowser/tabwidget.cpp b/src/web/simplebrowser/tabwidget.cpp index 39b70ac93..1c189eac5 100644 --- a/src/web/simplebrowser/tabwidget.cpp +++ b/src/web/simplebrowser/tabwidget.cpp @@ -120,7 +120,11 @@ void TabWidget::handleCurrentChanged(int index) void TabWidget::handleContextMenuRequested(const QPoint &pos) { QMenu menu; +#if (QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)) + menu.addAction(tr("New &Tab"), QKeySequence::AddTab, this, &TabWidget::createTab); +#else menu.addAction(tr("New &Tab"), this, &TabWidget::createTab, QKeySequence::AddTab); +#endif int index = tabBar()->tabAt(pos); if (index != -1) { QAction *action = menu.addAction(tr("Clone Tab")); diff --git a/src/web/simplebrowser/webpage.cpp b/src/web/simplebrowser/webpage.cpp index efd9c42bb..89e92e4b9 100644 --- a/src/web/simplebrowser/webpage.cpp +++ b/src/web/simplebrowser/webpage.cpp @@ -64,7 +64,11 @@ WebPage::WebPage(QWebEngineProfile *profile, QObject *parent) : QWebEnginePage(profile, parent) { connect(this, &QWebEnginePage::authenticationRequired, this, &WebPage::handleAuthenticationRequired); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)) + connect(this, &QWebEnginePage::permissionRequested, this, &WebPage::handlePermissionRequested); +#else connect(this, &QWebEnginePage::featurePermissionRequested, this, &WebPage::handleFeaturePermissionRequested); +#endif connect(this, &QWebEnginePage::proxyAuthenticationRequired, this, &WebPage::handleProxyAuthenticationRequired); connect(this, &QWebEnginePage::registerProtocolHandlerRequested, this, &WebPage::handleRegisterProtocolHandlerRequested); #if !defined(QT_NO_SSL) || QTWEBENGINEWIDGETS_VERSION >= QT_VERSION_CHECK(5, 12, 0) @@ -170,6 +174,30 @@ void WebPage::handleAuthenticationRequired(const QUrl &requestUrl, QAuthenticato } } +#if (QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)) +inline QString questionForFeature(QWebEnginePermission::PermissionType permissionType) +{ + switch (permissionType) { + case QWebEnginePermission::PermissionType::MediaAudioCapture: + return WebPage::tr("Allow %1 to access your microphone?"); + case QWebEnginePermission::PermissionType::MediaVideoCapture: + return WebPage::tr("Allow %1 to access your webcam?"); + case QWebEnginePermission::PermissionType::MediaAudioVideoCapture: + return WebPage::tr("Allow %1 to access your microphone and webcam?"); + case QWebEnginePermission::PermissionType::DesktopVideoCapture: + return WebPage::tr("Allow %1 to capture video of your desktop?"); + case QWebEnginePermission::PermissionType::DesktopAudioVideoCapture: + return WebPage::tr("Allow %1 to capture audio and video of your desktop?"); + case QWebEnginePermission::PermissionType::MouseLock: + return WebPage::tr("Allow %1 to lock your mouse cursor?"); + case QWebEnginePermission::PermissionType::Geolocation: + return WebPage::tr("Allow %1 to access your location information?"); + case QWebEnginePermission::PermissionType::Notifications: + return QString(); + } + return QString(); +} +#else inline QString questionForFeature(QWebEnginePage::Feature feature) { switch (feature) { @@ -192,7 +220,23 @@ inline QString questionForFeature(QWebEnginePage::Feature feature) } return QString(); } +#endif + +#if (QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)) +void WebPage::handlePermissionRequested(QWebEnginePermission permission) +{ + if (permission.state() == QWebEnginePermission::State::Ask) + { + QString title = tr("Permission Request"); + QString question = questionForFeature(permission.permissionType()).arg(permission.origin().host()); + if (!question.isEmpty() && QMessageBox::question(UBApplication::mainWindow->centralWidget(), title, question) == QMessageBox::Yes) + permission.grant(); + else + permission.deny(); + } +} +#else void WebPage::handleFeaturePermissionRequested(const QUrl &securityOrigin, Feature feature) { PermissionPolicy policy = UBApplication::webController->hasFeaturePermission(securityOrigin, feature); @@ -212,6 +256,7 @@ void WebPage::handleFeaturePermissionRequested(const QUrl &securityOrigin, Featu setFeaturePermission(securityOrigin, feature, policy); } +#endif void WebPage::handleProxyAuthenticationRequired(const QUrl &, QAuthenticator *auth, const QString &proxyHost) { diff --git a/src/web/simplebrowser/webpage.h b/src/web/simplebrowser/webpage.h index 83b976b69..2b0c647a5 100644 --- a/src/web/simplebrowser/webpage.h +++ b/src/web/simplebrowser/webpage.h @@ -53,6 +53,9 @@ #include #include +#if (QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)) +#include +#endif #include #include @@ -73,7 +76,11 @@ private slots: private slots: void handleAuthenticationRequired(const QUrl &requestUrl, QAuthenticator *auth); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)) + void handlePermissionRequested(QWebEnginePermission permission); +#else void handleFeaturePermissionRequested(const QUrl &securityOrigin, Feature feature); +#endif void handleProxyAuthenticationRequired(const QUrl &requestUrl, QAuthenticator *auth, const QString &proxyHost); void handleRegisterProtocolHandlerRequested(QWebEngineRegisterProtocolHandlerRequest request); #if !defined(QT_NO_SSL) || QTWEBENGINEWIDGETS_VERSION >= QT_VERSION_CHECK(5, 12, 0)