From 38da4349a5e617742e154fcfd87db3c24a2f27fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferry=20J=C3=A9r=C3=A9mie?= Date: Wed, 16 Dec 2020 22:11:54 +0100 Subject: [PATCH 001/130] change and QT_PATH on arm --- release_scripts/linux/build.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/release_scripts/linux/build.sh b/release_scripts/linux/build.sh index 5158e019..6529c583 100755 --- a/release_scripts/linux/build.sh +++ b/release_scripts/linux/build.sh @@ -40,6 +40,10 @@ initializeVariables() if [ $ARCHITECTURE == "x86_64" ]; then ARCHITECTURE="amd64" fi + if [$ARCHITECTURE == "armv7l" ]; then + $ARCHITECTURE="armhf" + QT_PATH="/usr/lib/arm-linux-gnueabihf/qt5" + fi fi } From 476d8768ee3d4b90d4c4569efbce861fec7d89d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Mon, 7 Jun 2021 15:06:14 +0200 Subject: [PATCH 002/130] don't call documentThumbnailsUpdated every time + set current document to null if it is about to be deleted --- src/document/UBDocumentController.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/document/UBDocumentController.cpp b/src/document/UBDocumentController.cpp index 5f7bef20..60a56854 100644 --- a/src/document/UBDocumentController.cpp +++ b/src/document/UBDocumentController.cpp @@ -2370,8 +2370,8 @@ void UBDocumentController::deleteMultipleItems(QModelIndexList indexes, UBDocume for (int i =0; i < indexes.size(); i++) { deleteIndexAndAssociatedData(indexes.at(i)); - emit documentThumbnailsUpdated(this); } + emit documentThumbnailsUpdated(this); break; } case EmptyFolder: @@ -2762,10 +2762,16 @@ void UBDocumentController::deleteIndexAndAssociatedData(const QModelIndex &pInde } //N/C - NNE - 20140408 - if(pIndex.column() == 0){ + if(pIndex.column() == 0) + { if (docModel->isDocument(pIndex)) { UBDocumentProxy *proxyData = docModel->proxyData(pIndex); + if (selectedDocument() == proxyData) + { + setDocument(nullptr); + } + if (proxyData) { UBPersistenceManager::persistenceManager()->deleteDocument(proxyData); } From 30087bb34dce03f113a0dd61f5fc675628a945c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Tue, 8 Jun 2021 16:12:29 +0200 Subject: [PATCH 003/130] fixed a crash that would randomly occur when performing multiple deletion inside the trash --- src/document/UBDocumentController.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/document/UBDocumentController.cpp b/src/document/UBDocumentController.cpp index 60a56854..e6520b9c 100644 --- a/src/document/UBDocumentController.cpp +++ b/src/document/UBDocumentController.cpp @@ -2762,10 +2762,11 @@ void UBDocumentController::deleteIndexAndAssociatedData(const QModelIndex &pInde } //N/C - NNE - 20140408 + UBDocumentProxy *proxyData = nullptr; if(pIndex.column() == 0) { if (docModel->isDocument(pIndex)) { - UBDocumentProxy *proxyData = docModel->proxyData(pIndex); + proxyData = docModel->proxyData(pIndex); if (selectedDocument() == proxyData) { @@ -2778,7 +2779,15 @@ void UBDocumentController::deleteIndexAndAssociatedData(const QModelIndex &pInde } } - docModel->removeRow(pIndex.row(), pIndex.parent()); + if (proxyData) + { + // need to recall indexForProxy as rows could have changed when performing a multiple deletion + QModelIndex indexForProxy = docModel->indexForProxy(proxyData); + if (!docModel->removeRow(indexForProxy.row(), indexForProxy.parent())) + { + qDebug() << "could not remove row (r:" << indexForProxy.row() << "p:" << indexForProxy.parent() << ")"; + } + } } From c9826bbe6bb5b62c74547b32e94e51df744c1ae2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Tue, 8 Jun 2021 17:45:46 +0200 Subject: [PATCH 004/130] folders still need to be removed --- src/document/UBDocumentController.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/document/UBDocumentController.cpp b/src/document/UBDocumentController.cpp index e6520b9c..c85410ce 100644 --- a/src/document/UBDocumentController.cpp +++ b/src/document/UBDocumentController.cpp @@ -2788,6 +2788,16 @@ void UBDocumentController::deleteIndexAndAssociatedData(const QModelIndex &pInde qDebug() << "could not remove row (r:" << indexForProxy.row() << "p:" << indexForProxy.parent() << ")"; } } + else + { + if (docModel->isCatalog(pIndex)) + { + if (!docModel->removeRow(pIndex.row(), pIndex.parent())) + { + qDebug() << "could not remove row (r:" << pIndex.row() << "p:" << pIndex.parent() << ")"; + } + } + } } From 43f2aba2cc02d96d0ec065294625beee84d4e771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Tue, 8 Jun 2021 17:46:17 +0200 Subject: [PATCH 005/130] persist metadata when docs are renamed by the replace dialog --- src/core/UBPersistenceManager.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/core/UBPersistenceManager.cpp b/src/core/UBPersistenceManager.cpp index 90a160c4..8d1759f6 100644 --- a/src/core/UBPersistenceManager.cpp +++ b/src/core/UBPersistenceManager.cpp @@ -256,7 +256,12 @@ QDialog::DialogCode UBPersistenceManager::processInteractiveReplacementDialog(UB mDocumentTreeStructureModel->removeRow(i, parentIndex); } } - pProxy->setMetaData(UBSettings::documentName, resultName); + + if (docName != resultName) + { + pProxy->setMetaData(UBSettings::documentName, resultName); + UBMetadataDcSubsetAdaptor::persist(pProxy); + } mDocumentTreeStructureModel->addDocument(pProxy, parentIndex); } replaceDialog->setParent(0); From 19e6331f5d21ef594a3a5eebb4189e0f1d125af7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Wed, 9 Jun 2021 14:16:57 +0200 Subject: [PATCH 006/130] Revert "Due to perfomance issues at openboard start, in a network-attached storage context, I had to find some (dirty) optimizations in order to counter-balance the huge response time of the directory scans performed when creating the documents tree (very huge in slowest machines (HDD, low CPU, with a thousand documents)). The simplest solution (I found) was to let the opening of metadatas fail, and to prevent any scanning. This implied to introduce the page-count as a metadata. As this issue is not encountered with a standard use of OpenBoard (with local documents), no update operation (of every document) should be necessary out of the described context" This reverts commit 9adb8e6643793c85926ac2a22a17ebcc8d01c9b0. --- src/adaptors/UBMetadataDcSubsetAdaptor.cpp | 7 --- src/core/UBPersistenceManager.cpp | 54 +++++++++------------- src/core/UBSettings.cpp | 1 - src/core/UBSettings.h | 1 - src/document/UBDocumentProxy.cpp | 12 +---- src/document/UBDocumentProxy.h | 1 - 6 files changed, 24 insertions(+), 52 deletions(-) diff --git a/src/adaptors/UBMetadataDcSubsetAdaptor.cpp b/src/adaptors/UBMetadataDcSubsetAdaptor.cpp index 9cbe55eb..54ce6d51 100644 --- a/src/adaptors/UBMetadataDcSubsetAdaptor.cpp +++ b/src/adaptors/UBMetadataDcSubsetAdaptor.cpp @@ -125,8 +125,6 @@ void UBMetadataDcSubsetAdaptor::persist(UBDocumentProxy* proxy) // introduced in UB 4.4 xmlWriter.writeTextElement(UBSettings::uniboardDocumentNamespaceUri, "updated-at", UBStringUtils::toUtcIsoDateTime(QDateTime::currentDateTimeUtc())); - xmlWriter.writeTextElement(UBSettings::uniboardDocumentNamespaceUri, "page-count", QString::number(proxy->pageCount())); - xmlWriter.writeEndElement(); //dc:Description xmlWriter.writeEndElement(); //RDF @@ -226,11 +224,6 @@ QMap UBMetadataDcSubsetAdaptor::load(QString pPath) metadata.insert(UBSettings::documentUpdatedAt, xml.readElementText()); updatedAtFound = true; } - else if (xml.name() == "page-count" - && xml.namespaceUri() == UBSettings::uniboardDocumentNamespaceUri) - { - metadata.insert(UBSettings::documentPageCount, xml.readElementText()); - } metadata.insert(UBSettings::documentVersion, docVersion); } diff --git a/src/core/UBPersistenceManager.cpp b/src/core/UBPersistenceManager.cpp index 8d1759f6..d9703d40 100644 --- a/src/core/UBPersistenceManager.cpp +++ b/src/core/UBPersistenceManager.cpp @@ -164,44 +164,36 @@ void UBPersistenceManager::createDocumentProxiesStructure(const QFileInfoList &c { QString fullPath = path.absoluteFilePath(); - QMap metadatas = UBMetadataDcSubsetAdaptor::load(fullPath); + QDir dir(fullPath); - QString docGroupName = metadatas.value(UBSettings::documentGroupName, QString()).toString(); - QString docName = metadatas.value(UBSettings::documentName, QString()).toString(); + if (dir.entryList(QDir::Files | QDir::NoDotAndDotDot).size() > 0) + { + QMap metadatas = UBMetadataDcSubsetAdaptor::load(fullPath); + QString docGroupName = metadatas.value(UBSettings::documentGroupName, QString()).toString(); + QString docName = metadatas.value(UBSettings::documentName, QString()).toString(); - if (docName.isEmpty()) { - qDebug() << "Group name and document name are empty in UBPersistenceManager::createDocumentProxiesStructure()"; - continue; - } + if (docName.isEmpty()) { + qDebug() << "Group name and document name are empty in UBPersistenceManager::createDocumentProxiesStructure()"; + continue; + } - QModelIndex parentIndex = mDocumentTreeStructureModel->goTo(docGroupName); - if (!parentIndex.isValid()) { - return; - } + QModelIndex parentIndex = mDocumentTreeStructureModel->goTo(docGroupName); + if (!parentIndex.isValid()) { + return; + } - UBDocumentProxy* docProxy = new UBDocumentProxy(fullPath, metadatas); // managed in UBDocumentTreeNode - foreach(QString key, metadatas.keys()) { - docProxy->setMetaData(key, metadatas.value(key)); - } + UBDocumentProxy* docProxy = new UBDocumentProxy(fullPath); // managed in UBDocumentTreeNode + foreach(QString key, metadatas.keys()) { + docProxy->setMetaData(key, metadatas.value(key)); + } - if (metadatas.contains(UBSettings::documentPageCount)) - { - int pageCount = metadatas.value(UBSettings::documentPageCount).toInt(); - if (pageCount == 0) - pageCount = sceneCount(docProxy); + docProxy->setPageCount(sceneCount(docProxy)); - docProxy->setPageCount(pageCount); - } - else - { - int pageCount = sceneCount(docProxy); - docProxy->setPageCount(pageCount); + if (!interactive) + mDocumentTreeStructureModel->addDocument(docProxy, parentIndex); + else + processInteractiveReplacementDialog(docProxy); } - - if (!interactive) - mDocumentTreeStructureModel->addDocument(docProxy, parentIndex); - else - processInteractiveReplacementDialog(docProxy); } } diff --git a/src/core/UBSettings.cpp b/src/core/UBSettings.cpp index 23bbb069..2e3c5839 100644 --- a/src/core/UBSettings.cpp +++ b/src/core/UBSettings.cpp @@ -61,7 +61,6 @@ QString UBSettings::documentSize = QString("Size"); QString UBSettings::documentIdentifer = QString("ID"); QString UBSettings::documentVersion = QString("Version"); QString UBSettings::documentUpdatedAt = QString("UpdatedAt"); -QString UBSettings::documentPageCount = QString("PageCount"); QString UBSettings::documentDate = QString("date"); QString UBSettings::trashedDocumentGroupNamePrefix = QString("_Trash:"); diff --git a/src/core/UBSettings.h b/src/core/UBSettings.h index 653eb6dc..9cba7a10 100644 --- a/src/core/UBSettings.h +++ b/src/core/UBSettings.h @@ -202,7 +202,6 @@ class UBSettings : public QObject static QString documentIdentifer; static QString documentVersion; static QString documentUpdatedAt; - static QString documentPageCount; static QString documentDate; diff --git a/src/document/UBDocumentProxy.cpp b/src/document/UBDocumentProxy.cpp index 0bf6e565..6f9aa711 100644 --- a/src/document/UBDocumentProxy.cpp +++ b/src/document/UBDocumentProxy.cpp @@ -56,6 +56,7 @@ UBDocumentProxy::UBDocumentProxy(const UBDocumentProxy &rValue) : mPageCount = rValue.mPageCount; } + UBDocumentProxy::UBDocumentProxy(const QString& pPersistancePath) : mPageCount(0) , mPageDpi(0) @@ -67,17 +68,6 @@ UBDocumentProxy::UBDocumentProxy(const QString& pPersistancePath) } -UBDocumentProxy::UBDocumentProxy(const QString& pPersistancePath, QMap metadatas) - : mPageCount(0) - , mPageDpi(0) -{ - init(); - setPersistencePath(pPersistancePath); - - mMetaDatas = metadatas; -} - - void UBDocumentProxy::init() { setMetaData(UBSettings::documentGroupName, ""); diff --git a/src/document/UBDocumentProxy.h b/src/document/UBDocumentProxy.h index dfff6908..cb507dec 100644 --- a/src/document/UBDocumentProxy.h +++ b/src/document/UBDocumentProxy.h @@ -49,7 +49,6 @@ class UBDocumentProxy : public QObject UBDocumentProxy(); UBDocumentProxy(const UBDocumentProxy &rValue); UBDocumentProxy(const QString& pPersistencePath); - UBDocumentProxy(const QString& pPersistencePath, QMap metadatas); virtual ~UBDocumentProxy(); From 1cb97a8f85d068a7f797dd3575997bae6d2f3af5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Thu, 10 Jun 2021 11:46:19 +0200 Subject: [PATCH 007/130] call adjust displayView when centering on last center --- src/board/UBBoardController.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/board/UBBoardController.cpp b/src/board/UBBoardController.cpp index 0ee35ae5..a836e3bc 100644 --- a/src/board/UBBoardController.cpp +++ b/src/board/UBBoardController.cpp @@ -984,7 +984,7 @@ void UBBoardController::previousScene() persistViewPositionOnCurrentScene(); persistCurrentScene(); setActiveDocumentScene(mActiveSceneIndex - 1); - mControlView->centerOn(mActiveScene->lastCenter()); + centerOn(mActiveScene->lastCenter()); QApplication::restoreOverrideCursor(); } @@ -1000,7 +1000,7 @@ void UBBoardController::nextScene() persistViewPositionOnCurrentScene(); persistCurrentScene(); setActiveDocumentScene(mActiveSceneIndex + 1); - mControlView->centerOn(mActiveScene->lastCenter()); + centerOn(mActiveScene->lastCenter()); QApplication::restoreOverrideCursor(); } @@ -1016,7 +1016,7 @@ void UBBoardController::firstScene() persistViewPositionOnCurrentScene(); persistCurrentScene(); setActiveDocumentScene(0); - mControlView->centerOn(mActiveScene->lastCenter()); + centerOn(mActiveScene->lastCenter()); QApplication::restoreOverrideCursor(); } @@ -1032,7 +1032,7 @@ void UBBoardController::lastScene() persistViewPositionOnCurrentScene(); persistCurrentScene(); setActiveDocumentScene(selectedDocument()->pageCount() - 1); - mControlView->centerOn(mActiveScene->lastCenter()); + centerOn(mActiveScene->lastCenter()); QApplication::restoreOverrideCursor(); } From b3692061a57ae7d434b87cb190f0b18a1e0d32c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Thu, 10 Jun 2021 16:53:04 +0200 Subject: [PATCH 008/130] fixed an issue where erase a stroke then clear the scene before changing page would cause a crash --- src/board/UBBoardController.cpp | 22 ++++++++++++++++------ src/board/UBBoardController.h | 2 +- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/board/UBBoardController.cpp b/src/board/UBBoardController.cpp index a836e3bc..62dcd073 100644 --- a/src/board/UBBoardController.cpp +++ b/src/board/UBBoardController.cpp @@ -1632,11 +1632,11 @@ void UBBoardController::moveSceneToIndex(int source, int target) } } -void UBBoardController::findUniquesItems(const QUndoCommand *parent, QSet &itms) +void UBBoardController::findUniquesItems(const QUndoCommand *parent, QSet &items) { if (parent->childCount()) { for (int i = 0; i < parent->childCount(); i++) { - findUniquesItems(parent->child(i), itms); + findUniquesItems(parent->child(i), items); } } @@ -1657,16 +1657,26 @@ void UBBoardController::findUniquesItems(const QUndoCommand *parent, QSetparentItem() && UBGraphicsGroupContainerItem::Type == item->parentItem()->type())) - itms.insert(item); + if (!items.contains(item) && + !(item->parentItem() && UBGraphicsGroupContainerItem::Type == item->parentItem()->type()) && + !items.contains(item->parentItem()) + ) + { + items.insert(item); + } } QSetIterator itRemoved(cmd->GetRemovedList()); while (itRemoved.hasNext()) { QGraphicsItem* item = itRemoved.next(); - if( !itms.contains(item) && !(item->parentItem() && UBGraphicsGroupContainerItem::Type == item->parentItem()->type())) - itms.insert(item); + if (!items.contains(item) && + !(item->parentItem() && UBGraphicsGroupContainerItem::Type == item->parentItem()->type()) && + !items.contains(item->parentItem()) + ) + { + items.insert(item); + } } } diff --git a/src/board/UBBoardController.h b/src/board/UBBoardController.h index 360e2e71..2f95ed88 100644 --- a/src/board/UBBoardController.h +++ b/src/board/UBBoardController.h @@ -177,7 +177,7 @@ class UBBoardController : public UBDocumentContainer void notifyPageChanged(); void displayMetaData(QMap metadatas); - void findUniquesItems(const QUndoCommand *parent, QSet &itms); + void findUniquesItems(const QUndoCommand *parent, QSet &items); void ClearUndoStack(); void setActiveDocumentScene(UBDocumentProxy* pDocumentProxy, int pSceneIndex = 0, bool forceReload = false, bool onImport = false); From ad80fb152c28e2b35342b0b840b5dcb2ea32e7a6 Mon Sep 17 00:00:00 2001 From: SiderealArt Date: Sat, 12 Jun 2021 21:47:36 +0800 Subject: [PATCH 009/130] Update OpenBoard_zh_TW.ts --- resources/i18n/OpenBoard_zh_TW.ts | 282 +++++++++++++++--------------- 1 file changed, 141 insertions(+), 141 deletions(-) diff --git a/resources/i18n/OpenBoard_zh_TW.ts b/resources/i18n/OpenBoard_zh_TW.ts index 859aece4..0aa9bf18 100644 --- a/resources/i18n/OpenBoard_zh_TW.ts +++ b/resources/i18n/OpenBoard_zh_TW.ts @@ -50,7 +50,7 @@ IntranetPodcastPublishingDialog Publish Podcast to YouTube - 發佈Podcast至YouTube + 發佈 Podcast 至 YouTube Title @@ -333,7 +333,7 @@ Pen - 電子筆 + Annotate Document @@ -365,7 +365,7 @@ Selector - 選擇器 + 選擇工具 Select And Modify Objects @@ -413,7 +413,7 @@ Import eduMedia simulation - 匯入eduMedia模擬程序 + 匯入 eduMedia 模擬程序 Check Update @@ -673,19 +673,19 @@ Podcast Config - Podcast設定 + Podcast 設定 Configure Podcast Recording - Podcast錄製設定 + Podcast 錄製設定 Flash Trap - Flash動畫擷取 + Flash 動畫擷取 Trap Flash Content - 擷取Flash動畫內容 + 擷取 Flash 動畫內容 Web Trap @@ -745,11 +745,11 @@ Wide Size (16/9) - 寬螢幕顯示(16/9) + 寬螢幕顯示 (16/9) Regular Size (4/3) - 一般螢幕顯示(4/3) + 一般螢幕顯示 (4/3) Custom Size @@ -765,15 +765,15 @@ Quit OpenBoard - 退出OpenBoard + 退出 OpenBoard Hide OpenBoard - 隱藏OpenBoard + 隱藏 OpenBoard Hide OpenBoard Application - 隱藏OpenBoard應用程式 + 隱藏 OpenBoard 應用程式 Play @@ -801,39 +801,39 @@ Open Tutorial - + 開啟新手教學 Open the tutorial web page - + 打開教學網頁 Reset grid size - + 重設網格大小 Small Eraser - + 小橡皮擦 Color 1 - + 顏色 1 Color 2 - + 顏色 2 Color 3 - + 顏色 3 Color 4 - + 顏色 4 Color 5 - + 顏色 5 Draw intermediate grid lines @@ -848,30 +848,30 @@ Username: - 帳號: + 帳號: Password: - 密碼: + 密碼: ProxyDialog Proxy Authentication - Proxy驗證 + Proxy 驗證 Connect to Proxy - 連接至Proxy + 連接至 Proxy Username: - 帳號: + 帳號: Password: - 密碼: + 密碼: Save username and password for future use @@ -894,7 +894,7 @@ Are you sure you want to remove 1 page from the selected document '%0'? - 確定要移除所選文件 '%0' 的一個頁面 ? + 確定要移除所選文件 '%0' 的一個頁面? @@ -916,7 +916,7 @@ New update available, would you go to the web page ? - 可更新,要上網頁嗎? + 有可用更新,要前往網頁下載嗎? No update available @@ -924,7 +924,7 @@ Update available - 目前可更新 + 有可用更新 Update @@ -935,7 +935,7 @@ UBBackgroundPalette Grid size - + 網格大小 Draw intermediate grid lines @@ -954,7 +954,7 @@ Unknown tool type %1 - 陌生工具型態 %1 + 未知工具類型 %1 Add Item @@ -962,11 +962,11 @@ All Supported (%1) - 全部已支援(%1) + 全部已支援 (%1) Unknown content type %1 - 陌生內容型態 %1 + 未知內容類型 %1 Delete page %1 from document @@ -978,7 +978,7 @@ Add file operation failed: file copying error - 新增檔案失敗:檔案複製有錯誤 + 新增檔案失敗:檔案複製錯誤 Group @@ -990,26 +990,26 @@ Saving document... - + 正在儲存檔案... Document has just been saved... - + 檔案已被儲存... Deleting page %1 - + 正在刪除第 %1 頁 Color - 顏色 + 顏色 UBBoardPaletteManager Error Adding Image to Library - 錯誤新增圖像至圖書館 + 新增圖像至圖書館時發生錯誤 CapturedImage @@ -1020,26 +1020,26 @@ UBBoardThumbnailsView Loading page (%1/%2) - + 正在載入第 %1 頁,共 %2 頁 UBCachePropertiesWidget Cache Properties - Cache屬性 + Cache 屬性 Color: - 顏色: + 顏色: Shape: - 形狀: + 形狀: Size: - 大小: + 大小: Close @@ -1070,7 +1070,7 @@ Show OpenBoard - + 顯示 OpenBoard @@ -1085,7 +1085,7 @@ Add Folder of Images - 新增圖像檔案夾 + 新增圖像資料夾 Add Images @@ -1210,19 +1210,19 @@ Remove Item - + 刪除項目 Are you sure you want to remove the selected item(s) ? - + 您確定要刪除所選項目嗎? The document '%1' has been generated with a newer version of OpenBoard (%2). By opening it, you may lose some information. Do you want to proceed? - + 檔案 '%1' 是由新版的 OpenBoard (%2) 建立的。開啟後可能會遺失資料。您確定要繼續嗎? Title page - + 標題頁 @@ -1286,15 +1286,15 @@ UBDocumentReplaceDialog Accept - 接受 + 接受 Cancel - 取消 + 取消 Replace - + 取代 The name %1 is allready used. @@ -1307,38 +1307,38 @@ Providing a new name will create a new document. UBDocumentTreeModel Trash - 回收桶 + 回收桶 %1 pages copied - + 已複製 %1 個頁面 My documents - + 我的檔案 UBDocumentTreeView %1 pages copied - + 已複製 %1 個頁面 Remove Item - + 刪除項目 Are you sure you want to remove the selected item(s) ? - + 您確定要刪除所選項目嗎? Copying page %1/%2 - 頁面 %1/%2 複製中 + 頁面 %1/%2 複製中 @@ -1391,38 +1391,38 @@ Providing a new name will create a new document. Exporting document... - + 匯出檔案中... Export failed - + 匯出失敗 Unable to export to the selected location. You do not have the permissions necessary to save the file. - + 無法匯出至指定位置。您沒有儲存檔案的權限。 Export failed: location not writable - + 匯出失敗:路徑無法被寫入 Export successful. - + 匯出成功。 UBExportCFF Export to IWB - 匯出至IWB + 匯出至 IWB Export as IWB File - 以IWB格式匯出 + 以 IWB 格式匯出 Exporting document... - 匯出文件... + 匯出檔案中... Export successful. @@ -1457,41 +1457,41 @@ Providing a new name will create a new document. Export to OpenBoard Format - + 以 OpenBoard 格式匯出 UBExportDocumentSetAdaptor Export failed. - 匯出失敗。 + 匯出失敗。 Failed to export... - + 匯出失敗... Export as UBX File - + 以 UBX 檔案匯出 Exporting document... - + 匯出檔案中... Export successful. - + 匯出成功。 Export to OpenBoard UBX Format - + 以 OpenBoard UBX 格式匯出 UBExportFullPDF Export as PDF File - 以PDF格式匯出 + 以 PDF 格式匯出 Exporting document... @@ -1499,7 +1499,7 @@ Providing a new name will create a new document. Export to PDF - 匯出成PDF + 匯出成 PDF Export successful. @@ -1510,7 +1510,7 @@ Providing a new name will create a new document. UBExportPDF Export as PDF File - 以PDF格式匯出 + 以 PDF 格式匯出 Exporting page %1 of %2 @@ -1526,7 +1526,7 @@ Providing a new name will create a new document. Export to PDF - 匯出成PDF + 匯出成 PDF @@ -1553,7 +1553,7 @@ Providing a new name will create a new document. Export to Web Browser - 匯出成網頁瀏覽 + 匯出至瀏覽器 @@ -1669,7 +1669,7 @@ Providing a new name will create a new document. Enter a new folder name - 鍵入檔案名稱 + 輸入檔案名稱 @@ -1706,7 +1706,7 @@ Providing a new name will create a new document. Set as background - 設定成背景 + 設定成背景 @@ -1717,7 +1717,7 @@ Providing a new name will create a new document. Unsupported media format - + 不支援的媒體格式 Media playback service not found @@ -1725,14 +1725,14 @@ Providing a new name will create a new document. Media error: - + 媒體錯誤: UBGraphicsTextItem <Type Text Here> - <此處鍵入文字> + <此處輸入文字> @@ -1810,7 +1810,7 @@ Providing a new name will create a new document. OpenBoard (*.ubz) - + OpenBoard (*.ubz) @@ -1824,18 +1824,18 @@ Providing a new name will create a new document. UBImportImage Image Format ( - 圖像格式( + 圖像格式 ( UBImportPDF Portable Document Format (*.pdf) - Portable Document Format (*.pdf) + 可攜式檔案格式 (*.pdf) PDF import failed. - PDF匯入失敗。 + PDF 匯入失敗。 Importing page %1 of %2 @@ -1864,7 +1864,7 @@ Providing a new name will create a new document. UBKeyboardPalette Enter - 鍵入 + 輸入 @@ -1897,7 +1897,7 @@ Providing a new name will create a new document. Failed to log to Proxy - 登入Proxy失敗 + 登入 Proxy 失敗 Yes @@ -1922,7 +1922,7 @@ Do you want to ignore these errors for this host? UBOpenSankoreImporterWidget Cancel - 取消 + 取消 Open-Sankore Documents Detected @@ -1942,7 +1942,7 @@ Do you want to ignore these errors for this host? Proceed - + 繼續 @@ -1980,22 +1980,22 @@ Do you want to ignore these errors for this host? Swiss French - 法文(瑞士) + 法文 (瑞士) UBPodcastController Failed to start encoder ... - 編碼器(encoder)啟動失敗... + 編碼器啟動失敗... No Podcast encoder available ... - 沒有可用的Podcast編碼器(encoder)... + 沒有可用的Podcast編碼器... Part %1 - Part %1 + 第 %1 部分 on your desktop ... @@ -2011,7 +2011,7 @@ Do you want to ignore these errors for this host? Podcast recording error (%1) - Podcast錄製有錯誤 (%1) + Podcast 錄製有錯誤 (%1) Default Audio Input @@ -2039,7 +2039,7 @@ Do you want to ignore these errors for this host? Publish to Youtube - 發佈至YouTube + 發佈至 YouTube OpenBoard Cast @@ -2050,7 +2050,7 @@ Do you want to ignore these errors for this host? UBPreferencesController version: - 版本: + 版本: Marker is pressure sensitive @@ -2061,15 +2061,15 @@ Do you want to ignore these errors for this host? UBProxyLoginDlg Proxy Login - Proxy登入 + Proxy 登入 Username: - 帳號: + 帳號: Password: - 密碼: + 密碼: @@ -2080,11 +2080,11 @@ Do you want to ignore these errors for this host? Title: - 標題: + 標題: Description: - 描述: + 描述: Publish @@ -2120,7 +2120,7 @@ Do you want to ignore these errors for this host? UBThumbnailTextItem Page %0 - 第 %0 頁 + 第 %0 頁 @@ -2151,7 +2151,7 @@ Do you want to ignore these errors for this host? Cache - Cache + 快取 Axes @@ -2199,7 +2199,7 @@ Do you want to ignore these errors for this host? Files update successful! Please reboot the application to access the updated documents. 成功更新檔案! -請重新啟動程式使用該檔案。 +請重新啟動程式以使用該檔案。 An error occured during the update. The files have not been affected. @@ -2215,11 +2215,11 @@ Please reboot the application to access the updated documents. Please wait the import process will start soon... - 即將開始匯入,請稍待... + 即將開始匯入,請稍候... Remind me later - 稍候題提醒我 + 稍候提醒我 @@ -2240,15 +2240,15 @@ Please reboot the application to access the updated documents. UBYouTubePublisher YouTube authentication failed. - YouTube驗證失敗。 + YouTube 驗證失敗。 Error while uploading video to YouTube (%1) - 影片上傳至YouTube (%1)過程中有錯誤 + 影片上傳至 YouTube (%1)過程中有錯誤 Upload to YouTube in progress %1 % - 上傳至YouTube中 %1 % + 上傳至 YouTube 中 %1 % @@ -2385,7 +2385,7 @@ Please reboot the application to access the updated documents. ? unknown file size - ? + ? @@ -2520,14 +2520,14 @@ Please reboot the application to access the updated documents. XPDFRenderer Processing... - + 處理中... YouTubePublishingDialog Publish Podcast to YouTube - 發佈Podcast至YouTube + 發佈 Podcast 至 YouTube Title @@ -2547,11 +2547,11 @@ Please reboot the application to access the updated documents. YouTube Username - YouTube帳號 + YouTube 帳號 YouTube Password - YouTube密碼 + YouTube 密碼 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> @@ -2567,7 +2567,7 @@ p, li { white-space: pre-wrap; } OpenBoard - OpenBoard + OpenBoard Restore credentials on reboot @@ -2614,7 +2614,7 @@ p, li { white-space: pre-wrap; } px - + 像素 @@ -2671,19 +2671,19 @@ p, li { white-space: pre-wrap; } Creation date - + 建立日期 Update date - + 更新日期 Alphabetical order - + 按字母排序 Sort Order - + 排序方式 @@ -2750,7 +2750,7 @@ p, li { white-space: pre-wrap; } Check software update at launch - 啟用時檢查更新 + 啟動時檢查更新 Internet @@ -2758,7 +2758,7 @@ p, li { white-space: pre-wrap; } Home Page: - 主頁: + 主頁: Toolbar @@ -2774,7 +2774,7 @@ p, li { white-space: pre-wrap; } version : … - 版本: … + 版本: … Licences @@ -2802,7 +2802,7 @@ p, li { white-space: pre-wrap; } Mode to start in: - 啟動模式: + 啟動模式: Board @@ -2814,11 +2814,11 @@ p, li { white-space: pre-wrap; } Proxy User: - Proxy User: + Proxy 使用者: Pass: - 密碼: + 密碼: Credits @@ -2826,15 +2826,15 @@ p, li { white-space: pre-wrap; } On Dark Background - 深色背景 + 深色背景 Opacity - 透明度 + 透明度 On Light Background - 淡色背景 + 淡色背景 Swap first and second view displays @@ -2842,15 +2842,15 @@ p, li { white-space: pre-wrap; } Built-in virtual keyboard button size: - + 內建虛擬鍵盤按鍵大小: Use system keyboard (recommended) - + 使用系統鍵盤 (推薦) Grid - + 網格 Open-Sankoré Importer @@ -2874,7 +2874,7 @@ p, li { white-space: pre-wrap; } days - + PDF Rendering @@ -2889,11 +2889,11 @@ p, li { white-space: pre-wrap; } trapFlashDialog Trap flash - 擷取flash + 擷取 flash Select a flash to trap - 選擇要擷取的flash動畫 + 選擇要擷取的 flash 動畫 about:blank From 8ba85f412b6064ff66e8d71b2317de29ab53ed9e Mon Sep 17 00:00:00 2001 From: SiderealArt Date: Sat, 12 Jun 2021 21:48:32 +0800 Subject: [PATCH 010/130] Update OpenBoard_zh_TW.ts --- resources/i18n/OpenBoard_zh_TW.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/i18n/OpenBoard_zh_TW.ts b/resources/i18n/OpenBoard_zh_TW.ts index 0aa9bf18..8ce86b8c 100644 --- a/resources/i18n/OpenBoard_zh_TW.ts +++ b/resources/i18n/OpenBoard_zh_TW.ts @@ -1020,7 +1020,7 @@ UBBoardThumbnailsView Loading page (%1/%2) - 正在載入第 %1 頁,共 %2 頁 + 正在載入第 %1 頁,共 %2 頁 From cd4a66f90ca0b4f0ff4a71a40363f263db1db0b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Mon, 21 Jun 2021 15:44:04 +0200 Subject: [PATCH 011/130] fixed an issue where font style would not be persisted correctly --- src/core/UBSettings.cpp | 12 ++++++++++++ src/core/UBSettings.h | 3 +++ src/domain/UBGraphicsTextItemDelegate.cpp | 16 +++++++++++----- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/core/UBSettings.cpp b/src/core/UBSettings.cpp index 2e3c5839..e9620107 100644 --- a/src/core/UBSettings.cpp +++ b/src/core/UBSettings.cpp @@ -72,6 +72,7 @@ QString UBSettings::undoCommandTransactionName = "UndoTransaction"; const int UBSettings::sDefaultFontPixelSize = 36; const char *UBSettings::sDefaultFontFamily = "Arial"; +const char *UBSettings::sDefaultFontStyleName = "Regular"; QString UBSettings::currentFileVersion = "4.8.0"; @@ -870,6 +871,17 @@ void UBSettings::setFontFamily(const QString &family) } +QString UBSettings::fontStyleName() +{ + return value("Board/FontStyleName", sDefaultFontStyleName).toString(); +} + + +void UBSettings::setFontStyleName(const QString &styleName) +{ + setValue("Board/FontStyleName", styleName); +} + int UBSettings::fontPixelSize() { return value("Board/FontPixelSize", sDefaultFontPixelSize).toInt(); diff --git a/src/core/UBSettings.h b/src/core/UBSettings.h index 9cba7a10..31575e43 100644 --- a/src/core/UBSettings.h +++ b/src/core/UBSettings.h @@ -97,6 +97,8 @@ class UBSettings : public QObject // Text related QString fontFamily(); void setFontFamily(const QString &family); + QString fontStyleName(); + void setFontStyleName(const QString &family); int fontPixelSize(); void setFontPixelSize(int pixelSize); int fontPointSize(); @@ -465,6 +467,7 @@ class UBSettings : public QObject static const int sDefaultFontPixelSize; static const char *sDefaultFontFamily; + static const char *sDefaultFontStyleName; static QSettings* getAppSettings(); diff --git a/src/domain/UBGraphicsTextItemDelegate.cpp b/src/domain/UBGraphicsTextItemDelegate.cpp index a385b3ac..08f705b0 100644 --- a/src/domain/UBGraphicsTextItemDelegate.cpp +++ b/src/domain/UBGraphicsTextItemDelegate.cpp @@ -148,21 +148,25 @@ UBGraphicsTextItemDelegate::~UBGraphicsTextItemDelegate() QFont UBGraphicsTextItemDelegate::createDefaultFont() { - QTextCharFormat textFormat; + QFont font; QString fFamily = UBSettings::settings()->fontFamily(); if (!fFamily.isEmpty()) - textFormat.setFontFamily(fFamily); + font.setFamily(fFamily); + + QString fStyleName = UBSettings::settings()->fontStyleName(); + if (!fStyleName .isEmpty()) + font.setStyleName(fStyleName); bool bold = UBSettings::settings()->isBoldFont(); if (bold) - textFormat.setFontWeight(QFont::Bold); + font.setWeight(QFont::Bold); bool italic = UBSettings::settings()->isItalicFont(); if (italic) - textFormat.setFontItalic(true); + font.setItalic(true); + - QFont font(fFamily, -1, bold ? QFont::Bold : -1, italic); int pointSize = UBSettings::settings()->fontPointSize(); if (pointSize > 0) { font.setPointSize(pointSize); @@ -336,6 +340,7 @@ void UBGraphicsTextItemDelegate::pickFont() QFontDialog fontDialog(static_cast(UBApplication::boardController->controlView())); fontDialog.setOption(QFontDialog::DontUseNativeDialog); + fontDialog.setCurrentFont(delegated()->textCursor().charFormat().font()); customize(fontDialog); @@ -343,6 +348,7 @@ void UBGraphicsTextItemDelegate::pickFont() { QFont selectedFont = fontDialog.selectedFont(); UBSettings::settings()->setFontFamily(selectedFont.family()); + UBSettings::settings()->setFontStyleName(selectedFont.styleName()); UBSettings::settings()->setBoldFont(selectedFont.bold()); UBSettings::settings()->setItalicFont(selectedFont.italic()); UBSettings::settings()->setFontPointSize(selectedFont.pointSize()); From 05b6bf87b3726fb876b899c96765811a7d0f59a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Wed, 23 Jun 2021 10:49:17 +0200 Subject: [PATCH 012/130] forbid deletion if all pages are selected --- src/document/UBDocumentContainer.h | 2 +- src/document/UBDocumentController.cpp | 25 ++++++++++++++++++++++--- src/document/UBDocumentController.h | 1 + 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/document/UBDocumentContainer.h b/src/document/UBDocumentContainer.h index 497861b0..7be279e0 100644 --- a/src/document/UBDocumentContainer.h +++ b/src/document/UBDocumentContainer.h @@ -45,7 +45,7 @@ class UBDocumentContainer : public QObject void pureSetDocument(UBDocumentProxy *document) {mCurrentDocument = document;} UBDocumentProxy* selectedDocument(){return mCurrentDocument;} - int pageCount(){return mCurrentDocument->pageCount();} + int pageCount() const{return mCurrentDocument->pageCount();} const QPixmap* pageAt(int index) { if (index < mDocumentThumbs.size()) diff --git a/src/document/UBDocumentController.cpp b/src/document/UBDocumentController.cpp index c85410ce..19fc3e77 100644 --- a/src/document/UBDocumentController.cpp +++ b/src/document/UBDocumentController.cpp @@ -3360,6 +3360,7 @@ void UBDocumentController::updateActions() updateExportSubActions(selectedIndex); bool firstSceneSelected = false; + bool everyPageSelected = false; if (docSelected) { mMainWindow->actionDuplicate->setEnabled(!trashSelected); @@ -3539,7 +3540,12 @@ UBDocumentController::deletionTypeForSelection(LastSelectedElementType pTypeSele , UBDocumentTreeModel *docModel) const { - if (pTypeSelection == Page) { + if (pTypeSelection == Page) + { + if (everySceneSelected()) + { + return NoDeletion; + } if (!firstAndOnlySceneSelected()) { return DeletePage; } @@ -3566,11 +3572,24 @@ UBDocumentController::deletionTypeForSelection(LastSelectedElementType pTypeSele return NoDeletion; } +bool UBDocumentController::everySceneSelected() const +{ + QList selection = mDocumentUI->thumbnailWidget->selectedItems(); + if (selection.count() > 0) + { + UBSceneThumbnailPixmap* p = dynamic_cast(selection.at(0)); + if (p) + { + return (selection.count() == p->proxy()->pageCount()); + } + } + return false; +} + bool UBDocumentController::firstAndOnlySceneSelected() const { - bool firstSceneSelected = false; QList selection = mDocumentUI->thumbnailWidget->selectedItems(); - for(int i = 0; i < selection.count() && !firstSceneSelected; i += 1) + for(int i = 0; i < selection.count(); i += 1) { UBSceneThumbnailPixmap* p = dynamic_cast(selection.at(i)); if (p) diff --git a/src/document/UBDocumentController.h b/src/document/UBDocumentController.h index a67908be..9999579d 100644 --- a/src/document/UBDocumentController.h +++ b/src/document/UBDocumentController.h @@ -401,6 +401,7 @@ class UBDocumentController : public UBDocumentContainer , const QModelIndex &selectedIndex , UBDocumentTreeModel *docModel) const; bool firstAndOnlySceneSelected() const; + bool everySceneSelected() const; QWidget *mainWidget() const {return mDocumentWidget;} //issue 1629 - NNE - 20131212 From fe06bdf1df1e2d2d802fa4e6c0242e0763029c15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Fri, 9 Jul 2021 16:56:31 +0200 Subject: [PATCH 013/130] same behavior for document naming as for folder naming --- src/document/UBDocumentController.cpp | 39 +++++++++++++++++++++------ src/document/UBDocumentController.h | 21 +++++++++++++++ 2 files changed, 52 insertions(+), 8 deletions(-) diff --git a/src/document/UBDocumentController.cpp b/src/document/UBDocumentController.cpp index 19fc3e77..523b87da 100644 --- a/src/document/UBDocumentController.cpp +++ b/src/document/UBDocumentController.cpp @@ -1645,10 +1645,15 @@ UBDocumentTreeItemDelegate::UBDocumentTreeItemDelegate(QObject *parent) void UBDocumentTreeItemDelegate::commitAndCloseEditor() { - QLineEdit *lineEditor = qobject_cast(sender()); - if (lineEditor) { - emit commitData(lineEditor); + QLineEdit *lineEditor = dynamic_cast(sender()); + if (lineEditor) + { + if (lineEditor->hasAcceptableInput()) + { + emit commitData(lineEditor); //emit closeEditor(lineEditor); + } + emit UBApplication::documentController->reorderDocumentsRequested(); } } @@ -1678,13 +1683,26 @@ QWidget *UBDocumentTreeItemDelegate::createEditor(QWidget *parent, const QStyleO //N/C - NNE - 20140407 : Add the test for the index column. if(index.column() == 0){ mExistingFileNames.clear(); - const UBDocumentTreeModel *indexModel = qobject_cast(index.model()); - if (indexModel) { - mExistingFileNames = indexModel->nodeNameList(index.parent()); - mExistingFileNames.removeOne(index.data().toString()); + const UBDocumentTreeModel *docModel = 0; + + const UBSortFilterProxyModel *proxy = dynamic_cast(index.model()); + + if(proxy){ + docModel = dynamic_cast(proxy->sourceModel()); + }else{ + docModel = dynamic_cast(index.model()); + } + + QModelIndex sourceIndex = proxy->mapToSource(index); + + if (docModel) { + mExistingFileNames = docModel->nodeNameList(sourceIndex.parent()); + mExistingFileNames.removeOne(sourceIndex.data().toString()); } QLineEdit *nameEditor = new QLineEdit(parent); + UBValidator* validator = new UBValidator(mExistingFileNames); + nameEditor->setValidator(validator); connect(nameEditor, SIGNAL(editingFinished()), this, SLOT(commitAndCloseEditor())); connect(nameEditor, SIGNAL(textChanged(QString)), this, SLOT(processChangedText(QString))); return nameEditor; @@ -1760,7 +1778,12 @@ void UBDocumentController::createNewDocument() ? docModel->virtualPathForIndex(selectedIndex) : docModel->virtualDirForIndex(selectedIndex); - UBDocumentProxy *document = pManager->createDocument(groupName); + + QDateTime now = QDateTime::currentDateTime(); + QString documentName = docModel->adjustNameForParentIndex(now.toString(Qt::SystemLocaleShortDate), selectedIndex.parent()); + + UBDocumentProxy *document = pManager->createDocument(groupName, documentName); + selectDocument(document, true, false, true); if (document) diff --git a/src/document/UBDocumentController.h b/src/document/UBDocumentController.h index 9999579d..5ee68d59 100644 --- a/src/document/UBDocumentController.h +++ b/src/document/UBDocumentController.h @@ -321,6 +321,27 @@ private: void updateIndexEnvirons(const QModelIndex &index); }; +class UBValidator : public QValidator +{ + const QStringList mExistingFileNames; + + public: + UBValidator(const QStringList existingFileNames, QObject *parent = nullptr) + : QValidator(parent) + , mExistingFileNames(existingFileNames) + { + + } + + QValidator::State validate(QString &input, int &pos) const + { + if (mExistingFileNames.contains(input)) + return QValidator::Intermediate; + else + return QValidator::Acceptable; + } +}; + class UBDocumentTreeItemDelegate : public QStyledItemDelegate { Q_OBJECT From 61721b7f3bfc278708ec7f86a823da8ffe209740 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Mon, 23 Aug 2021 09:11:58 +0200 Subject: [PATCH 014/130] clear the error message after displaying it as it prevents any attempt to play the media if mError is not empty ! --- src/domain/UBGraphicsMediaItem.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/domain/UBGraphicsMediaItem.cpp b/src/domain/UBGraphicsMediaItem.cpp index 47b025cb..ddc61513 100644 --- a/src/domain/UBGraphicsMediaItem.cpp +++ b/src/domain/UBGraphicsMediaItem.cpp @@ -427,6 +427,7 @@ void UBGraphicsMediaItem::mediaError(QMediaPlayer::Error errorCode) if (!mErrorString.isEmpty() ) { UBApplication::showMessage(mErrorString); qDebug() << mErrorString; + mErrorString.clear(); } } From 986cbd60ab797568080c2f932bbb15432d305779 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Mon, 23 Aug 2021 14:47:34 +0200 Subject: [PATCH 015/130] adjust name using selected index when a folder is selected --- src/document/UBDocumentController.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/document/UBDocumentController.cpp b/src/document/UBDocumentController.cpp index 523b87da..93680e16 100644 --- a/src/document/UBDocumentController.cpp +++ b/src/document/UBDocumentController.cpp @@ -1780,7 +1780,16 @@ void UBDocumentController::createNewDocument() QDateTime now = QDateTime::currentDateTime(); - QString documentName = docModel->adjustNameForParentIndex(now.toString(Qt::SystemLocaleShortDate), selectedIndex.parent()); + QString documentName = ""; + if (docModel->isCatalog(selectedIndex)) + { + documentName = docModel->adjustNameForParentIndex(now.toString(Qt::SystemLocaleShortDate), selectedIndex); + } + else + { + documentName = docModel->adjustNameForParentIndex(now.toString(Qt::SystemLocaleShortDate), selectedIndex.parent()); + } + UBDocumentProxy *document = pManager->createDocument(groupName, documentName); From ec5f202ef1ee3bf63acdf2cdb1ed312e3ce4e32f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Fri, 27 Aug 2021 12:42:42 +0200 Subject: [PATCH 016/130] always verify movingItem is not null + don't call QGraphicsView::mouseMoveEvent if movingItem is null + don't display grey rectangle with the magic finger --- src/board/UBBoardView.cpp | 123 ++++++++++++++++---------- src/domain/UBGraphicsItemDelegate.cpp | 13 +-- 2 files changed, 82 insertions(+), 54 deletions(-) diff --git a/src/board/UBBoardView.cpp b/src/board/UBBoardView.cpp index 5ba68364..0e25b8b0 100644 --- a/src/board/UBBoardView.cpp +++ b/src/board/UBBoardView.cpp @@ -486,8 +486,14 @@ void UBBoardView::handleItemsSelection(QGraphicsItem *item) if (item) { // item has group as first parent - it is any item or UBGraphicsStrokesGroup. - if(item->parentItem() && UBGraphicsGroupContainerItem::Type == getMovingItem()->parentItem()->type()) - return; + if (getMovingItem()) + { + if (getMovingItem()->parentItem()) + { + if(item->parentItem() && UBGraphicsGroupContainerItem::Type == getMovingItem()->parentItem()->type()) + return; + } + } // delegate buttons shouldn't selected if (DelegateButton::Type == item->type()) @@ -653,11 +659,17 @@ bool UBBoardView::itemShouldBeMoved(QGraphicsItem *item) if (!(mMouseButtonIsPressed || mTabletStylusIsPressed)) return false; - if (getMovingItem()->data(UBGraphicsItemData::ItemLocked).toBool()) - return false; + if (getMovingItem()) + { + if (getMovingItem()->data(UBGraphicsItemData::ItemLocked).toBool()) + return false; - if (getMovingItem()->parentItem() && UBGraphicsGroupContainerItem::Type == getMovingItem()->parentItem()->type() && !getMovingItem()->isSelected() && getMovingItem()->parentItem()->isSelected()) - return false; + if (getMovingItem()->parentItem()) + { + if (UBGraphicsGroupContainerItem::Type == getMovingItem()->parentItem()->type() && !getMovingItem()->isSelected() && getMovingItem()->parentItem()->isSelected()) + return false; + } + } UBStylusTool::Enum currentTool = (UBStylusTool::Enum)UBDrawingController::drawingController()->stylusTool(); @@ -831,20 +843,22 @@ void UBBoardView::handleItemMouseMove(QMouseEvent *event) QPointF posAfterMove; if (getMovingItem()) + { posBeforeMove = getMovingItem()->pos(); - - QGraphicsView::mouseMoveEvent (event); - - if (getMovingItem()) + QGraphicsView::mouseMoveEvent (event); posAfterMove = getMovingItem()->pos(); + } mWidgetMoved = ((posAfterMove-posBeforeMove).manhattanLength() != 0); // a cludge for terminate moving of w3c widgets. // in some cases w3c widgets catches mouse move and doesn't sends that events to web page, // at simple - in google map widget - mouse move events doesn't comes to web page from rectangle of wearch bar on bottom right corner of widget. - if (getMovingItem() && mWidgetMoved && UBGraphicsW3CWidgetItem::Type == getMovingItem()->type()) - getMovingItem()->setPos(posBeforeMove); + if (getMovingItem()) + { + if (mWidgetMoved && UBGraphicsW3CWidgetItem::Type == getMovingItem()->type()) + getMovingItem()->setPos(posBeforeMove); + } } } @@ -1270,51 +1284,58 @@ void UBBoardView::mouseReleaseEvent (QMouseEvent *event) setMovingItem(nullptr); } else - if (getMovingItem() && (!isCppTool(getMovingItem()) || UBGraphicsCurtainItem::Type == getMovingItem()->type())) + { + if (getMovingItem()) { - if (suspendedMousePressEvent) - { - QGraphicsView::mousePressEvent(suspendedMousePressEvent); // suspendedMousePressEvent is deleted by old Qt event loop - setMovingItem(NULL); - delete suspendedMousePressEvent; - suspendedMousePressEvent = NULL; - bReleaseIsNeed = true; - } - else + if (!isCppTool(getMovingItem()) || UBGraphicsCurtainItem::Type == getMovingItem()->type()) { - if (isUBItem(getMovingItem()) && - DelegateButton::Type != getMovingItem()->type() && - UBGraphicsDelegateFrame::Type != getMovingItem()->type() && - UBGraphicsCache::Type != getMovingItem()->type() && - QGraphicsWebView::Type != getMovingItem()->type() && // for W3C widgets as Tools. - !(!isMultipleSelectionEnabled() && getMovingItem()->parentItem() && UBGraphicsWidgetItem::Type == getMovingItem()->type() && UBGraphicsGroupContainerItem::Type == getMovingItem()->parentItem()->type())) + if (suspendedMousePressEvent) { - bReleaseIsNeed = false; - if (getMovingItem()->isSelected() && isMultipleSelectionEnabled()) - getMovingItem()->setSelected(false); - else - if (getMovingItem()->parentItem() && getMovingItem()->parentItem()->isSelected() && isMultipleSelectionEnabled()) - getMovingItem()->parentItem()->setSelected(false); + QGraphicsView::mousePressEvent(suspendedMousePressEvent); // suspendedMousePressEvent is deleted by old Qt event loop + setMovingItem(NULL); + delete suspendedMousePressEvent; + suspendedMousePressEvent = NULL; + bReleaseIsNeed = true; + } + else + { + if (isUBItem(getMovingItem()) && + DelegateButton::Type != getMovingItem()->type() && + UBGraphicsDelegateFrame::Type != getMovingItem()->type() && + UBGraphicsCache::Type != getMovingItem()->type() && + QGraphicsWebView::Type != getMovingItem()->type() && // for W3C widgets as Tools. + !(!isMultipleSelectionEnabled() && getMovingItem()->parentItem() && UBGraphicsWidgetItem::Type == getMovingItem()->type() && UBGraphicsGroupContainerItem::Type == getMovingItem()->parentItem()->type())) + { + bReleaseIsNeed = false; + if (getMovingItem()->isSelected() && isMultipleSelectionEnabled()) + getMovingItem()->setSelected(false); else - { - if (getMovingItem()->isSelected()) - bReleaseIsNeed = true; - - UBGraphicsTextItem* textItem = dynamic_cast(getMovingItem()); - UBGraphicsMediaItem* movieItem = dynamic_cast(getMovingItem()); - if(textItem) - textItem->setSelected(true); - else if(movieItem) - movieItem->setSelected(true); + if (getMovingItem()->parentItem() && getMovingItem()->parentItem()->isSelected() && isMultipleSelectionEnabled()) + getMovingItem()->parentItem()->setSelected(false); else - getMovingItem()->setSelected(true); - } + { + if (getMovingItem()->isSelected()) + bReleaseIsNeed = true; + + UBGraphicsTextItem* textItem = dynamic_cast(getMovingItem()); + UBGraphicsMediaItem* movieItem = dynamic_cast(getMovingItem()); + if(textItem) + textItem->setSelected(true); + else if(movieItem) + movieItem->setSelected(true); + else + getMovingItem()->setSelected(true); + } + } } } + else + bReleaseIsNeed = true; } else bReleaseIsNeed = true; + } if (bReleaseIsNeed) { @@ -1402,9 +1423,13 @@ void UBBoardView::mouseReleaseEvent (QMouseEvent *event) return; } - if (mWidgetMoved) { - getMovingItem()->setSelected(false); - setMovingItem(NULL); + if (mWidgetMoved) + { + if (getMovingItem()) + { + getMovingItem()->setSelected(false); + setMovingItem(NULL); + } mWidgetMoved = false; } else { diff --git a/src/domain/UBGraphicsItemDelegate.cpp b/src/domain/UBGraphicsItemDelegate.cpp index bb19e2ca..49085698 100644 --- a/src/domain/UBGraphicsItemDelegate.cpp +++ b/src/domain/UBGraphicsItemDelegate.cpp @@ -328,12 +328,15 @@ void UBGraphicsItemDelegate::postpaint(QPainter *painter, const QStyleOptionGrap { Q_UNUSED(widget) if (option->state & QStyle::State_Selected && !controlsExist()) { - painter->save(); - painter->setPen(Qt::NoPen); - painter->setBrush(QColor(0x88, 0x88, 0x88, 0x77)); - painter->drawRect(option->rect); + if (UBStylusTool::Play != UBDrawingController::drawingController()->stylusTool()) + { + painter->save(); + painter->setPen(Qt::NoPen); + painter->setBrush(QColor(0x88, 0x88, 0x88, 0x77)); + painter->drawRect(option->rect); - painter->restore(); + painter->restore(); + } } } From 60bed975fddd43487846ff06e39223e0029328d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Fri, 27 Aug 2021 16:16:52 +0200 Subject: [PATCH 017/130] fixed an issue where reloadThumbnails & treeviewSelectionChanged would be called every time a page is deleted ! Resulting on really poor performances for the deletion operation, even in a crash, if trying with a lot of pages. --- .qmake.stash | 23 +++++++++++++++++++++++ src/core/UBPersistenceManager.cpp | 5 ----- src/core/UBPersistenceManager.h | 1 - src/document/UBDocumentController.cpp | 1 - 4 files changed, 23 insertions(+), 7 deletions(-) create mode 100644 .qmake.stash diff --git a/.qmake.stash b/.qmake.stash new file mode 100644 index 00000000..d6e97efd --- /dev/null +++ b/.qmake.stash @@ -0,0 +1,23 @@ +QMAKE_CXX.QT_COMPILER_STDCXX = 201402L +QMAKE_CXX.QMAKE_GCC_MAJOR_VERSION = 9 +QMAKE_CXX.QMAKE_GCC_MINOR_VERSION = 3 +QMAKE_CXX.QMAKE_GCC_PATCH_VERSION = 0 +QMAKE_CXX.COMPILER_MACROS = \ + QT_COMPILER_STDCXX \ + QMAKE_GCC_MAJOR_VERSION \ + QMAKE_GCC_MINOR_VERSION \ + QMAKE_GCC_PATCH_VERSION +QMAKE_CXX.INCDIRS = \ + /usr/include/c++/9 \ + /usr/include/x86_64-linux-gnu/c++/9 \ + /usr/include/c++/9/backward \ + /usr/lib/gcc/x86_64-linux-gnu/9/include \ + /usr/local/include \ + /usr/include/x86_64-linux-gnu \ + /usr/include +QMAKE_CXX.LIBDIRS = \ + /usr/lib/gcc/x86_64-linux-gnu/9 \ + /usr/lib/x86_64-linux-gnu \ + /usr/lib \ + /lib/x86_64-linux-gnu \ + /lib diff --git a/src/core/UBPersistenceManager.cpp b/src/core/UBPersistenceManager.cpp index d9703d40..70a6ceb8 100644 --- a/src/core/UBPersistenceManager.cpp +++ b/src/core/UBPersistenceManager.cpp @@ -622,11 +622,6 @@ void UBPersistenceManager::deleteDocumentScenes(UBDocumentProxy* proxy, const QL if (compactedIndexes.size() == 0) return; - foreach(int index, compactedIndexes) - { - emit documentSceneWillBeDeleted(proxy, index); - } - QString sourceName = proxy->metaData(UBSettings::documentName).toString(); UBDocumentProxy *trashDocProxy = createDocument(UBSettings::trashedDocumentGroupNamePrefix/* + sourceGroupName*/, sourceName, false); diff --git a/src/core/UBPersistenceManager.h b/src/core/UBPersistenceManager.h index 2f5356f3..93492def 100644 --- a/src/core/UBPersistenceManager.h +++ b/src/core/UBPersistenceManager.h @@ -166,7 +166,6 @@ class UBPersistenceManager : public QObject void documentWillBeDeleted(UBDocumentProxy* pDocumentProxy); void documentSceneCreated(UBDocumentProxy* pDocumentProxy, int pIndex); - void documentSceneWillBeDeleted(UBDocumentProxy* pDocumentProxy, int pIndex); private: int sceneCount(const UBDocumentProxy* pDocumentProxy); diff --git a/src/document/UBDocumentController.cpp b/src/document/UBDocumentController.cpp index 93680e16..fcde2f26 100644 --- a/src/document/UBDocumentController.cpp +++ b/src/document/UBDocumentController.cpp @@ -2128,7 +2128,6 @@ void UBDocumentController::setupViews() connect(mDocumentUI->thumbnailWidget->scene(), SIGNAL(selectionChanged()), this, SLOT(pageSelectionChanged())); connect(UBPersistenceManager::persistenceManager(), SIGNAL(documentSceneCreated(UBDocumentProxy*, int)), this, SLOT(documentSceneChanged(UBDocumentProxy*, int))); - connect(UBPersistenceManager::persistenceManager(), SIGNAL(documentSceneWillBeDeleted(UBDocumentProxy*, int)), this, SLOT(documentSceneChanged(UBDocumentProxy*, int))); mDocumentUI->thumbnailWidget->setBackgroundBrush(UBSettings::documentViewLightColor); From dfca689d2d372cde0056d0406d148603a323ee75 Mon Sep 17 00:00:00 2001 From: alexrsoares <85532589+alexrsoares@users.noreply.github.com> Date: Mon, 20 Sep 2021 12:30:58 -0300 Subject: [PATCH 018/130] Update OpenBoard_pt_BR.ts Portuguese version updated based on English (en_GB) and French (fr_CH) versions. --- resources/i18n/OpenBoard_pt_BR.ts | 1889 +++++++++++++++++------------ 1 file changed, 1116 insertions(+), 773 deletions(-) diff --git a/resources/i18n/OpenBoard_pt_BR.ts b/resources/i18n/OpenBoard_pt_BR.ts index b5240dd5..342e27e7 100644 --- a/resources/i18n/OpenBoard_pt_BR.ts +++ b/resources/i18n/OpenBoard_pt_BR.ts @@ -1,1716 +1,1803 @@ - + BlackoutWidget Click to Return to Application - Clicar para voltar para a Aplicação + Clique para voltar à Aplicação DownloadDialog Downloads - Transferências + Transferências Clean Up - Limpar + Limpar 0 Items - 0 Itens + 0 Itens DownloadItem Form - Formulário + Formulário Filename - Nome do arquivo + Nome do arquivo Try Again - Tentar novamente + Tentar novamente Stop - Parar + Parar Open - Abrir + Abrir IntranetPodcastPublishingDialog Publish Podcast to YouTube - Publicar Podcast no YouTube + Publicar Podcast no YouTube Title - Título + Título Description - Descrição + Descrição Author - Autor + Autor MainWindow + + OpenBoard + OpenBoard + Board - Quadro + Quadro Web - Web + Web Documents - Documentos + Documentos Stylus - Paleta + Paleta Ctrl+T - Ctrl+T + Ctrl+T Backgrounds - Fundo + Fundo Change Background - Mudar Fundo + Mudar Fundo Undo - Desfazer + Desfazer Ctrl+Z - Ctrl+Z + Ctrl+Z Redo - Refazer + Refazer Ctrl+Y - Ctrl+Y + Ctrl+Y Previous - Anterior + Anterior Previous Page - Página anterior + Página anterior PgUp - Subir na página + Subir na página Next - Próximo + Próximo Next Page - Próxima Página + Próxima Página PgDown - Descer na página + Descer na página Manage Documents - Gerir Documentos + Gerir Documentos Ctrl+D - Ctrl+D + Ctrl+D Web Browsing - Navegar na Web + Navegar na Web Ctrl+W - Ctrl+W + Ctrl+W Line - Linha + Linha Small Line - Traço Fino + Traço Fino Medium Line - Traço Médio + Traço Médio Large Line - Traço Grosso + Traço Grosso Quit - Sair + Sair - Eraser - Borracha + Quit OpenBoard + Fechar OpenBoard - Smalle Eraser - Borracha Pequena + Eraser + Borracha Medium Eraser - Borracha Média + Borracha Média Large Eraser - Borracha Grande - - - Color - Cores + Borracha Grande Back - Recuar + Voltar Left - Esquerda + Esquerda Forward - Avançar + Avançar Right - Direita + Direita Reload - Recarregar + Recarregar Reload Current Page - Recarregar a página atual + Recarregar a página atual Home - Página Inicial + Página Inicial Load Home Page - Carregar a Página Inicial + Carregar a Página Inicial Bookmarks - Favoritos + Favoritos Show Bookmarks - Mostrar os Favoritos + Mostrar os Favoritos Bookmark - Favorito + Favorito Add Bookmark - Adicionar aos Favoritos + Adicionar aos Favoritos Display Board - Mostrar o Quadro + Mostrar o Quadro Ctrl+B - Ctrl+B + Ctrl+B Erase - Apagar + Apagar Erase Content - Apagar Conteúdo + Apagar Conteúdo Preferences - Preferências + Preferências Display Preferences - Mostrar Preferências + Mostrar Preferências Library - Biblioteca + Biblioteca Show Library - Mostrar Biblioteca + Mostrar Biblioteca Ctrl+L - Ctrl+L + Ctrl+L Show Desktop - Ver desktop + Área de Trabalho Show Computer Desktop - Mostrar o ambiente de trabalho do Computador + Mostrar o ambiente de trabalho do Computador Ctrl+Shift+H - Ctrl+Shift+H + Ctrl+Shift+H Bigger - Ampliar + Ampliar Zoom In - Aproximar + Ampliar Ctrl++ - Ctrl++ + Ctrl++ Smaller - Reduzir + Reduzir Zoom Out - Afastar + Afastar Ctrl+- - Ctrl+- + Ctrl+- New Folder - Nova Pasta + Nova Pasta Create a New Folder - Criar uma Nova Pasta + Criar uma Nova Pasta New Document - Novo Documento + Novo Documento Create a New Document - Criar um Novo Documento + Criar um Novo Documento Import - Importar + Importar Import a Document - Importar um Documento + Importar um Documento Export - Exportar + Exportar Export a Document - Exportar um Documento + Exportar um Documento Open in Board - Abrir no Quadro + Abrir no Quadro Open Page in Board - Abrir Página no Quadro + Abrir Página no Quadro Ctrl+O - Ctrl+O + Ctrl+O Duplicate - Duplicar + Duplicar Duplicate Selected Content - Duplicar o Conteúdo Selecionado + Duplicar o Conteúdo Selecionado Delete - Apagar + Apagar Delete Selected Content - Apagar o Conteúdo Selecionado + Apagar o Conteúdo Selecionado Del - Del + Del Add to Working Document - Adicionar ao Documento de Trabalho + Adicionar ao Documento de Trabalho Add Selected Content to Open Document - Adicionar o Conteúdo Selecionado ao Documento Aberto + Adicionar o Conteúdo Selecionado ao Documento Aberto Add - Adicionar + Adicionar Add Content to Document - Adicionar Conteúdo ao Documento + Adicionar Conteúdo ao Documento Rename - Mudar o Nome + Mudar o Nome Rename Content - Mudar o Nome do Conteúdo + Mudar o Nome do Conteúdo Tools - Ferramentas + Ferramentas Display Tools - Mostrar Ferramentas + Mostrar Ferramentas Multi Screen - Múltiplos Monitores + Múltiplos Monitores Wide Size (16/9) - Formato Panorâmico (16/9) + Formato Panorâmico (16/9) Use Document Wide Size (16/9) - Usar documento no Formato Panorâmico (16/9) + Usar documento no Formato Panorâmico (16/9) Regular Size (4/3) - Formato Tradicional (4/3) + Formato Tradicional (4/3) Use Document Regular Size (4/3) - Usar documento no Formato Tradicional (4/3) + Usar documento no Formato Tradicional (4/3) Custom Size - Tamanho Personalizado + Tamanho Personalizado Use Custom Document Size - Usar documento no Tamanho Personalizado + Usar documento no Tamanho Personalizado Stop Loading - Parar de Carregar + Parar de Carregar Stop Loading Web Page - Parar de Carregar a Página Web + Parar de Carregar a Página Web Cut - Cortar + Cortar Copy - Copiar + Copiar Paste - Colar + Colar Sleep - Modo "Espera" + Modo "Espera" Put Presentation to Sleep - Colocar a Apresentaçãao em modo "Espera" + Colocar a Apresentaçãao em modo "Espera" Virtual Keyboard - Teclado Virtual + Teclado Virtual Display Virtual Keyboard - Mostrar o Teclado Virtual + Mostrar o Teclado Virtual Plain Light Background - Fundo Branco e Liso + Fundo Branco e Liso Light - Claro + Claro Grid Light Background - Fundo Branco e Quadriculado + Fundo Claro Quadriculado + + + Ruled Light Background + Caderno com Fundo Claro Plain Dark Background - Fundo Preto e Liso + Fundo Escuro Quadriculado Dark - Escuro + Escuro Grid Dark Background - Fundo Preto e Quadriculado + Fundo Escuro Quadriculado + + + Ruled Dark Background + Caderno com Fundo Escuro Podcast - Podcast + Podcast Record Presentation to Video - Gravar a Apresentação em Vídeo + Gravar a Apresentação em Vídeo Record - Gravar + Gravar Start Screen Recording - Iniciar a Gravação do Vídeo + Iniciar a Gravação do Vídeo Erase Items - Apagar Itens + Apagar Itens Erase All Items - Apagar Todos os Itens + Apagar Todos os Itens Erase Annotations - Apagar Anotações + Apagar Anotações Erase All Annotations - Apagar Todas as Anotações + Apagar Todas as Anotações Clear Page - Limpar a Página + Limpar a Página Clear All Elements - Limpar Todos os Elementos + Limpar Todos os Elementos Pen - Caneta + Caneta Annotate Document - Escrever + Escrever Ctrl+I - Ctrl+I + Ctrl+I Erase Annotation - Apagar + Apagar Anotação Ctrl+E - Ctrl+E + Ctrl+E Marker - Marcador + Marca-Texto Highlight - Marcador + Realçar Ctrl+M - Ctrl+M + Ctrl+M Selector - Seletor + Seletor Select And Modify Objects - Selecionar e Modificar Objetos + Selecionar e Modificar Objetos Ctrl+F - Ctrl+F + Ctrl+F Hand - Mão + Mão Scroll Page - Desloca-se na página + Arrastar a página Laser Pointer - Apontador Laser + Apontador Laser Virtual Laser Pointer - Apontador Laser Virtual + Apontador Laser Virtual Ctrl+G - Ctrl+G + Ctrl+G Draw Lines - Desenhar Linhas + Desenhar Linhas Ctrl+J - Ctrl+J + Ctrl+J Text - Texto + Texto Write Text - Escrever Texto + Escrever Texto Ctrl+K - Ctrl+K + Ctrl+K Capture - Capturar + Capturar Capture Part of the Screen - Capturar uma Parte da Tela + Capturar uma Parte da Tela Add To Current Page - Adicionar à Página Atual + Adicionar à Página Atual Add Item To Current Page - Adicionar Item à Página Atual + Adicionar Item à Página Atual Add To New Page - Adicionar a uma Página Nova + Adicionar a uma Página Nova Add Item To New Page - Adicionar Item a uma Página Nova + Adicionar Item a uma Página Nova Add To Library - Adicionar à Biblioteca + Adicionar à Biblioteca Add Item To Library - Adicionar Item à Biblioteca + Adicionar Item à Biblioteca Pages - Páginas + Páginas Create a New Page - Criar uma Nova Página + Criar uma Nova Página New Page - Nova Página + Nova Página Duplicate Page - Duplicar Página + Duplicar Página Duplicate the Current Page - Duplicar a Página Atual + Duplicar a Página Atual Import Page - Importar Página + Importar Página Import an External Page - Importar uma Página Externa + Importar uma Página Externa Pause - Pausa + Pausa Pause Podcast Recording - Pausar a Gravação do Podcast + Pausar a Gravação do Podcast Podcast Config - Configuração do Podcast + Configuração do Podcast Configure Podcast Recording - Configuração da Gravação de Podcast + Configuração da Gravação de Podcast Flash Trap - Captura de Flash + Captura de Flash Trap Flash Content - Captura de Conteúdo Flash + Captura de Conteúdo Flash Web Trap - Captura de Web + Captura de Web Trap Web Content - Captura de Conteúdo Web + Captura de Conteúdo Web Custom Capture - Captura Personalizada + Captura Personalizada Window Capture - Captura da Janela + Captura da Janela Capture a Window - Capturar uma Janela + Capturar uma Janela Embed Web Content - Conteúdo Web Embutido + Conteúdo Web Embutido Capture Embeddable Web Content - Capturar Conteúdo Web Embutido + Capturar Conteúdo Web Embutido Show on Display - Mostrar no Vídeo + Mostrar no Vídeo Show Main Screen on Display Screen - Mostrar a Tela Principal no Vídeo + Mostrar a Tela Principal no Vídeo Erase all Annotations - Apagar todas as notas + Apagar todas as notas eduMedia - eduMedia + eduMedia Import eduMedia simulation - Importar uma simulação eduMedia + Importar uma simulação eduMedia Check Update - Verificar Atualização - - - Ctrl+H - Ctrl+H - - - OpenBoard - OpenBoard - - - Quit OpenBoard - Sair do OpenBoard + Verificar Atualização Hide OpenBoard - Ocultar o OpenBoard + Ocultar o OpenBoard Hide OpenBoard Application - Ocultar a Aplicação OpenBoard + Ocultar a Aplicação OpenBoard + + + Ctrl+H + Ctrl+H Play Começar(?) - Iniciar + Iniciar Interact with items - Interagir com itens + Interagir com itens Erase Background - Apagar o fundo + Apagar o fundo Remove the backgound - Remover o fundo + Remover o fundo Open Tutorial - Abrir Tutorial + Abrir Tutorial Open the tutorial web page - Abrir a página web de tutorial + Abrir tutorial na web + + + Reset grid size + Redefinir o tamanho da grade + + + Small Eraser + Borracha estreita + + + Color 1 + Cor 1 + + + Color 2 + Cor 2 + + + Color 3 + Cor 3 + + + Color 4 + Cor 4 + + + Color 5 + Cor 5 + + + Draw intermediate grid lines + Desenhar linhas intermediárias PasswordDialog Authentication Required - Autenticação Necessária + Autenticação Necessária Username: - Usuário: + Usuário: Password: - Senha: + Senha: ProxyDialog Proxy Authentication - Autenticação do Proxy + Autenticação do Proxy Connect to Proxy - Conetar ao Proxy + Conetar ao Proxy Username: - Nome de Utilizador: + Nome do Usuário: Password: - Senha: + Senha: Save username and password for future use - Gravar "Nome de Usuário" e "Senha" para futuras utilizações + Gravar "Nome de Usuário" e "Senha" para futuras utilizações QObject Element ID = - ID do Elemento + ID do Elemento Content is not supported in destination format. - O conteúdo não é suportado no formato de destino. + O conteúdo não é suportado no formato de destino. Remove Page - Remover Página + Remover Página Are you sure you want to remove 1 page from the selected document '%0'? - Tem a certeza que quer remover 1 página do documento selecionado '%0'? + Tem certeza que deseja remover 1 página do documento '%0' selecionado? + + + Common + Usual UBApplication Page Size - Tamanho da Página + Tamanho da Página Podcast - Podcast + Podcast UBApplicationController Web - Web + Web Update available - Atualização disponível + Atualização disponível New update available, would you go to the web page ? - Nova atualização disponível. Ir para a página web? + Atualização disponível: deseja ir até a página de download? + + + Update + Atualizar No update available - Nenhuma atualização disponível + Sem atualização disponível + + + UBBackgroundPalette - Update - Atualizar + Grid size + Tamanho da grade + + + Draw intermediate grid lines + Desenhar linhas de grade intermediárias UBBoardController - Downloading content %1 failed - A transferência de conteúdos falhou %1 + Group + Agrupar - Download finished - Transferência Completa + Ungroup + Desagrupar - Unknown tool type %1 - Tipo de ferramenta desconhecido %1 + Saving document... + Salvando documento... - Unknown content type %1 - Tipo de conteúdo desconhecido %1 + Document has just been saved... + O documento acabou de ser salvo... - Add Item - Adicionar Item + Deleting page %1 + Deletando página %1 - All Supported (%1) - Tudo Suportado (%1) + Page %1 deleted + Página %1 deletada - Delete page %1 from document - Apagar a página %1 do documento + Downloading content %1 failed + A transferência de conteúdos falhou %1 - Page %1 deleted - Página %1 apagada + Download finished + Transferência concluída Add file operation failed: file copying error - Falha na operação de adição de arquivo: erro na cópia do arquivo + Falha na operação de adição de arquivo: erro ao copiar arquivo - Group - Agrupar + Unknown tool type %1 + Tipo de ferramenta desconhecido %1 - Ungroup - Desagrupar + Unknown content type %1 + Tipo de conteúdo desconhecido %1 - Saving document... - Gravando documento... + Add Item + Adicionar Item - Document has just been saved... - Documento acaba de ser gravado... + All Supported (%1) + Tudo Suportado (%1) + + + Color + Cor UBBoardPaletteManager + + CapturedImage + Imagem capturada + Error Adding Image to Library - Erro ao Adicionar Imagem à Biblioteca + Erro ao Adicionar Imagem à Biblioteca + + + UBBoardThumbnailsView - CapturedImage - Imagem capturada + Loading page (%1/%2) + Carregando página (%1/%2) UBCachePropertiesWidget Cache Properties - Propriedades da Cache + Propriedades da Cache Color: - Cor: + Cor: Shape: - Forma: + Forma: Size: - Tamanho: + Tamanho: Close - Fechar + Fechar UBDesktopPalette + + Show OpenBoard + Mostrar OpenBoard + Capture Part of the Screen - Capturar Parte da Tela + Capturar Parte da Tela Capture the Screen - Capturar Tela + Capturar Tela Show the stylus palette - Mostrar Paleta + Mostrar Paleta Show Board on Secondary Screen - Mostrar o Quadro num Vídeo Secundário + Mostrar o Quadro numa Tela Secundária Show Desktop on Secondary Screen - Mostrar Ambiente de Trabalho num Vídeo Secundário - - - Show OpenBoard - Mostrar OpenBoard + Mostrar Área de Trabalho numa Tela Secundária UBDocumentController - New Folder - Nova Pasta + Trash + Lixeira - Page %1 - Página %1 + Untitled Documents + Documentos sem nome + + + New Folder + Nova Pasta Add Folder of Images - Adicionar Pasta de Imagens + Adicionar Pasta de Imagens Add Images - Adicionar Imagens + Adicionar Imagens Add Pages from File - Adicionar Páginas de um Arquivo + Adicionar Páginas de um Arquivo + + + duplicated %1 page + duplicated %1 pages + + %1 página duplicada + %1 páginas duplicadas + Duplicating Document %1 - A Duplicar Documento %1 + Copiando Documento %1 Document %1 copied - Documento %1 Copiado - - - Remove Page - Remover Página - - - Remove Document - Remover Documento - - - Are you sure you want to remove the document '%1'? - Tem a certeza que quer remover o documento '%1'? - - - Empty Trash - Vazar Lixo - - - Are you sure you want to empty trash? - Tem a certeza que quer vazar a pasta Lixo? + Documento %1 copiado - Emptying trash - A vazar Lixo + Remove Item + Remover Item - Emptied trash - Pasta Lixo limpa - - - Remove Folder - Apagar Pasta - - - Are you sure you want to remove the folder '%1' and all its content? - Tem a certeza que quer apagar a pasta '%1' e todo o seu conteúdo? - - - No document selected! - Não há documentos selecionados! + Are you sure you want to remove the selected item(s) ? + Tem certeza que deseja excluir os itens selecionados? Open Supported File - Abrir Arquivo Suportado + Abrir Arquivo Suportado Importing file %1... - Importando arquivo %1... + Importando arquivo %1... Failed to import file ... - Falha ao importar arquivo ... + Falha ao importar arquivo ... Import all Images from Folder - Importar todas as Imagens de uma Pasta - - - Delete - Apagar + Importar todas as Imagens de uma Pasta - Empty - Vazio + Folder does not contain any image files + A pasta não contém imagens - Trash - Lixo + Open Document + Abrir Documento - Open Document - Abrir Documento + The document '%1' has been generated with a newer version of OpenBoard (%2). By opening it, you may lose some information. Do you want to proceed? + O documento '%1' foi produzido com uma versão mais nova do OpenBoard (%2). Ao abri-lo, você poderá perder alguma informação. Gostaria de continuar? Add all Images to Document - Adicionar todas as imagens ao Documento + Adicionar todas as imagens ao Documento All Images (%1) - Todas as Imagens (%1) + Todas as Imagens (%1) Selection does not contain any image files! - A seleção não contém arquivos de Imagem! - - - Are you sure you want to remove %n page(s) from the selected document '%1'? - - Tem certeza que quer apagar %n página do documento selecionado '%1'? - Tem certeza que quer apagar %n páginas do documento selecionado '%1'? - - - - Folder does not contain any image files - O diretório não contém imagens + A seleção não contém arquivos de Imagem! - Untitled Documents - Documentos sem nome + Delete + Apagar - The document '%1' has been generated with a newer version of OpenBoard (%2). By opening it, you may lose some information. Do you want to proceed? - O documento '%1' foi produzido com uma versão mais nova do OpenBoard (%2). Ao abri-lo, você poderá perder alguma informação. Gostaria de continuar? + Page %1 + Página %1 - Are you sure you want to remove all selected documents? - Você tem certeza que deseja remover todos os documentos selecionados? + Title page + Título da página - Remove multiple documents - Remover múltiplos documentos + Empty + Vazio UBDocumentManager images - imagens + imagens videos - vídeos + vídeos objects - objetos + objetos widgets - widgets + widgets All supported files (*.%1) - Todos os arquivos suportados (*.%1) - - - File %1 saved - Arquivo %1 gravado + Todos os arquivos suportados (*.%1) Inserting page %1 of %2 - A inserir página %1 de %2 + A inserir página %1 de %2 Import successful. - Importação bem sucedida. + Importado com sucesso.. + + + Importing file %1 + Importando o arquivo %1 Import of file %1 successful. - Importação do arquivo %1 bem sucedida. + Importação do arquivo %1 bem sucedida. - Importing file %1 - Importando o arquivo %1 + File %1 saved + Arquivo %1 gravado UBDocumentNavigator Page %0 - Página %0 + Página %0 - UBDocumentTreeWidget + UBDocumentReplaceDialog - %1 (copy) - %1 (cópia) + Accept + Aceitar - Copying page %1/%2 - Copiando página %1/%2 + Replace + Substituir + + + Cancel + Cancelar + + + The name %1 is allready used. +Keeping this name will replace the document. +Providing a new name will create a new document. + O nome %1 já está em uso. +ATENÇÃO: Ao manter este nome, o documento antigo será sobrescrito pelo novo. +Um outro nome permitirá que você mantenha o documento antigo. + + + + UBDocumentTreeModel + + My documents + Meus documentos + + + Trash + Lixeira + + + %1 pages copied + + %1 página copiada + %1 páginas copiadas + + + + UBDocumentTreeView %1 pages copied - + %1 página copiada %1 páginas copiadas + + Remove Item + Remover itens + + + Are you sure you want to remove the selected item(s) ? + Tem certeza que deseja excluir os itens selecionados? + + + Copying page %1/%2 + Cópia da página %1/%2 em andamento + UBDownloadWidget Downloading files - Baixando arquivos + Transferindo arquivos Cancel - Cancelar + Cancelar - UBExportAdaptor + UBDraggableThumbnail - Warnings during export was appeared - Surgiram alertas durante a exportação + Page %0 + Página %0 - UBExportCFF + UBDraggableThumbnailView - Export to IWB - Exportar para IWB + Page %0 + Página %0 + + + UBExportAdaptor - Export as IWB File - Exportar como arquivo IWB + Exporting document... + A exportar documento... - Exporting document... - A exportar documento... + Export failed + Falha ao exportar + + + Unable to export to the selected location. You do not have the permissions necessary to save the file. + Impossível exportar para a localização selcionada. Você não possui as permissões necessárias para gravar o arquivo. + + + Export failed: location not writable + Exportação falhou: localização não gravável Export successful. - Exportação bem sucedida. + Exportado com sucesso. - Export failed. - Falha na Exportação. + Warnings during export was appeared + Surgiram alertas durante a exportação - UBExportDocument + UBExportCFF - Page - Página + Export to IWB + Exportar como IWB - Export as UBZ File - Exportar como Arquivo UBZ + Export as IWB File + Exportar como arquivo IWB Exporting document... - A exportar documento... + A exportar documento... Export successful. - Exportação bem sucedida. + Exportado com sucesso. - Exporting %1 %2 of %3 - A exportar %1 %2 de %3 + Export failed. + Falha na Exportação. + + + UBExportDocument - Export to OpenBoard Format - Exportar para o formato OpenBoard + Page + Página - Export failed: location not writable - Exportação falhou: localização não gravável + Export as UBZ File + Exportar como Arquivo UBZ - Export failed - Exportação falhou + Exporting %1 %2 of %3 + A exportar %1 %2 de %3 - Unable to export to the selected location. You do not have the permissions necessary to save the file. - Impossível exportar para a localização selcionada. Você não possui as permissões necessárias para gravar o arquivo. + Export to OpenBoard Format + Exportar para o formato OpenBoard - UBExportFullPDF + UBExportDocumentSetAdaptor - Export as PDF File - Exportar como Arquivo PDF + Failed to export... + Falha ao exportar... + + + Export as UBX File + Exportar para formato UBX (*.ubx) Exporting document... - A exportar documento... + Exportando documento... Export successful. - Exportação bem sucedida. + Exportado com sucesso. - Export to PDF - Exportar para PDF + Export failed. + Falha ao exportar. - Export failed: location not writable - Exportação falhou: localização não gravável + Export to OpenBoard UBX Format + Exportar para formato OpenBoard UBX + + + UBExportFullPDF - Export failed - Exportação falhou + Export as PDF File + Exportar como Arquivo PDF - Unable to export to the selected location. You do not have the permissions necessary to save the file. - Impossível exportar para a localização selecionada. Você não possui as permissões necessárias para gravar o arquivo. + Export to PDF + Exportar como PDF UBExportPDF Export as PDF File - Exportar como Arquivo PDF - - - Exporting document... - Exportando documento... - - - Export successful. - Exportação bem sucedida. + Exportar como Arquivo PDF Exporting page %1 of %2 - Exportando página %1 de %2 + Exportando página %1 de %2 Export to PDF - Exportar para PDF + Exportar como PDF UBExportWeb Page - Página + Página Export as Web data - Exportar para formato de Web + Exportar para formato de Web Exporting document... - Exportando documento... + A exportar documento... Export successful. - Exportação bem sucedida. + Exportado com sucesso. Export failed. - Falha na exportação. + Exportação falhou. Export to Web Browser - Exportar para Explorador Web + Exportar para Navegador Web UBFeatureProperties Add to page - Adicionar à página - - - Set as background - Definir como fundo + Adicionar à página Add to library - Adicionar à biblioteca + Adicionar à biblioteca Object informations - Informações do Objecto + Informações do Objeto UBFeaturesActionBar Add to favorites - Adicionar aos favoritos + Adicionar aos favoritos Share - Partilhar + Partilhar Search - Procurar + Procurar + + + Rescan file system + Verificar novamente o sistema de arquivos Delete - Apagar + Apagar Back to folder - Voltar à pasta + Voltar à pasta Remove from favorites - Remover dos favoritos + Remover dos favoritos Create new folder - Criar uma nova pasta - - - Rescan file system - "Procurar arquivo de Sistema"(?) - Voltar a procurar arquivo de Sistema + Criar uma nova pasta UBFeaturesController - - ImportedImage - Imagem Importada - Audios - Áudios + Áudios Movies - Vídeos + Vídeos Pictures - Imagens + Imagens Animations - Animações + Flash Interactivities - Atividades Interativas + Interagir Applications - Aplicações + Utilidades Shapes - Formas + Formas Favorites - Favoritos + Favoritos Web search - Busca na web + Busca Web Trash - Lixo + Lixeira + + + ImportedImage + Imagem Importada UBFeaturesNewFolderDialog Accept - Aceitar + Aceitar Cancel - Cancelar + Cancelar Enter a new folder name - Escrever um novo nome para o diretório + Dê um novo nome para o diretório UBFeaturesProgressInfo Loading - A carregar + A carregar UBGraphicsGroupContainerItemDelegate Locked - Bloqueado + Bloqueado Visible on Extended Screen - Visível em Vídeo Expandido + Visível na Tela Estendida UBGraphicsItemDelegate Locked - Bloqueado + Bloqueado Visible on Extended Screen - Visível em Vídeo Expandido + Visível na Tela Estendida + + + Set as background + Definir como fundo Go to Content Source - Ir para a Fonte do Conteúdo + Ir para a Fonte do Conteúdo UBGraphicsMediaItem Media resource couldn't be resolved - Recurso de mídia não pode ser resolvido + Recurso de mídia não pôde ser resolvido Unsupported media format - Formato de mídia não suportado + Formato de mídia não suportado Media playback service not found - Serviço de reprodução de mídia não encontrado + Serviço de reprodução de mídia não encontrado Media error: - Erro de mídia: + Erro na mídia: UBGraphicsTextItem <Type Text Here> - <Introduzir o Texto Aqui> + <Digite o Texto Aqui> UBGraphicsTextItemDelegate Text Color - Cor do Texto + Cor do Texto Editable - Editável + Editável UBGraphicsW3CWidgetItem Web - Web + Web UBGraphicsWidgetItem Loading ... - Carregando ... + Carregando ... UBGraphicsWidgetItemDelegate Frozen - Congelado + Congelado Transform as Tool - Transformar em Ferramenta + Transformar em Ferramenta UBImportCFF Common File Format ( - Formato de Arquivo Frequente ( + Formato de Arquivo Frequente ( Importing file %1... - Importando arquivos %1... + Importando arquivos %1... Import of file %1 failed. - Falha a importar arquivo %1. + Falha a importar arquivo %1. Import successful. - Importação bem sucedida. + Importado com sucesso.. Import failed. - Falha na importação. + Falha na importação. UBImportDocument + + OpenBoard (*.ubz) + OpenBoard (*.ubz) + Importing file %1... - Importando arquivo %1... + Importando arquivo %1... Import of file %1 failed. - Falha a importar arquivo %1. + Falha a importar arquivo %1. Import successful. - Importação bem sucedida. + Importado com sucesso. + + + UBImportDocumentSetAdaptor - OpenBoard (*.ubz) - OpenBoard (*.ubz) + Openboard (set of documents) (*.ubx) + Openboard (conjunto de documentos) (*.ubx) UBImportImage Image Format ( - Formato da Imagem ( + Formato da Imagem ( UBImportPDF Portable Document Format (*.pdf) - Portable Document Format (*.pdf) + Portable Document Format (*.pdf) PDF import failed. - Falha na importação de PDF. + Falha na importação de PDF. Importing page %1 of %2 - Importando página %1 de %2 + Importando página %1 de %2 UBIntranetPodcastPublisher Error while publishing video to intranet (%1) - Erro durante a publicação do vídeo na intranet (%1) + Erro durante a publicação do vídeo na intranet (%1) Publishing to Intranet in progress %1 % - Publicação na Intranet em progresso %1 % + Publicação na Intranet em progresso %1 % UBIntranetPodcastPublishingDialog Publish - Publicar + Publicar UBKeyboardPalette Enter - Enter + Enter UBMainWindow Yes - Sim + Sim No - Não + Não Ok - Ok + OK UBMessagesDialog Close - Fechar + Fechar UBNetworkAccessManager <qt>Enter username and password for "%1" at %2</qt> - <qt>Introduzir o Nome de Usuário e Senha "%1" em %2</qt> + <qt>Introduzir o Nome de Usuário e Senha "%1" em %2</qt> Failed to log to Proxy - Falha ao entrar no Proxy + Falha ao entrar no Proxy SSL Errors: @@ -1720,641 +1807,799 @@ %2 Do you want to ignore these errors for this host? - Erros SSL: + Erros SSL: %1 %2 -Quer ignorar estes erros, deste servidor? +Quer ignorar estes erros deste servidor? Yes - Sim + Sim No - Não + Não UBOpenSankoreImporterWidget Open-Sankore Documents Detected - Documentos do Open-Sankore Detectado + Documentos do Open-Sankore Detectados + + + Open-Sankoré documents are present on your computer. It is possible to import them to OpenBoard by pressing the “Proceed” button to launch the importer application. + Há documentos do Open-Sankoré em seu computador. É possível importá-los para o OpenBoard pressionando o botão "Prosseguir" para lançar o recurso de importação. Show this panel next time - Mostrar este painel da próxima vez + Mostrar este painel na próxima vez You can always access the OpenBoard Document Importer through the Preferences panel in the About tab. Warning, if you have already imported your Open-Sankore datas, you might loose your current OpenBoard documents. - Você sempre poderá acessar o Importador de Documentos OpenBoard através do painel de Preferências na aba Sobre. Aviso, se você já importou seus dados do Open-Sankoré, você poderá perder seus atuais documentos do OpenBoard. + Você sempre poderá acessar o Importador de Documentos OpenBoard através do painel de Preferências na aba Sobre. Aviso, se você já importou seus dados do Open-Sankoré, você poderá perder seus documentos atuais do OpenBoard. Cancel - Cancelar + Cancelar Proceed - Prosseguir - - - Open-Sankoré documents are present on your computer. It is possible to import them to OpenBoard by pressing the “Proceed” button to launch the importer application. - Documentos do Open-Sankoré estão presentes em seu computador. É possível importá-los para o OpenBoard pressionando o botão "Prosseguir" para lançar a aplicação de importação. + Prosseguir UBPersistenceManager (copy) - (cópia) + (cópia) Document Repository Loss - Repositório de Documentos perdido + Repositório de Documentos perdido - has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. - Acesso ao repositório '%1' foi perdido. Infelizmente a aplicação deverá desligar para avitar a corrupção de dados. As últimas alterações também serão perdidas. + OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. + Acesso ao repositório '%1' foi perdido. Infelizmente, deverá encerrar a aplicação para evitar a corrupção de dados. As últimas alterações também serão perdidas. UBPlatformUtils English - Inglês + Inglês Russian - Russo + Russo German - Alemão + Alemão French - Francês + Francês Swiss French - Francês (Suiço) + Francês (Suíço) UBPodcastController + + OpenBoard Cast + OpenBoard Cast + Failed to start encoder ... - Falha ao iniciar o codificador ... + Falha ao iniciar o codificador... No Podcast encoder available ... - Nenhum codificador de Podcast disponível ... + Nenhum codificador de Podcast disponível ... Part %1 - Parte %1 + Parte %1 on your desktop ... - no seu ambiente de trabalho ... + no seu ambiente de trabalho ... in folder %1 - na pasta %1 + na pasta %1 Podcast created %1 - Podcast criado %1 + Podcast criado %1 Podcast recording error (%1) - Erro de gravação do Podcast (%1) + Erro ao gravar Podcast (%1) Default Audio Input - Entrada de áudio padrão + Entrada de áudio padrão No Audio Recording - Sem gravação de áudio + Sem gravação de áudio Small - Pequeno + Pequena Medium - Médio + Média Full - Completo + Cheia Publish to Intranet - Publicar na Intranet + Publicar na Intranet Publish to Youtube - Publicar no Youtube - - - OpenBoard Cast - OpenBoard Cast + Publicar no Youtube UBPreferencesController version: - versão: + versão: Marker is pressure sensitive - O marcador é sensível à pressão + Marca-texto sensível à pressão - - - UBProxyLoginDlg - Proxy Login - Iniciar sessão no Proxy + Key sequence already in use + Combinação de teclas já em uso - Username: - Nome de Utilizador: + Mouse button already in use + Botão do mouse já em uso - Password: - Senha: + Stylus button already in use + Botão de caneta já em uso + + + Accept + preferencesDialog + Aceitar + + + Record + preferencesDialog + Gravar - UBPublicationDlg + UBSettings - Publish document on the web - Publicar documentos na web + My Movies + Meus Vídeos + + + UBShortcutManager - Title: - Título: + Common + Frequente - Description: - Descrição: + Board + Quadro - Publish - Publicar + Stylus Palette + Paleta - - - UBSettings - My Movies - Os meus filmes + Lines and colours + Linhas e cores + + + Background + Fundo + + + Podcast + Podcast + + + First scene + Primeira cena + + + Show first scene + Ver primeira cena + + + Last scene + Última cena + + + Show last scene + Ver última cena + + + Zoom reset + Zoom 1:1 + + + Reset zoom factor + Redefinir fator de zoom + + + Scroll left + Rolar para a esquerda + + + Scroll page left + Rolar página para a esquerda + + + Scroll right + Rolar para a direita + + + Scroll page right + Rolar página para a direita + + + Scroll up + Rolar para cima + + + Scroll page up + Rolar página para cima + + + Scroll down + Rolar para baixo + + + Scroll page down + Rolar página para baixo + + + Built-in (not editable) + Integrado (não editável) + + + Command + Comando + + + Description + Descrição + + + Key Sequence + Sequência de Teclas + + + Mouse Button + Botão do Mouse + + + Tablet Button + Botão da Mesa Digitalizadora + + + Left + MouseButton + Esquerdo + + + Right + MouseButton + Direito + + + Middle + MouseButton + Meio + + + Back + MouseButton + Voltar + + + Forward + MouseButton + Avançar + + + Task + MouseButton + Tarefa + + + Extra + MouseButton + Extra UBStartupHintsPalette Visible next time - Visível na próxima vez + Visível na próxima vez + + + + UBTeacherBarWidget + + + UBThumbnailAdaptor Generating preview thumbnails ... - A gerar pré-visualização de miniaturas ... + Criando miniaturas de visualização... %1 thumbnails generated ... - %1 de miniaturas geradas ... + %1 miniaturas criadas... + + + + UBThumbnailTextItem + + Page %0 + Página %0 UBToolsManager Mask - Cortina + Cortina Ruler - Régua + Régua Compass - Compasso + Compasso Protractor - Transferidor + Transferidor Triangle - Esquadro + Esquadro Magnifier - Lupa + Lupa Cache - Cache + Cache + + + Axes + Eixos UBTrapFlashController Whole page - Página completa + Página completa Web - Web + Web UBUpdateDlg Document updater - Atualizador de documento + Atualizador de documento files require an update. - arquivos requerem uma atualização. + arquivos requerem uma atualização. Backup path: - Localização da Cópia de Segurança: + Localização da Cópia de Segurança: Browse - Procurar + Procurar Update - Atualizar + Atualizar + + + Remind me later + Lembrar-me depois Select a backup folder - Selecione uma pasta para a Cópia de Segurança + Selecione uma pasta para cópia de segurança Please wait the import process will start soon... - Aguarde. O processo de importação vai começar em breve... + Aguarde. O processo de importação iniciará em breve... Files update successful! Please reboot the application to access the updated documents. - Atualização de arquivos bem sucedida! + Atualização de arquivos bem sucedida! Por favor, reinicie o aplicativo para ir aos documentos atualizados. An error occured during the update. The files have not been affected. - Ocorreu um erro durante a atualização. Os arquivos não foram afetados. + Ocorreu um erro durante a atualização. Os arquivos não foram afetados. Files update results - Resultados da atualização de arquivos + Resultados da atualização de arquivos Updating file - Atualizando o arquivo - - - Remind me later - Lembrar-me depois + Atualizando o arquivo UBWebPluginWidget Loading... - Carregando... + Carregando... UBWidgetUniboardAPI %0 called (method=%1, status=%2) - %0 pedido (método=%1, estado=%2) + %0 pedido (método=%1, estado=%2) UBYouTubePublisher YouTube authentication failed. - Erro de autenticação no YouTube. + Erro de autenticação no YouTube. Error while uploading video to YouTube (%1) - Erro no envio do vídeo para o YouTube (%1) + Erro ao enviar o vídeo para o YouTube (%1) Upload to YouTube in progress %1 % - Envio para o YouTube em curso %1 % + Envio para o YouTube em curso %1 % UBYouTubePublishingDialog Upload - Enviar + Enviar Autos & Vehicles - Automóveis + Veículos Music - Música + Música Pets & Animals - Animais + Animais Sports - Esportes + Esportes Travel & Events - Viagens e Eventos + Viagens & Eventos Gaming - Jogos + Jogos Comedy - Humor + Comédia People & Blogs - Pessoas e Blogues + Pessoas & Blogues News & Politics - Notícias e Política + Notícias & Política Entertainment - Entretenimento + Entretenimento Education - Educação + Educação Howto & Style - Guias e Estilo + Tutoriais & Estilo Nonprofits & Activism - Sem fins lucrativos e Ativismo + ONGs & Ativismo Science & Technology - Ciência e Tecnologia + Ciência & Tecnologia UBZoomPalette %1 x - %1 x + %1 x WBClearButton Clear - Limpar + Limpar WBDownloadItem Save File - Gravar arquivo + Gravar arquivo Download canceled: %1 - Download cancelado: %1 + Transferência cancelada: %1 Error opening saved file: %1 - Erro a abrir o arquivo gravado: %1 + Erro ao abrir o arquivo gravado: %1 Error saving: %1 - Erro ao gravar: %1 + Erro ao gravar: %1 Network Error: %1 - Erro na Ligação: %1 + Erro na Ligação: %1 seconds - Segundos + segundos minutes - Minutos + minutos - %4 %5 remaining - - %4 %5 de tempo restante + - %4 %5 de tempo restante %1 of %2 (%3/sec) %4 - %1 de %2 (%3/seg) %4 + %1 de %2 (%3/s) %4 ? unknown file size Tamanho do arquivo desconhecido - ? + ? %1 of %2 - Stopped - %1 de %2 - Parado + %1 de %2 - Parado bytes - bytes + bytes KB - KB + KB MB - MB + MB WBDownloadManager 1 Download - 1 arquivo baixado + 1 arquivo transferido %1 Downloads always >= 2 - Sempre >= 2 - %1 arquivos baixados + %1 arquivos transferidos WBHistoryModel Title - Título + Título Address - Endereço + Endereço WBHistoryTreeModel Earlier Today - Hoje + Hoje %1 items - %1 item(s) + %1 itens WBSearchLineEdit Search - Pesquisar + Pesquisar WBTabBar New &Tab - Novo &Separador + Novo &Separador Clone Tab - Duplicar Separador + Duplicar Separador &Close Tab - &Fechar Separador + &Fechar Separador Close &Other Tabs - Fechar os &Outros Separadores + Fechar os &Outros Separadores Reload Tab - Atualizar Separador + Atualizar Separador Reload All Tabs - Atualizar todos os Separadores + Atualizar todos os Separadores WBTabWidget Recently Closed Tabs - Separadores recentemente fechados + Separadores recentemente fechados (Untitled) - (Sem Título) + (Sem Título) WBToolbarSearch Search - Pesquisar + Pesquisar No Recent Searches - Sem pesquisas recentes + Sem pesquisas recentes Recent Searches - Pesquisas recentes + Pesquisas recentes Clear Recent Searches - Limpar as pesquisas recentes + Limpar as pesquisas recentes WBWebPage + + Download PDF Document: would you prefer to download the PDF file or add it to the current OpenBoard document? + Download de Documento PDF: você prefere transferir o arquivo PDF ou adicioná-lo ao documento OpenBoard atual? + Download - Baixar + Transferir Add to Current Document - Adicionar ao documento atual + Adicionar ao documento atual PDF - PDF + PDF Error loading page: %1 - Erro ao carregar a página: %1 - - - Download PDF Document: would you prefer to download the PDF file or add it to the current OpenBoard document? - Download de Documento PDF: você prefere baixar o arquivo PDF ou adicioná-lo ao atual documento OpenBoard? + Erro ao carregar a página: %1 WBWebView Open in New Tab - Abrir num Novo Separador + Abrir num Novo Separador + + + + XPDFRenderer + + Processing... + Processando... YouTubePublishingDialog Publish Podcast to YouTube - Publicar Podcast no Youtube + Publicar Podcast no YouTube Title - Título + Título Description - Descrição + Descrição Keywords - Palavras chave + Tags (Palavras-Chave) + + + OpenBoard + OpenBoard Category - Categoria + Categoria YouTube Username - Nome de utilizador do Youtube + Nome de Usuário Youtube YouTube Password - Senha do Youtube + Senha no Youtube <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> @@ -2362,264 +2607,362 @@ Por favor, reinicie o aplicativo para ir aos documentos atualizados. - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<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:'Lucida Grande'; font-size:10pt;">Clicando em 'Enviar', você certifica que possui todos os direitos sobre o conteúdo ou que está autorizado pelo proprietário a tornar o conteúdo disponível publicamente no YouTube, e que esta de acordo com os Termos de Serviço do YouTube localizado em</span><a href="http://www.youtube.com/t/terms"><span style=" font-family:'Lucida Grande'; font-size:10pt; text-decoration: underline; color:#0000ff;">http://www.youtube.com/t/terms</span></a></p></body></html> +<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:'Lucida Grande'; font-size:10pt;">Clicando em 'Enviar', você certifica que possui todos os direitos sobre o conteúdo ou que está autorizado pelo proprietário a tornar o conteúdo disponível publicamente no YouTube, e que està de acordo com os Termos de Serviço do YouTube localizado em </span><a href="http://www.youtube.com/t/terms"><span style=" font-family:'Lucida Grande'; font-size:10pt; text-decoration: underline; color:#0000ff;">http://www.youtube.com/t/terms</span></a></p></body></html> Restore credentials on reboot - Reestabelecer credenciais ao reiniciar - - - OpenBoard - OpenBoard + Reestabelecer credenciais ao reiniciar brushProperties - On Light Background - Num fundo branco + Pen is Pressure Sensitive + Caneta sensível à pressão - On Dark Background - Num fundo preto + Opacity + Opacidade - Opacity - Opacidade + On Light Background + Em fundo claro + + + On Dark Background + Em fundo escuro Line Width - Largura da linha + Largura da Ponta Medium - Médio + Média Strong - Largo + Grossa Fine - Fino + Fina - Pen is Pressure Sensitive - Caneta sensível à pressão + Show preview circle from + Ver cruz a partir de + + + px + px capturePublishingDialog Dialog - Caixa de Diálogo + Caixa de Diálogo Title - Título + Título E-mail - E-mail + E-mail Author - Autor + Autor Description - Descrição + Descrição documents OpenBoard Documents - Documentos OpenBoard + Documentos OpenBoard + + + Creation date + Data de criação + + + Update date + Data de modificação + + + Alphabetical order + Ordem alfabética + + + Sort Order + Inverter Ordem preferencesDialog Preferences - Preferências - - - version : … - versão : ... + Preferências Default Settings - Configuração por defeito + Configurações Padrão Close - Fechar + Fechar Display - Exibir + Exibir - Internet - Internet - - - Show Page with External Browser - Mostrar a página num Browser externo + Multi display + Telas múltiplas - Home Page: - Página de entrada: + Swap control display and view display + Alternar entre telas de controle e de apresentação - Virtual Keyboard - Teclado virtual + Show internal web page content on secondary screen or projector + Mostrar o conteúdo interno da página web numa tela ou projetor secundário - Keyboard button size: - Tamanho dos botões do teclado: + Swap first and second view displays + Trocar as telas Toolbar - Barra de ferramentas + Barra de ferramentas Positioned at the Top (recommended for tablets) - Posicionado no topo (recomendado para tablets) + Posicionado no topo (recomendado para tablets ou mesas digitalizadoras) Positioned at the Bottom (recommended for white boards) - Posicionado em baixo (recomendado para quadros brancos) + Posicionado em baixo (recomendado para quadros brancos) Display Text Under Button - Mostrar o texto por debaixo dos botões + Mostrar legenda abaixo dos botões Stylus Palette - Paleta + Paleta Horizontal - Horizontal + Horizontal Vertical - Vertical + Vertical + + + Mode + Modo + + + Mode to start in: + Iniciar como + + + Board + Quadro + + + Desktop + Área de Trabalho + + + Virtual Keyboard + Teclado Virtual + + + Built-in virtual keyboard button size: + Tamanho das teclas do teclado virtual embarcado: + + + Use system keyboard (recommended) + Usar teclado do sistema (recomendado) + + + Grid + Grade + + + On Light Background + Em fundo claro + + + Opacity + Opacidade + + + On Dark Background + Em fundo escuro Pen - Caneta + Caneta Marker - Marcador + Marca-Texto + + + Network + Rede + + + Internet + Internet + + + Show Page with External Browser + Mostrar a página em navegador externo + + + Home Page: + Página Inicial: + + + Proxy User: + Usuário Proxy: + + + Pass: + Senha: Licences - Licenças + Licenças + + + Credits + Créditos About - Sobre + Sobre Software Update - Atualização de Software + Atualização de Software Check software update at launch - Verificar atualizações de software no arranque + Verificar atualizações ao iniciar - Network - Rede + Open-Sankoré Importer + Importador Open-Sankoré - Multi display - Múltiplos Monitores + Check if Open-Sankoré data could be imported at launch + Verificar se os dados do Open-Sankoré poderão ser importados ao inicializar - Show internal web page content on secondary screen or projector - Mostrar o conteúdo interno da página web num monitor ou projetor secundário + version : … + versão : … - Swap control display and view display - Trocar entre o monitor de controle e apresentação? - Trocar entre o monitor de controle e visualização + Documents Mode + Modo dos Documentos - Mode - Modo + Display date column on alphabetical sort + Mostrar coluna de data em ordem alfabética - Mode to start in: - "Mode to start in:" - Modo iniciar: + Empty trash for documents older than + Esvaziar lixeira para documentos com mais de - Board - Quadro + days + dias - Desktop - Área de Trabalho + PDF Rendering + Renderização de PDF - Proxy User: - Proxy User: + Shortcut + Atalho - Pass: - Senha: + Filter + Filtro - Credits - Créditos + Active keyboard shortcuts without pressing Ctrl key + Ativar atalhos de teclado sem pressionar a tecla Ctrl - Open-Sankoré Importer - Importador Open-Sankoré + Shortcuts + Atalhos - Check if Open-Sankoré data could be imported at launch - Verifique se os dados do Open-Sankoré poderão ser importados na inicialização + Abort + Abortar - Use system keyboard (recommended) - Usar teclado do sistema (recomendado) + Record + Gravar - Built-in virtual keyboard button size: - Tamanho da tecla do teclado virtual embarcado: + Stylus Button + Botão da Caneta + + + Mouse Button + Botão do Mouse + + + Reset + Reiniciar + + + Key Sequence + Sequência de Teclas + + + Improve zoom execution time (can slightly affect rendering quality) + Melhorar o tempo de execução do zoom (pode afetar ligeiramente a qualidade da renderização) trapFlashDialog Trap flash - Capturar flash + Capturar flash Select a flash to trap - Selecionar o flash a capturar + Selecionar o flash a capturar about:blank - Acerca:em branco + Sobre:em branco Application name - Nome da Aplicação + Nome da Aplicação Create Application - Criar Aplicação + Criar Aplicação From cb5d2dba18ad63d245043d8f36e2f7642a52b69a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Fri, 24 Sep 2021 13:46:03 +0200 Subject: [PATCH 019/130] fixed an issue where toggling right palette would put the tools and area in an inconsistent state --- src/desktop/UBDesktopAnnotationController.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/desktop/UBDesktopAnnotationController.cpp b/src/desktop/UBDesktopAnnotationController.cpp index 564ca812..138d0fb4 100644 --- a/src/desktop/UBDesktopAnnotationController.cpp +++ b/src/desktop/UBDesktopAnnotationController.cpp @@ -129,6 +129,7 @@ UBDesktopAnnotationController::UBDesktopAnnotationController(QObject *parent, UB connect(mDesktopPalette, SIGNAL(minimizeStart(eMinimizedLocation)), this, SLOT(onDesktopPaletteMinimize())); connect(mDesktopPalette, SIGNAL(mouseEntered()), mTransparentDrawingScene, SLOT(hideTool())); connect(mRightPalette, SIGNAL(mouseEntered()), mTransparentDrawingScene, SLOT(hideTool())); + connect(mRightPalette, SIGNAL(pageSelectionChangedRequired()), this, SLOT(updateBackground())); connect(mTransparentDrawingView, SIGNAL(resized(QResizeEvent*)), this, SLOT(onTransparentWidgetResized())); From a76d0d85c1f5b6c79087d624faef6bd507807911 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Fri, 24 Sep 2021 14:46:32 +0200 Subject: [PATCH 020/130] fixed an issue where eraseBackground then undo would make a PDF being partly attached as a background and behave as an object --- src/domain/UBGraphicsItemUndoCommand.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/domain/UBGraphicsItemUndoCommand.cpp b/src/domain/UBGraphicsItemUndoCommand.cpp index 7653a054..4787aed5 100644 --- a/src/domain/UBGraphicsItemUndoCommand.cpp +++ b/src/domain/UBGraphicsItemUndoCommand.cpp @@ -128,7 +128,7 @@ void UBGraphicsItemUndoCommand::undo() QGraphicsItem* item = itRemoved.next(); if (item) { - if (UBItemLayerType::FixedBackground == item->data(UBGraphicsItemData::ItemLayerType)) + if (itemLayerType::BackgroundItem == item->data(UBGraphicsItemData::itemLayerType)) mScene->setAsBackgroundObject(item); else mScene->addItem(item); From ba1bef4ab3a24e6d6df3a7395f146cc501c9bd9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Fri, 24 Sep 2021 14:47:29 +0200 Subject: [PATCH 021/130] (should not happen but) prevent segfault if itemDelegate is null --- src/domain/UBGraphicsScene.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/domain/UBGraphicsScene.cpp b/src/domain/UBGraphicsScene.cpp index 327b43c2..5b5c59e8 100644 --- a/src/domain/UBGraphicsScene.cpp +++ b/src/domain/UBGraphicsScene.cpp @@ -1335,9 +1335,12 @@ void UBGraphicsScene::updateSelectionFrame() mSelectionFrame->setEnclosedItems(QList()); UBGraphicsItemDelegate *itemDelegate = UBGraphicsItem::Delegate(selItems.first()); - itemDelegate->createControls(); - selItems.first()->setVisible(true); - itemDelegate->showControls(); + if (itemDelegate) + { + itemDelegate->createControls(); + selItems.first()->setVisible(true); + itemDelegate->showControls(); + } } break; default: { From aa558673e4bbbd93bbbe4ab7e2f49eb872d4105a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Fri, 24 Sep 2021 15:47:33 +0200 Subject: [PATCH 022/130] pass arguments through run.sh script so ubz files can be double clicked again --- resources/linux/run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/linux/run.sh b/resources/linux/run.sh index a179fb4b..c3b1fe85 100644 --- a/resources/linux/run.sh +++ b/resources/linux/run.sh @@ -25,4 +25,4 @@ DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" # Add custom libraries to LD_LIBRARY_PATH # TODO: Remomve the need for this -env LD_LIBRARY_PATH=$DIR/qtlib:$LD_LIBRARY_PATH QT_PLUGIN_PATH=$DIR/plugins $DIR/OpenBoard +env LD_LIBRARY_PATH=$DIR/qtlib:$LD_LIBRARY_PATH QT_PLUGIN_PATH=$DIR/plugins $DIR/OpenBoard "$@" From 4c95514a1ad8ac047b5bb0ce03b63a929c2e9449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Mon, 27 Sep 2021 08:59:14 +0200 Subject: [PATCH 023/130] QGraphicsView::mouseMoveEvent should be called if tool is selector or play but mouse not pressed --- src/board/UBBoardView.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/board/UBBoardView.cpp b/src/board/UBBoardView.cpp index 0e25b8b0..ccef2d98 100644 --- a/src/board/UBBoardView.cpp +++ b/src/board/UBBoardView.cpp @@ -848,6 +848,13 @@ void UBBoardView::handleItemMouseMove(QMouseEvent *event) QGraphicsView::mouseMoveEvent (event); posAfterMove = getMovingItem()->pos(); } + else + { + if (!mMouseButtonIsPressed) + { + QGraphicsView::mouseMoveEvent(event); + } + } mWidgetMoved = ((posAfterMove-posBeforeMove).manhattanLength() != 0); @@ -1134,9 +1141,6 @@ void UBBoardView::mouseMoveEvent (QMouseEvent *event) return; } - if ((UBDrawingController::drawingController()->isDrawingTool()) && !mMouseButtonIsPressed) - QGraphicsView::mouseMoveEvent(event); - int currentTool = static_cast(UBDrawingController::drawingController()->stylusTool()); switch (currentTool) { From 3e64122a17e279adbad948711c1832e56f9b9677 Mon Sep 17 00:00:00 2001 From: kaamui Date: Mon, 27 Sep 2021 10:21:05 +0200 Subject: [PATCH 024/130] Update README.md --- README.md | 32 ++++---------------------------- 1 file changed, 4 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 35623702..d90a97d3 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,14 @@ # OpenBoard OpenBoard is an open source cross-platform interactive white board application designed primarily for use in schools. It was originally forked from Open-Sankoré, which was itself based on Uniboard. -Supported platforms are Windows (7+), OS X (10.9+) and Linux (tested on Ubuntu 16.04). - +Supported platforms are Windows (10+), OS X (10.13+) and Linux (Ubuntu 20.04 is the only officially maintained platform, but many others are reported by the community to work). ## Installing -Installers are available for Windows, OS X and Ubuntu on the [wiki](https://github.com/DIP-SEM/OpenBoard/wiki/Downloads). +Installers are available for Windows, OS X and Ubuntu on the [wiki](https://github.com/OpenBoard-org/OpenBoard/wiki/Downloads). ## Building from source -First, obtain the third party libraries from the OpenBoard-ThirdParty repository, and build them (instructions are provided for each library). - -Then, you may use the build (and packaging) scripts which are provided for all three platforms. These take care of compiling OpenBoard, including the translations (for OpenBoard and for Qt), stripping the debug symbols, creating the installers etc. -Minor modification to those scripts may be necessary depending on your configuration, to set the correct Qt path for example. - -Alternatively, you can easily build OpenBoard with qmake and make: - - qmake OpenBoard.pro -spec linux-g++-64 # replace linux-g++-64 by macx or win32 for other platforms - make - -Compilers used are gcc (Linux), clang (OS X) and MSVC 2010 (Windows). Make sure that your version of Qt matches this, as it is not possible e.g to build OpenBoard with clang if Qt was built with gcc. +Please follow instructions available on the [wiki](https://github.com/OpenBoard-org/OpenBoard/wiki/Build-from-source) ## Dependencies -The latest version (1.5) requires Qt 5.5. (While it has been shown to mostly work with Qt 5.2, we cannot guarantee compatibility with Qt versions other than 5.5.) - -### Qt 5.5 on Linux - -Due to a shared library conflict within Qt 5 in some distributions / some Qt versions (the Multimedia and Webkit modules were built against different versions of gstreamer by default), a specific installation of Qt5.5 may be needed for all of OpenBoard's features to work correctly. - -It can either be built from source, with the configure flag `-gstreamer 1.0` (see [here](http://doc.qt.io/qt-5/linux-building.html)), or installed from Stephan Binner's PPAs on Ubuntu. -In the latter case, simply add the repositories and install Qt 5.5.1 like so (example provided for Ubuntu 14.04, aka "Trusty"): - - sudo add-apt-repository ppa:beineri/opt-qt551-trusty - sudo apt-get update - sudo apt-get install qt-latest - -Some distributions, such as Ubuntu 16.04, provide Qt 5.5.1 packages that work perfectly with OpenBoard, so you can simply install Qt from the official repository. +The latest version (1.6.1) can be compiled with the latest open source binaries of Qt 5.15. While it has been shown to work with Qt 5.9 or any other version in between, we recommend that you use the latest version you can. From 8f33423ec3d00b764408dd6850e2398298d29f55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Tue, 28 Sep 2021 10:05:32 +0200 Subject: [PATCH 025/130] fixed http/https behavior of webbrowser.wgt + explain user when iframe is not possible --- .../applications/WebBrowser.wgt/index.html | 56 +++++++++++++++---- .../WebBrowser.wgt/scripts/languages.js | 16 ++++-- 2 files changed, 56 insertions(+), 16 deletions(-) diff --git a/resources/library/applications/WebBrowser.wgt/index.html b/resources/library/applications/WebBrowser.wgt/index.html index a1ca93a7..8c77b1fe 100644 --- a/resources/library/applications/WebBrowser.wgt/index.html +++ b/resources/library/applications/WebBrowser.wgt/index.html @@ -138,12 +138,41 @@ if($("#textbox").val().length > 0){ loadingState = false; var url = $("#textbox").val(); - var urlStart = url.split("://"); - - if(urlStart[0]!="http"){ - url = "http://" + url; - }; - + if(!url.startsWith("http")) + { + url = "https://" + url; + $("#textbox").val(url); + } + + //non-exhaustive. add new names here as it is requested + var x_frame_options_secured_sites = [ + "google.", + "youtube.", + "stackoverflow.", + "facebook.", + "github.", + "twitter.", + "amazon.", + "linkedin.", + "ebay." + ]; + + var iframe_denied = x_frame_options_secured_sites.some(function(element, indice, array) + { + return url.includes(element); + }); + + if (iframe_denied) + { + $("#container").addClass("error"); + $("#web-content").hide(); + $("#container-shadow").hide(); + $("#content").hide(); + $("#arrow").hide(); + $("#notifications").html(sankoreLang[lang].error_xframe_options); + return false; + } + if(checkURLs(references, url)){ if(currentHistory == references.length) references[currentHistory++] = url; @@ -158,7 +187,8 @@ $("#arrow").hide(); $("#embeded-content").hide(); $("#web-content").hide(); - $('#web-content').attr('src',url); + $('#web-content').contents().find("body").html(""); + $('#web-content').attr('src', url); checkcontent(); checkLoading(); @@ -225,11 +255,12 @@ }; if(resizerIndex > 80){ resizerIndex = 0; - console.log("error on loading page"); - $("#back-button").trigger("click"); - $("#textbox").val($("#textbox").val().replace("http://", "")); - $("#textbox").val("http://www.metacrawler.com/search/web?&q=" + $("#textbox").val().replace("http://", "")+"&ql="); - $("#search-button").trigger("click"); + $("#container").addClass("error"); + $("#web-content").hide(); + $("#container-shadow").hide(); + $("#content").hide(); + $("#arrow").hide(); + $("#notifications").html(sankoreLang[lang].error_loading_page); }else{ resizer = setTimeout(function(){checkLoading()}, 100); resizerIndex++; @@ -280,6 +311,7 @@
+
arrow-top
diff --git a/resources/library/applications/WebBrowser.wgt/scripts/languages.js b/resources/library/applications/WebBrowser.wgt/scripts/languages.js index 396dca88..6411fbee 100644 --- a/resources/library/applications/WebBrowser.wgt/scripts/languages.js +++ b/resources/library/applications/WebBrowser.wgt/scripts/languages.js @@ -6,7 +6,9 @@ "prev_page":"Previous page", "next_page":"Next page", "open":"Open the site", - "alert":"Cannot open a page! Maybe it's because of a security policy or a wrong url. Also check your internet connection." + "alert":"Cannot open a page! Maybe it's because of a security policy or a wrong url. Also check your internet connection.", + "error_loading_page": "An error has occured during page's loading", + "error_xframe_options" : "This site does not allow its content to be embed from another domain" }, "ru":{ "previous":"Пред.", @@ -15,7 +17,9 @@ "prev_page":"Пред. страница", "next_page":"След. страница", "open":"Перейти", - "alert":"Невозможно отобразить страницу! Возможно это из-за политики безопасности сайта или неверного адреса.Также стоит проверить подключение к интернету." + "alert":"Невозможно отобразить страницу! Возможно это из-за политики безопасности сайта или неверного адреса.Также стоит проверить подключение к интернету.", + "error_loading_page": "An error has occured during page's loading", + "error_xframe_options" : "This site does not allow its content to be embed from another domain" }, "fr":{ "previous":"Précédente", @@ -24,7 +28,9 @@ "prev_page":"Page précédente", "next_page":"Page suivante", "open":"Ouvrez le site", - "alert":"Impossible d'ouvrir une page! Peut-être c'est à cause d'une politique de sécurité ou une URL erronée. Vérifiez aussi votre connexion internet." + "alert":"Impossible d'ouvrir une page! Peut-être c'est à cause d'une politique de sécurité ou une URL erronée. Vérifiez aussi votre connexion internet.", + "error_loading_page": "Une erreur est survenue durant le chargement de la page", + "error_xframe_options" : "Ce site n'autorise pas l'intégration de son contenu à partir d'un autre domaine" }, "sk":{ "previous":"Predošlá", @@ -33,7 +39,9 @@ "prev_page":"Predošlá stránka", "next_page":"Ďalšia stránka", "open":"Otvoriť stránku", - "alert":"Stránka sa nedá otvoriť! Možno je to kvôli spôsobu zabezpečenia alebo nesprávnej internetovej adrese. Skontrolujte aj svoje internetové pripojenie." + "alert":"Stránka sa nedá otvoriť! Možno je to kvôli spôsobu zabezpečenia alebo nesprávnej internetovej adrese. Skontrolujte aj svoje internetové pripojenie.", + "error_loading_page": "An error has occured during page's loading", + "error_xframe_options" : "This site does not allow its content to be embed from another domain" } }; From 0f9196f5e51215f2a06e78b575a33f66d8213cbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Tue, 28 Sep 2021 11:43:59 +0200 Subject: [PATCH 026/130] remove bookmark settings (no longer used) --- resources/etc/OpenBoard.config | 3 --- src/core/UBSettings.cpp | 3 --- src/core/UBSettings.h | 3 --- src/web/UBWebController.cpp | 4 ---- src/web/browser/WBBrowserWindow.cpp | 15 --------------- src/web/browser/WBBrowserWindow.h | 3 --- 6 files changed, 31 deletions(-) diff --git a/resources/etc/OpenBoard.config b/resources/etc/OpenBoard.config index 5d29fb4b..463ece5b 100644 --- a/resources/etc/OpenBoard.config +++ b/resources/etc/OpenBoard.config @@ -147,11 +147,8 @@ ReplyPlusMaxKeypads=100 ReplyWWSerialPort=3 [Web] -AddBookmarkURL="http://www.myuniboard.com/bookmarks/save/?url=" -BookmarksPage=http://www.myuniboard.com HistoryLimit=15 Homepage=http://www.openboard.ch -ShowAddBookmarkButton=true ShowPageImediatelyOnMirroredScreen=false UseExternalBrowser=false diff --git a/src/core/UBSettings.cpp b/src/core/UBSettings.cpp index e9620107..f659452f 100644 --- a/src/core/UBSettings.cpp +++ b/src/core/UBSettings.cpp @@ -361,9 +361,6 @@ void UBSettings::init() webShowPageImmediatelyOnMirroredScreen = new UBSetting(this, "Web", "ShowPageImediatelyOnMirroredScreen", defaultShowPageImmediatelyOnMirroredScreen); webHomePage = new UBSetting(this, "Web", "Homepage", softwareHomeUrl); - webBookmarksPage = new UBSetting(this, "Web", "BookmarksPage", "http://www.myuniboard.com"); - webAddBookmarkUrl = new UBSetting(this, "Web", "AddBookmarkURL", "http://www.myuniboard.com/bookmarks/save/?url="); - webShowAddBookmarkButton = new UBSetting(this, "Web", "ShowAddBookmarkButton", false); pageCacheSize = new UBSetting(this, "App", "PageCacheSize", 20); diff --git a/src/core/UBSettings.h b/src/core/UBSettings.h index 31575e43..17d4ae79 100644 --- a/src/core/UBSettings.h +++ b/src/core/UBSettings.h @@ -329,9 +329,6 @@ class UBSettings : public QObject UBSetting* webShowPageImmediatelyOnMirroredScreen; UBSetting* webHomePage; - UBSetting* webBookmarksPage; - UBSetting* webAddBookmarkUrl; - UBSetting* webShowAddBookmarkButton; UBSetting* pageCacheSize; diff --git a/src/web/UBWebController.cpp b/src/web/UBWebController.cpp index f47bac80..c8d53216 100644 --- a/src/web/UBWebController.cpp +++ b/src/web/UBWebController.cpp @@ -141,10 +141,6 @@ void UBWebController::webBrowserInstance() UBApplication::app()->insertSpaceToToolbarBeforeAction(mMainWindow->webToolBar, mMainWindow->actionBoard, 32); UBApplication::app()->decorateActionMenu(mMainWindow->actionMenu); - bool showAddBookmarkButtons = UBSettings::settings()->webShowAddBookmarkButton->get().toBool(); - mMainWindow->actionBookmarks->setVisible(showAddBookmarkButtons); - mMainWindow->actionAddBookmark->setVisible(showAddBookmarkButtons); - showTabAtTop(UBSettings::settings()->appToolBarPositionedAtTop->get().toBool()); adaptToolBar(); diff --git a/src/web/browser/WBBrowserWindow.cpp b/src/web/browser/WBBrowserWindow.cpp index 6b7ca6b9..24f780ff 100644 --- a/src/web/browser/WBBrowserWindow.cpp +++ b/src/web/browser/WBBrowserWindow.cpp @@ -248,9 +248,6 @@ void WBBrowserWindow::setupToolBar() mWebToolBar->insertSeparator(mUniboardMainWindow->actionWebBigger); connect(mUniboardMainWindow->actionHome, SIGNAL(triggered()), this , SLOT(slotHome())); - - connect(mUniboardMainWindow->actionBookmarks, SIGNAL(triggered()), this , SLOT(bookmarks())); - connect(mUniboardMainWindow->actionAddBookmark, SIGNAL(triggered()), this , SLOT(addBookmark())); connect(mUniboardMainWindow->actionWebBigger, SIGNAL(triggered()), this , SLOT(slotViewZoomIn())); connect(mUniboardMainWindow->actionWebSmaller, SIGNAL(triggered()), this , SLOT(slotViewZoomOut())); @@ -514,18 +511,6 @@ void WBBrowserWindow::tabCurrentChanged(int index) } -void WBBrowserWindow::bookmarks() -{ - loadPage(UBSettings::settings()->webBookmarksPage->get().toString()); -} - - -void WBBrowserWindow::addBookmark() -{ - loadPage(UBSettings::settings()->webAddBookmarkUrl->get().toString() + currentTabWebView()->url().toString()); -} - - WBWebView* WBBrowserWindow::paintWidget() { return mTabWidget->currentWebView(); diff --git a/src/web/browser/WBBrowserWindow.h b/src/web/browser/WBBrowserWindow.h index 80926e3b..46959099 100644 --- a/src/web/browser/WBBrowserWindow.h +++ b/src/web/browser/WBBrowserWindow.h @@ -121,9 +121,6 @@ class WBBrowserWindow : public QWidget void tabCurrentChanged(int); - void bookmarks(); - void addBookmark(); - void showTabAtTop(bool attop); void aboutToShowBackMenu(); From ee4e2830742c5d316514c6f2bd4366c513d7fb76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Tue, 28 Sep 2021 16:43:58 +0200 Subject: [PATCH 027/130] make trash icons more explicit --- resources/OpenBoard.qrc | 7 +++ resources/images/trash-delete-document.png | Bin 0 -> 9136 bytes resources/images/trash-delete-folder.png | Bin 0 -> 9288 bytes resources/images/trash-document-page.png | Bin 0 -> 6602 bytes resources/images/trash-document.png | Bin 0 -> 7002 bytes resources/images/trash-empty.png | Bin 0 -> 9100 bytes resources/images/trash-folder.png | Bin 0 -> 6682 bytes resources/images/trash-my-documents.png | Bin 0 -> 8491 bytes src/document/UBDocumentController.cpp | 55 ++++++++++++++++++--- src/document/UBDocumentController.h | 1 + 10 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 resources/images/trash-delete-document.png create mode 100644 resources/images/trash-delete-folder.png create mode 100644 resources/images/trash-document-page.png create mode 100644 resources/images/trash-document.png create mode 100644 resources/images/trash-empty.png create mode 100644 resources/images/trash-folder.png create mode 100644 resources/images/trash-my-documents.png diff --git a/resources/OpenBoard.qrc b/resources/OpenBoard.qrc index 480269c1..f7008934 100644 --- a/resources/OpenBoard.qrc +++ b/resources/OpenBoard.qrc @@ -366,5 +366,12 @@ images/desc.png images/toolPalette/axesTool.png images/numbersTool.svg + images/trash-my-documents.png + images/trash-empty.png + images/trash-folder.png + images/trash-delete-document.png + images/trash-delete-folder.png + images/trash-document.png + images/trash-document-page.png diff --git a/resources/images/trash-delete-document.png b/resources/images/trash-delete-document.png new file mode 100644 index 0000000000000000000000000000000000000000..05f9c1e17bf09ec7f05e10bdade8b00e7787d551 GIT binary patch literal 9136 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 literal 0 HcmV?d00001 diff --git a/resources/images/trash-delete-folder.png b/resources/images/trash-delete-folder.png new file mode 100644 index 0000000000000000000000000000000000000000..ae920eaff7b750ce039fca2ba369e907c6c98b37 GIT binary patch literal 9288 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 literal 0 HcmV?d00001 diff --git a/resources/images/trash-document.png b/resources/images/trash-document.png new file mode 100644 index 0000000000000000000000000000000000000000..c3fda5928c8c3346a705bc01a927a0386256c74e GIT binary patch literal 7002 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 literal 0 HcmV?d00001 diff --git a/resources/images/trash-empty.png b/resources/images/trash-empty.png new file mode 100644 index 0000000000000000000000000000000000000000..94447b0761b8e2f28f9b1597ed7ced4b9594912b GIT binary patch literal 9100 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 z zIF1vkZ0%(SHL2Mt;$hcybw`B2Fb$m8CqW2?k37!z?$ae;spK$QuGgAnRk>o<*syE`M$p>hjgVo0M&@QyxJ>WFCGLj*F&Kv$+8S=+itD%5n8^SJb$51 z=BF2GX~`6=>$;=EBcqLll(AU6#mK9>`XX}73Fnr0DMh*~ zjSMB(=_zVny(-H@A&AEl+Hh>3PRp2y$%!$TA8uF?ViJkORJB^2x5BjXuw_LuXLd$} zA+Rl*glXW|HbNN0V{tkL0YG%V(N5neu92=!{JF$2iB_*!sVmS^di7*TdK!%|Xsx=ZAvu0WyMTQVq zrh#AabLcwXmw_11I|RJ9ery;3aapY4Z07zUMDvVM6K17H{;=lj)0qQkZduUifrbSSabX zbZyVJ~AT4nZJA*MHti~)~U0ins=uV0?W3T%4R!RB;3## z*D`%(b^pMAfNXYQb~G)MS}oMyNeF8A)u!0bO0#Fp7->@FjcZNqEk-n@a+xrcjZI34 z=e4GNgb-M!MXgrLEZ3!FNh%C$2Vge*5c9nHYXF69*|Ub9s6P{88Y?dd6%C{?8&oou zo3mC7S(e%G@@H4cG`%BwkLxx|!&s>ho_pT{s&^?+=-Rrl>6;LSm_IZj4D$JW78qm+8wOTb0G(=YFXeK% zILtS*WAQk)71=h?0GrkKN}ygV*8(lVWDe$+-e7~7A(Kc zpcxvgb%khhB$qPkn$Jmm-)}78ssA?jvHtA`!!QVCm|&5pT{El*F|kw}4MxXt@GJf{UM<9QcBY45HvC}B z`L>KXhk_b1W`$tP*~E&24q{P*Pc{A4Z5Z3O&qb$CucS}Q!lunvKHq#iuk_4un^@uE z%f)KZLeBaY%5aVyvl!?%`BlHMD8Akg&x^INpfGEgr2p=^zYo&yv)~fVN(icvDLMPVURDC#-qb!KYp>& zs8ZRsZG2yArn#kObC=Bl%;XoglrI|Q#9S7F+MIBMAzB$x2wkN)JTg+}`DH0)#o@M8 zdunuIa@Lk<@vse}g_T4U_gJYNQ!gtKO(iC#p?$HHv88HZrFyWv87#}9P$>AI7n3a4 z;))St+g29z2O}UVWRB=R-sCUI`tQYPriOzQ8E8r^xr{G*sPRaBfqH#wk@CuhYXATP zGD$>1RNP8Ob1cguH#5`AGU0|gtQax9y}eHZj6}9fGaZd!>F(-U zFu=Vyh*_qIek^O@Ia5&rqdWg5Cj3SSX@_oHEA`&FmN5W25besIIqzKV8s-3 z!%c7aKFo$6=7u;yR~Yp6_3Dt~lF&MF$+lMEZCWPnsg^H1E(r=qCX=L6sRbt_@~Zbl zhb#IV(~2p^5aLL4_+f78+2XLg`0VWLB$-T7EEcgHr;&PHwq@1adhfm~&I=rSY&XNqOPm zfy2>>6;aGZWyeg@*f}vd$^O0j=9QjZ9K13lF8I8ff2l5vUE8^LY;61!!2adJ4RyFE z#N2u39ba->_w++Qewd?&4!2xuOi*)leo-B;lyX>JO}DnRZzJLEsKnnQy zGL1LjqCa6!_5G_Y+j{1qA3uCWXIH0x!}Zs@bA=}mVM;8GgO>pV~#o~icKl_XwRD)kZQ^^y+ z#}_LQdespFjenxJhv+*`hGCR@dsmIGUcI_6lgT*sb=URk1!2Ad#6;icZAMHeWi6;y zU%GJq{By^TACDCZ#ZBlyHxWYaSt<>?6uc^b@lpRjf@phB{k&lsl|(!-(x2(8#N&yM z?w+2M=ee=2ZoO{tA`nx^PG{vzZsx`5>FLtM)KpugQduXZ8ffauesp>2{TE9i zbKy1l?~@%ncHRu!j9$R74sCHR2$pRfb8JT%QNAM#r5}d13P3feZ9u8yB9ERy2b^|A z+rE`Jhbx7Dw?Bo6*lrzqiD>hRkGaQnnETbS)6q74F+%j^!v6#Om+oL>?S_8<0000< KMNUMnLSTX{K-W3| literal 0 HcmV?d00001 diff --git a/resources/images/trash-folder.png b/resources/images/trash-folder.png new file mode 100644 index 0000000000000000000000000000000000000000..9cc89e97f5fa0bcd7e00f9e126648fff4bdcd6da GIT binary patch literal 6682 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 Z{{vE0mBxzEFBkv-002ovPDHLkV1oSH5=Q_4 literal 0 HcmV?d00001 diff --git a/src/document/UBDocumentController.cpp b/src/document/UBDocumentController.cpp index fcde2f26..dffa1be2 100644 --- a/src/document/UBDocumentController.cpp +++ b/src/document/UBDocumentController.cpp @@ -1387,6 +1387,12 @@ void UBDocumentTreeView::hSliderRangeChanged(int min, int max) } } +void UBDocumentTreeView::mousePressEvent(QMouseEvent *event) +{ + QTreeView::mousePressEvent(event); + UBApplication::documentController->updateActions(); +} + void UBDocumentTreeView::dragEnterEvent(QDragEnterEvent *event) { QTreeView::dragEnterEvent(event); @@ -3429,20 +3435,57 @@ void UBDocumentController::updateActions() switch (static_cast(deletionForSelection)) { case MoveToTrash : + if (mSelectionType == Folder) + { + mMainWindow->actionDelete->setIcon(QIcon(":/images/trash-folder.png")); + mMainWindow->actionDelete->setText(tr("Empty")); + } + else if (mSelectionType == Document) + { + mMainWindow->actionDelete->setIcon(QIcon(":/images/trash-document.png")); + mMainWindow->actionDelete->setText(tr("Trash")); + } + else if (mSelectionType == Page) + { + mMainWindow->actionDelete->setIcon(QIcon(":/images/trash-document-page.png")); + mMainWindow->actionDelete->setText(tr("Trash")); + } + else + {//can happen ? + mMainWindow->actionDelete->setIcon(QIcon(":/images/trash.png")); + mMainWindow->actionDelete->setText(tr("Trash")); + } + break; case DeletePage : - mMainWindow->actionDelete->setIcon(QIcon(":/images/trash.png")); + mMainWindow->actionDelete->setIcon(QIcon(":/images/trash-document-page.png")); mMainWindow->actionDelete->setText(tr("Trash")); break; case CompleteDelete : - mMainWindow->actionDelete->setIcon(QIcon(":/images/toolbar/deleteDocument.png")); - mMainWindow->actionDelete->setText(tr("Delete")); + if (mSelectionType == Folder) + { + mMainWindow->actionDelete->setIcon(QIcon(":/images/trash-delete-folder.png")); + mMainWindow->actionDelete->setText(tr("Delete")); + } + else + { + mMainWindow->actionDelete->setIcon(QIcon(":/images/trash-delete-document.png")); + mMainWindow->actionDelete->setText(tr("Delete")); + } break; case EmptyFolder : - mMainWindow->actionDelete->setIcon(QIcon(":/images/trash.png")); - mMainWindow->actionDelete->setText(tr("Empty")); + if (firstSelectedTreeIndex() == docModel->myDocumentsIndex()) + { + mMainWindow->actionDelete->setIcon(QIcon(":/images/trash-my-documents.png")); + mMainWindow->actionDelete->setText(tr("Empty")); + } + else + { + mMainWindow->actionDelete->setIcon(QIcon(":/images/trash-folder.png")); + mMainWindow->actionDelete->setText(tr("Empty")); + } break; case EmptyTrash : - mMainWindow->actionDelete->setIcon(QIcon(":/images/toolbar/deleteDocument.png")); + mMainWindow->actionDelete->setIcon(QIcon(":/images/trash-empty.png")); mMainWindow->actionDelete->setText(tr("Empty")); break; } diff --git a/src/document/UBDocumentController.h b/src/document/UBDocumentController.h index 5ee68d59..fa613d04 100644 --- a/src/document/UBDocumentController.h +++ b/src/document/UBDocumentController.h @@ -305,6 +305,7 @@ public slots: void hSliderRangeChanged(int min, int max); protected: + void mousePressEvent(QMouseEvent *event) override; void dragEnterEvent(QDragEnterEvent *event); void dragLeaveEvent(QDragLeaveEvent *event); void dragMoveEvent(QDragMoveEvent *event); From d2ab0e09629e5764583cb8f93657a6b7a21537b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Thu, 30 Sep 2021 15:08:40 +0200 Subject: [PATCH 028/130] strokes group should be moved if tool is play or (selection & item not selected) --- src/board/UBBoardView.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/board/UBBoardView.cpp b/src/board/UBBoardView.cpp index ccef2d98..02f32b3c 100644 --- a/src/board/UBBoardView.cpp +++ b/src/board/UBBoardView.cpp @@ -696,7 +696,6 @@ bool UBBoardView::itemShouldBeMoved(QGraphicsItem *item) case UBGraphicsAudioItem::Type: return true; case UBGraphicsStrokesGroup::Type: - return false; case UBGraphicsTextItem::Type: if (currentTool == UBStylusTool::Play) return true; From edf2245ae7562e09793c2bc19d165609369c0ee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Thu, 30 Sep 2021 15:19:16 +0200 Subject: [PATCH 029/130] fixed an issue where all items would be deselected except one but selection frame not updated --- src/board/UBBoardView.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/board/UBBoardView.cpp b/src/board/UBBoardView.cpp index 02f32b3c..147b78eb 100644 --- a/src/board/UBBoardView.cpp +++ b/src/board/UBBoardView.cpp @@ -516,6 +516,7 @@ void UBBoardView::handleItemsSelection(QGraphicsItem *item) if ((UBGraphicsItemType::UserTypesCount > item->type()) && (item->type() > QGraphicsItem::UserType)) { scene()->deselectAllItemsExcept(item); + scene()->updateSelectionFrame(); } } } From 48ffe65b28b8742d84099f3e1ffc423066a822bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Thu, 30 Sep 2021 15:20:30 +0200 Subject: [PATCH 030/130] updated version to 1.6.2a-0930 --- OpenBoard.pro | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OpenBoard.pro b/OpenBoard.pro index 2de5b263..18ee57ac 100644 --- a/OpenBoard.pro +++ b/OpenBoard.pro @@ -9,9 +9,9 @@ CONFIG += debug_and_release \ VERSION_MAJ = 1 VERSION_MIN = 6 -VERSION_PATCH = 1 -VERSION_TYPE = r # a = alpha, b = beta, rc = release candidate, r = release, other => error -VERSION_BUILD = 0309 +VERSION_PATCH = 2 +VERSION_TYPE = a # a = alpha, b = beta, rc = release candidate, r = release, other => error +VERSION_BUILD = 0930 VERSION = "$${VERSION_MAJ}.$${VERSION_MIN}.$${VERSION_PATCH}-$${VERSION_TYPE}.$${VERSION_BUILD}" From 4239437c536f3877a4af72cd4b5e8e2422d7ba90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Wed, 13 Oct 2021 14:17:31 +0200 Subject: [PATCH 031/130] changed deprecated qtsingleapplication for singleapplication. github : https://github.com/itay-grudev/SingleApplication --- OpenBoard.pro | 3 +- src/core/UBApplication.cpp | 2 +- src/core/UBApplication.h | 4 +- src/core/main.cpp | 4 +- src/qtsingleapplication/INSTALL.TXT | 254 --------- src/qtsingleapplication/README.TXT | 33 -- src/qtsingleapplication/buildlib/buildlib.pro | 13 - src/qtsingleapplication/common.pri | 14 - src/qtsingleapplication/configure | 25 - src/qtsingleapplication/configure.bat | 80 --- src/qtsingleapplication/doc/html/classic.css | 284 --------- .../doc/html/images/qt-logo.png | Bin 4075 -> 0 bytes src/qtsingleapplication/doc/html/index.html | 48 -- .../qtsingleapplication-example-loader.html | 175 ------ .../qtsingleapplication-example-trivial.html | 101 ---- .../doc/html/qtsingleapplication-members.html | 235 -------- .../html/qtsingleapplication-obsolete.html | 31 - .../doc/html/qtsingleapplication.dcf | 40 -- .../doc/html/qtsingleapplication.html | 162 ------ .../doc/html/qtsingleapplication.index | 90 --- .../doc/html/qtsingleapplication.qhp | 53 -- ...singlecoreapplication-example-console.html | 118 ---- .../html/qtsinglecoreapplication-members.html | 126 ---- .../doc/html/qtsinglecoreapplication.html | 98 ---- .../doc/images/qt-logo.png | Bin 4075 -> 0 bytes src/qtsingleapplication/doc/index.qdoc | 87 --- .../examples/console/console.pro | 5 - .../examples/console/console.qdoc | 65 --- .../examples/console/main.cpp | 89 --- src/qtsingleapplication/examples/examples.pro | 4 - .../examples/loader/file1.qsl | 1 - .../examples/loader/file2.qsl | 1 - .../examples/loader/loader.pro | 6 - .../examples/loader/loader.qdoc | 81 --- .../examples/loader/main.cpp | 152 ----- .../examples/trivial/main.cpp | 78 --- .../examples/trivial/trivial.pro | 5 - .../examples/trivial/trivial.qdoc | 76 --- .../qtsingleapplication.pro | 5 - src/qtsingleapplication/src/QtLockedFile | 1 - .../src/QtSingleApplication | 1 - src/qtsingleapplication/src/qtlocalpeer.cpp | 213 ------- src/qtsingleapplication/src/qtlocalpeer.h | 77 --- src/qtsingleapplication/src/qtlockedfile.cpp | 193 ------- src/qtsingleapplication/src/qtlockedfile.h | 97 ---- .../src/qtlockedfile_unix.cpp | 115 ---- .../src/qtlockedfile_win.cpp | 211 ------- .../src/qtsingleapplication.cpp | 347 ----------- .../src/qtsingleapplication.h | 105 ---- .../src/qtsingleapplication.pri | 17 - .../src/qtsinglecoreapplication.cpp | 149 ----- .../src/qtsinglecoreapplication.h | 71 --- .../src/qtsinglecoreapplication.pri | 10 - src/singleapplication/CHANGELOG.md | 306 ++++++++++ src/singleapplication/CMakeLists.txt | 40 ++ src/singleapplication/LICENSE | 24 + src/singleapplication/README.md | 305 ++++++++++ src/singleapplication/SingleApplication | 1 + src/singleapplication/Windows.md | 46 ++ src/singleapplication/singleapplication.cpp | 271 +++++++++ src/singleapplication/singleapplication.h | 154 +++++ src/singleapplication/singleapplication.pri | 20 + src/singleapplication/singleapplication_p.cpp | 538 ++++++++++++++++++ src/singleapplication/singleapplication_p.h | 109 ++++ 64 files changed, 1821 insertions(+), 4248 deletions(-) delete mode 100644 src/qtsingleapplication/INSTALL.TXT delete mode 100644 src/qtsingleapplication/README.TXT delete mode 100644 src/qtsingleapplication/buildlib/buildlib.pro delete mode 100644 src/qtsingleapplication/common.pri delete mode 100755 src/qtsingleapplication/configure delete mode 100644 src/qtsingleapplication/configure.bat delete mode 100644 src/qtsingleapplication/doc/html/classic.css delete mode 100644 src/qtsingleapplication/doc/html/images/qt-logo.png delete mode 100644 src/qtsingleapplication/doc/html/index.html delete mode 100644 src/qtsingleapplication/doc/html/qtsingleapplication-example-loader.html delete mode 100644 src/qtsingleapplication/doc/html/qtsingleapplication-example-trivial.html delete mode 100644 src/qtsingleapplication/doc/html/qtsingleapplication-members.html delete mode 100644 src/qtsingleapplication/doc/html/qtsingleapplication-obsolete.html delete mode 100644 src/qtsingleapplication/doc/html/qtsingleapplication.dcf delete mode 100644 src/qtsingleapplication/doc/html/qtsingleapplication.html delete mode 100644 src/qtsingleapplication/doc/html/qtsingleapplication.index delete mode 100644 src/qtsingleapplication/doc/html/qtsingleapplication.qhp delete mode 100644 src/qtsingleapplication/doc/html/qtsinglecoreapplication-example-console.html delete mode 100644 src/qtsingleapplication/doc/html/qtsinglecoreapplication-members.html delete mode 100644 src/qtsingleapplication/doc/html/qtsinglecoreapplication.html delete mode 100644 src/qtsingleapplication/doc/images/qt-logo.png delete mode 100644 src/qtsingleapplication/doc/index.qdoc delete mode 100644 src/qtsingleapplication/examples/console/console.pro delete mode 100644 src/qtsingleapplication/examples/console/console.qdoc delete mode 100644 src/qtsingleapplication/examples/console/main.cpp delete mode 100644 src/qtsingleapplication/examples/examples.pro delete mode 100644 src/qtsingleapplication/examples/loader/file1.qsl delete mode 100644 src/qtsingleapplication/examples/loader/file2.qsl delete mode 100644 src/qtsingleapplication/examples/loader/loader.pro delete mode 100644 src/qtsingleapplication/examples/loader/loader.qdoc delete mode 100644 src/qtsingleapplication/examples/loader/main.cpp delete mode 100644 src/qtsingleapplication/examples/trivial/main.cpp delete mode 100644 src/qtsingleapplication/examples/trivial/trivial.pro delete mode 100644 src/qtsingleapplication/examples/trivial/trivial.qdoc delete mode 100644 src/qtsingleapplication/qtsingleapplication.pro delete mode 100644 src/qtsingleapplication/src/QtLockedFile delete mode 100644 src/qtsingleapplication/src/QtSingleApplication delete mode 100644 src/qtsingleapplication/src/qtlocalpeer.cpp delete mode 100644 src/qtsingleapplication/src/qtlocalpeer.h delete mode 100644 src/qtsingleapplication/src/qtlockedfile.cpp delete mode 100644 src/qtsingleapplication/src/qtlockedfile.h delete mode 100644 src/qtsingleapplication/src/qtlockedfile_unix.cpp delete mode 100644 src/qtsingleapplication/src/qtlockedfile_win.cpp delete mode 100644 src/qtsingleapplication/src/qtsingleapplication.cpp delete mode 100644 src/qtsingleapplication/src/qtsingleapplication.h delete mode 100644 src/qtsingleapplication/src/qtsingleapplication.pri delete mode 100644 src/qtsingleapplication/src/qtsinglecoreapplication.cpp delete mode 100644 src/qtsingleapplication/src/qtsinglecoreapplication.h delete mode 100644 src/qtsingleapplication/src/qtsinglecoreapplication.pri create mode 100644 src/singleapplication/CHANGELOG.md create mode 100644 src/singleapplication/CMakeLists.txt create mode 100644 src/singleapplication/LICENSE create mode 100644 src/singleapplication/README.md create mode 100644 src/singleapplication/SingleApplication create mode 100644 src/singleapplication/Windows.md create mode 100644 src/singleapplication/singleapplication.cpp create mode 100644 src/singleapplication/singleapplication.h create mode 100644 src/singleapplication/singleapplication.pri create mode 100644 src/singleapplication/singleapplication_p.cpp create mode 100644 src/singleapplication/singleapplication_p.h diff --git a/OpenBoard.pro b/OpenBoard.pro index 18ee57ac..5ab0de3c 100644 --- a/OpenBoard.pro +++ b/OpenBoard.pro @@ -57,7 +57,8 @@ include(src/podcast/podcast.pri) include(src/tools/tools.pri) include(src/desktop/desktop.pri) include(src/web/web.pri) -include(src/qtsingleapplication/src/qtsingleapplication.pri) +include(src/singleapplication/singleapplication.pri) +DEFINES += QAPPLICATION_CLASS=QApplication DEPENDPATH += src/pdf-merger INCLUDEPATH += src/pdf-merger diff --git a/src/core/UBApplication.cpp b/src/core/UBApplication.cpp index b189c3b8..b5cdfcc4 100644 --- a/src/core/UBApplication.cpp +++ b/src/core/UBApplication.cpp @@ -88,7 +88,7 @@ bool bIsMinimized = false; QObject* UBApplication::staticMemoryCleaner = 0; -UBApplication::UBApplication(const QString &id, int &argc, char **argv) : QtSingleApplication(id, argc, argv) +UBApplication::UBApplication(const QString &id, int &argc, char **argv) : SingleApplication(argc, argv) , mPreferencesController(NULL) , mApplicationTranslator(NULL) , mQtGuiTranslator(NULL) diff --git a/src/core/UBApplication.h b/src/core/UBApplication.h index b821ce4a..5f9442d7 100644 --- a/src/core/UBApplication.h +++ b/src/core/UBApplication.h @@ -35,7 +35,7 @@ #include #include -#include "qtsingleapplication.h" +#include "singleapplication/singleapplication.h" namespace Ui { @@ -54,7 +54,7 @@ class UBApplicationController; class UBDocumentController; class UBMainWindow; -class UBApplication : public QtSingleApplication +class UBApplication : public SingleApplication { Q_OBJECT diff --git a/src/core/main.cpp b/src/core/main.cpp index ddb0c6ed..10f3f6ff 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -122,8 +122,8 @@ int main(int argc, char *argv[]) if (f.exists()) { fileToOpen += args[1]; - if (app.sendMessage(UBSettings::appPingMessage, 20000)) { - app.sendMessage(fileToOpen, 1000000); + if (app.sendMessage(UBSettings::appPingMessage.toUtf8(), 20000)) { + app.sendMessage(fileToOpen.toUtf8(), 1000000); return 0; } } diff --git a/src/qtsingleapplication/INSTALL.TXT b/src/qtsingleapplication/INSTALL.TXT deleted file mode 100644 index bbb74a9d..00000000 --- a/src/qtsingleapplication/INSTALL.TXT +++ /dev/null @@ -1,254 +0,0 @@ -INSTALLATION INSTRUCTIONS - -These instructions refer to the package you are installing as -some-package.tar.gz or some-package.zip. The .zip file is intended for use -on Windows. - -The directory you choose for the installation will be referred to as -your-install-dir. - -Note to Qt Visual Studio Integration users: In the instructions below, -instead of building from command line with nmake, you can use the menu -command 'Qt->Open Solution from .pro file' on the .pro files in the -example and plugin directories, and then build from within Visual -Studio. - -Unpacking and installation --------------------------- - -1. Unpacking the archive (if you have not done so already). - - On Unix and Mac OS X (in a terminal window): - - cd your-install-dir - gunzip some-package.tar.gz - tar xvf some-package.tar - - This creates the subdirectory some-package containing the files. - - On Windows: - - Unpack the .zip archive by right-clicking it in explorer and - choosing "Extract All...". If your version of Windows does not - have zip support, you can use the infozip tools available - from www.info-zip.org. - - If you are using the infozip tools (in a command prompt window): - cd your-install-dir - unzip some-package.zip - -2. Configuring the package. - - The configure script is called "configure" on unix/mac and - "configure.bat" on Windows. It should be run from a command line - after cd'ing to the package directory. - - You can choose whether you want to use the component by including - its source code directly into your project, or build the component - as a dynamic shared library (DLL) that is loaded into the - application at run-time. The latter may be preferable for - technical or licensing (LGPL) reasons. If you want to build a DLL, - run the configure script with the argument "-library". Also see - the note about usage below. - - (Components that are Qt plugins, e.g. styles and image formats, - are by default built as a plugin DLL.) - - The configure script will prompt you in some cases for further - information. Answer these questions and carefully read the license text - before accepting the license conditions. The package cannot be used if - you do not accept the license conditions. - -3. Building the component and examples (when required). - - If a DLL is to be built, or if you would like to build the - examples, next give the commands - - qmake - make [or nmake if your are using Microsoft Visual C++] - - The example program(s) can be found in the directory called - "examples" or "example". - - Components that are Qt plugins, e.g. styles and image formats, are - ready to be used as soon as they are built, so the rest of this - installation instruction can be skipped. - -4. Building the Qt Designer plugin (optional). - - Some of the widget components are provided with plugins for Qt - Designer. To build and install the plugin, cd into the - some-package/plugin directory and give the commands - - qmake - make [or nmake if your are using Microsoft Visual C++] - - Restart Qt Designer to make it load the new widget plugin. - - Note: If you are using the built-in Qt Designer from the Qt Visual - Studio Integration, you will need to manually copy the plugin DLL - file, i.e. copy - %QTDIR%\plugins\designer\some-component.dll - to the Qt Visual Studio Integration plugin path, typically: - C:\Program Files\Trolltech\Qt VS Integration\plugins - - Note: If you for some reason are using a Qt Designer that is built - in debug mode, you will need to build the plugin in debug mode - also. Edit the file plugin.pro in the plugin directory, changing - 'release' to 'debug' in the CONFIG line, before running qmake. - - - -Solutions components are intended to be used directly from the package -directory during development, so there is no 'make install' procedure. - - -Using a component in your project ---------------------------------- - -To use this component in your project, add the following line to the -project's .pro file (or do the equivalent in your IDE): - - include(your-install-dir/some-package/src/some-package.pri) - -This adds the package's sources and headers to the SOURCES and HEADERS -project variables respectively (or, if the component has been -configured as a DLL, it adds that library to the LIBS variable), and -updates INCLUDEPATH to contain the package's src -directory. Additionally, the .pri file may include some dependencies -needed by the package. - -To include a header file from the package in your sources, you can now -simply use: - - #include - -or alternatively, in pre-Qt 4 style: - - #include - -Refer to the documentation to see the classes and headers this -components provides. - - - -Install documentation (optional) --------------------------------- - -The HTML documentation for the package's classes is located in the -your-install-dir/some-package/doc/html/index.html. You can open this -file and read the documentation with any web browser. - -To install the documentation into Qt Assistant (for Qt version 4.4 and -later): - -1. In Assistant, open the Edit->Preferences dialog and choose the - Documentation tab. Click the Add... button and select the file - your-install-dir/some-package/doc/html/some-package.qch - -For Qt versions prior to 4.4, do instead the following: - -1. The directory your-install-dir/some-package/doc/html contains a - file called some-package.dcf. Execute the following commands in a - shell, command prompt or terminal window: - - cd your-install-dir/some-package/doc/html/ - assistant -addContentFile some-package.dcf - -The next time you start Qt Assistant, you can access the package's -documentation. - - -Removing the documentation from assistant ------------------------------------------ - -If you have installed the documentation into Qt Assistant, and want to uninstall it, do as follows, for Qt version 4.4 and later: - -1. In Assistant, open the Edit->Preferences dialog and choose the - Documentation tab. In the list of Registered Documentation, select - the item com.nokia.qtsolutions.some-package_version, and click - the Remove button. - -For Qt versions prior to 4.4, do instead the following: - -1. The directory your-install-dir/some-package/doc/html contains a - file called some-package.dcf. Execute the following commands in a - shell, command prompt or terminal window: - - cd your-install-dir/some-package/doc/html/ - assistant -removeContentFile some-package.dcf - - - -Using the component as a DLL ----------------------------- - -1. Normal components - - The shared library (DLL) is built and placed in the - some-package/lib directory. It is intended to be used directly - from there during development. When appropriate, both debug and - release versions are built, since the run-time linker will in some - cases refuse to load a debug-built DLL into a release-built - application or vice versa. - - The following steps are taken by default to help the dynamic - linker to locate the DLL at run-time (during development): - - Unix: The some-package.pri file will add linker instructions to - add the some-package/lib directory to the rpath of the - executable. (When distributing, or if your system does not support - rpath, you can copy the shared library to another place that is - searched by the dynamic linker, e.g. the "lib" directory of your - Qt installation.) - - Mac: The full path to the library is hardcoded into the library - itself, from where it is copied into the executable at link time, - and ready by the dynamic linker at run-time. (When distributing, - you will want to edit these hardcoded paths in the same way as for - the Qt DLLs. Refer to the document "Deploying an Application on - Mac OS X" in the Qt Reference Documentation.) - - Windows: the .dll file(s) are copied into the "bin" directory of - your Qt installation. The Qt installation will already have set up - that directory to be searched by the dynamic linker. - - -2. Plugins - - For Qt Solutions plugins (e.g. image formats), both debug and - release versions of the plugin are built by default when - appropriate, since in some cases the release Qt library will not - load a debug plugin, and vice versa. The plugins are automatically - copied into the plugins directory of your Qt installation when - built, so no further setup is required. - - Plugins may also be built statically, i.e. as a library that will be - linked into your application executable, and so will not need to - be redistributed as a separate plugin DLL to end users. Static - building is required if Qt itself is built statically. To do it, - just add "static" to the CONFIG variable in the plugin/plugin.pro - file before building. Refer to the "Static Plugins" section in the - chapter "How to Create Qt Plugins" for explanation of how to use a - static plugin in your application. The source code of the example - program(s) will also typically contain the relevant instructions - as comments. - - - -Uninstalling ------------- - - The following command will remove any fils that have been - automatically placed outside the package directory itself during - installation and building - - make distclean [or nmake if your are using Microsoft Visual C++] - - If Qt Assistant documentation or Qt Designer plugins have been - installed, they can be uninstalled manually, ref. above. - - -Enjoy! :) - -- The Qt Solutions Team. diff --git a/src/qtsingleapplication/README.TXT b/src/qtsingleapplication/README.TXT deleted file mode 100644 index 06abb095..00000000 --- a/src/qtsingleapplication/README.TXT +++ /dev/null @@ -1,33 +0,0 @@ -Qt Solutions Component: Single Application - -The QtSingleApplication component provides support for -applications that can be only started once per user. - - - -Version history: - -2.0: - Version 1.3 ported to Qt 4. - -2.1: - Fix compilation problem on Mac. - -2.2: - Really fix the Mac compilation problem. - - Mac: fix crash due to wrong object releasing. - - Mac: Fix memory leak. - -2.3: - Windows: Force creation of internal widget to make it work - with Qt 4.2. - -2.4: - Fix the system for automatic window raising on message - reception. NOTE: minor API change. - -2.5: - Mac: Fix isRunning() to work and report correctly. - -2.6: - - initialize() is now obsolete, no longer necessary to call - it - - - Fixed race condition where multiple instances migth be started - - - QtSingleCoreApplication variant provided for non-GUI (console) - usage - - Complete reimplementation. Visible changes: - - LGPL release. - diff --git a/src/qtsingleapplication/buildlib/buildlib.pro b/src/qtsingleapplication/buildlib/buildlib.pro deleted file mode 100644 index 37dddcda..00000000 --- a/src/qtsingleapplication/buildlib/buildlib.pro +++ /dev/null @@ -1,13 +0,0 @@ -TEMPLATE=lib -CONFIG += qt dll qtsingleapplication-buildlib -mac:CONFIG += absolute_library_soname -win32|mac:!wince*:!win32-msvc:!macx-xcode:CONFIG += debug_and_release build_all -include(../src/qtsingleapplication.pri) -TARGET = $$QTSINGLEAPPLICATION_LIBNAME -DESTDIR = $$QTSINGLEAPPLICATION_LIBDIR -win32 { - DLLDESTDIR = $$[QT_INSTALL_BINS] - QMAKE_DISTCLEAN += $$[QT_INSTALL_BINS]\\$${QTSINGLEAPPLICATION_LIBNAME}.dll -} -target.path = $$DESTDIR -INSTALLS += target diff --git a/src/qtsingleapplication/common.pri b/src/qtsingleapplication/common.pri deleted file mode 100644 index 924c57c8..00000000 --- a/src/qtsingleapplication/common.pri +++ /dev/null @@ -1,14 +0,0 @@ -exists(config.pri):infile(config.pri, SOLUTIONS_LIBRARY, yes): CONFIG += qtsingleapplication-uselib - -TEMPLATE += fakelib -greaterThan(QT_MAJOR_VERSION, 5)|\ - if(equals(QT_MAJOR_VERSION, 5):greaterThan(QT_MINOR_VERSION, 4))|\ - if(equals(QT_MAJOR_VERSION, 5):equals(QT_MINOR_VERSION, 4):greaterThan(QT_PATCH_VERSION, 1)) { - QTSINGLEAPPLICATION_LIBNAME = $$qt5LibraryTarget(QtSolutions_SingleApplication-head) -} else { - QTSINGLEAPPLICATION_LIBNAME = $$qtLibraryTarget(QtSolutions_SingleApplication-head) -} -TEMPLATE -= fakelib - -QTSINGLEAPPLICATION_LIBDIR = $$PWD/lib -unix:qtsingleapplication-uselib:!qtsingleapplication-buildlib:QMAKE_RPATHDIR += $$QTSINGLEAPPLICATION_LIBDIR diff --git a/src/qtsingleapplication/configure b/src/qtsingleapplication/configure deleted file mode 100755 index 3c4edfff..00000000 --- a/src/qtsingleapplication/configure +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/sh - -if [ "x$1" != "x" -a "x$1" != "x-library" ]; then - echo "Usage: $0 [-library]" - echo - echo "-library: Build the component as a dynamic library (DLL). Default is to" - echo " include the component source code directly in the application." - echo - exit 0 -fi - -rm -f config.pri -if [ "x$1" = "x-library" ]; then - echo "Configuring to build this component as a dynamic library." - echo "SOLUTIONS_LIBRARY = yes" > config.pri -fi - -echo -echo "This component is now configured." -echo -echo "To build the component library (if requested) and example(s)," -echo "run qmake and your make command." -echo -echo "To remove or reconfigure, run make distclean." -echo diff --git a/src/qtsingleapplication/configure.bat b/src/qtsingleapplication/configure.bat deleted file mode 100644 index 69efec35..00000000 --- a/src/qtsingleapplication/configure.bat +++ /dev/null @@ -1,80 +0,0 @@ -::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: -:: -:: Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -:: Contact: http://www.qt-project.org/legal -:: -:: This file is part of the Qt Solutions component. -:: -:: $QT_BEGIN_LICENSE:BSD$ -:: You may use this file under the terms of the BSD license as follows: -:: -:: "Redistribution and use in source and binary forms, with or without -:: modification, are permitted provided that the following conditions are -:: met: -:: * Redistributions of source code must retain the above copyright -:: notice, this list of conditions and the following disclaimer. -:: * Redistributions in binary form must reproduce the above copyright -:: notice, this list of conditions and the following disclaimer in -:: the documentation and/or other materials provided with the -:: distribution. -:: * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names -:: of its contributors may be used to endorse or promote products derived -:: from this software without specific prior written permission. -:: -:: -:: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -:: "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -:: LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -:: A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -:: OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -:: SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -:: LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -:: DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -:: THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -:: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -:: OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -:: -:: $QT_END_LICENSE$ -:: -::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - -@echo off - -rem -rem "Main" -rem - -if not "%1"=="" ( - if not "%1"=="-library" ( - call :PrintUsage - goto EOF - ) -) - -if exist config.pri. del config.pri -if "%1"=="-library" ( - echo Configuring to build this component as a dynamic library. - echo SOLUTIONS_LIBRARY = yes > config.pri -) - -echo . -echo This component is now configured. -echo . -echo To build the component library (if requested) and example(s), -echo run qmake and your make or nmake command. -echo . -echo To remove or reconfigure, run make (nmake) distclean. -echo . -goto EOF - -:PrintUsage -echo Usage: configure.bat [-library] -echo . -echo -library: Build the component as a dynamic library (DLL). Default is to -echo include the component source directly in the application. -echo A DLL may be preferable for technical or licensing (LGPL) reasons. -echo . -goto EOF - - -:EOF diff --git a/src/qtsingleapplication/doc/html/classic.css b/src/qtsingleapplication/doc/html/classic.css deleted file mode 100644 index b8cae8e1..00000000 --- a/src/qtsingleapplication/doc/html/classic.css +++ /dev/null @@ -1,284 +0,0 @@ -BODY,H1,H2,H3,H4,H5,H6,P,CENTER,TD,TH,UL,DL,DIV { - font-family: Arial, Geneva, Helvetica, sans-serif; -} -H1 { - text-align: center; - font-size: 160%; -} -H2 { - font-size: 120%; -} -H3 { - font-size: 100%; -} - -h3.fn,span.fn -{ - background-color: #eee; - border-width: 1px; - border-style: solid; - border-color: #ddd; - font-weight: bold; - padding: 6px 0px 6px 10px; - margin: 42px 0px 0px 0px; -} - -hr { - border: 0; - color: #a0a0a0; - background-color: #ccc; - height: 1px; - width: 100%; - text-align: left; - margin: 34px 0px 34px 0px; -} - -table.valuelist { - border-width: 1px 1px 1px 1px; - border-style: solid; - border-color: #dddddd; - border-collapse: collapse; - background-color: #f0f0f0; -} - -table.indextable { - border-width: 1px 1px 1px 1px; - border-style: solid; - border-collapse: collapse; - background-color: #f0f0f0; - border-color:#555; - font-size: 100%; -} - -table td.largeindex { - border-width: 1px 1px 1px 1px; - border-collapse: collapse; - background-color: #f0f0f0; - border-color:#555; - font-size: 120%; -} - -table.valuelist th { - border-width: 1px 1px 1px 2px; - padding: 4px; - border-style: solid; - border-color: #666; - color:white; - background-color:#666; -} - -th.titleheader { - border-width: 1px 0px 1px 0px; - padding: 2px; - border-style: solid; - border-color: #666; - color:white; - background-color:#555; - background-image:url('images/gradient.png')}; - background-repeat: repeat-x; - font-size: 100%; -} - - -th.largeheader { - border-width: 1px 0px 1px 0px; - padding: 4px; - border-style: solid; - border-color: #444; - color:white; - background-color:#555555; - font-size: 120%; -} - -p { - - margin-left: 4px; - margin-top: 8px; - margin-bottom: 8px; -} - -a:link -{ - color: #0046ad; - text-decoration: none -} - -a:visited -{ - color: #672967; - text-decoration: none -} - -a.obsolete -{ - color: #661100; - text-decoration: none -} - -a.compat -{ - color: #661100; - text-decoration: none -} - -a.obsolete:visited -{ - color: #995500; - text-decoration: none -} - -a.compat:visited -{ - color: #995500; - text-decoration: none -} - -body -{ - background: #ffffff; - color: black -} - -table.generic, table.annotated -{ - border-width: 1px; - border-color:#bbb; - border-style:solid; - border-collapse:collapse; -} - -table td.memItemLeft { - width: 180px; - padding: 2px 0px 0px 8px; - margin: 4px; - border-width: 1px; - border-color: #E0E0E0; - border-style: none; - font-size: 100%; - white-space: nowrap -} - -table td.memItemRight { - padding: 2px 8px 0px 8px; - margin: 4px; - border-width: 1px; - border-color: #E0E0E0; - border-style: none; - font-size: 100%; -} - -table tr.odd { - background: #f0f0f0; - color: black; -} - -table tr.even { - background: #e4e4e4; - color: black; -} - -table.annotated th { - padding: 3px; - text-align: left -} - -table.annotated td { - padding: 3px; -} - -table tr pre -{ - padding-top: 0px; - padding-bottom: 0px; - padding-left: 0px; - padding-right: 0px; - border: none; - background: none -} - -tr.qt-style -{ - background: #96E066; - color: black -} - -body pre -{ - padding: 0.2em; - border: #e7e7e7 1px solid; - background: #f1f1f1; - color: black -} - -table tr.qt-code pre -{ - padding: 0.2em; - border: #e7e7e7 1px solid; - background: #f1f1f1; - color: black -} - -span.preprocessor, span.preprocessor a -{ - color: darkblue; -} - -span.comment -{ - color: darkred; - font-style: italic -} - -span.string,span.char -{ - color: darkgreen; -} - -.title -{ - text-align: center -} - -.subtitle -{ - font-size: 0.8em -} - -.small-subtitle -{ - font-size: 0.65em -} - -.qmlitem { - padding: 0; -} - -.qmlname { - white-space: nowrap; -} - -.qmltype { - text-align: center; - font-size: 160%; -} - -.qmlproto { - background-color: #eee; - border-width: 1px; - border-style: solid; - border-color: #ddd; - font-weight: bold; - padding: 6px 10px 6px 10px; - margin: 42px 0px 0px 0px; -} - -.qmlreadonly { - float: right; - color: red -} - -.qmldoc { -} - -*.qmlitem p { -} diff --git a/src/qtsingleapplication/doc/html/images/qt-logo.png b/src/qtsingleapplication/doc/html/images/qt-logo.png deleted file mode 100644 index 794162f5af58e7b38beebf472842fc9703ede19b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4075 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 diff --git a/src/qtsingleapplication/doc/html/index.html b/src/qtsingleapplication/doc/html/index.html deleted file mode 100644 index af9dab10..00000000 --- a/src/qtsingleapplication/doc/html/index.html +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - Single Application - - - - - - - -
  Home

Single Application
-

- -

Description

-

The QtSingleApplication component provides support for applications that can be only started once per user.

-

For some applications it is useful or even critical that they are started only once by any user. Future attempts to start the application should activate any already running instance, and possibly perform requested actions, e.g. loading a file, in that instance.

-

The QtSingleApplication class provides an interface to detect a running instance, and to send command strings to that instance. For console (non-GUI) applications, the QtSingleCoreApplication variant is provided, which avoids dependency on QtGui.

- -

Classes

- - -

Examples

- - -

Tested platforms

-
    -
  • Qt 4.4, 4.5 / Windows XP / MSVC.NET 2005
  • -
  • Qt 4.4, 4.5 / Linux / gcc
  • -
  • Qt 4.4, 4.5 / MacOS X 10.5 / gcc
  • -
-


- - - - -
Copyright © 2010 Nokia Corporation and/or its subsidiary(-ies)Trademarks
Qt Solutions
- diff --git a/src/qtsingleapplication/doc/html/qtsingleapplication-example-loader.html b/src/qtsingleapplication/doc/html/qtsingleapplication-example-loader.html deleted file mode 100644 index 6a366329..00000000 --- a/src/qtsingleapplication/doc/html/qtsingleapplication-example-loader.html +++ /dev/null @@ -1,175 +0,0 @@ - - - - - - Loading Documents - - - - - - - -
  Home

Loading Documents
-

-

The application in this example loads or prints the documents passed as commandline parameters to further instances of this application.

-
 /****************************************************************************
- **
- ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
- ** Contact: http://www.qt-project.org/legal
- **
- ** This file is part of the Qt Solutions component.
- **
- ** You may use this file under the terms of the BSD license as follows:
- **
- ** "Redistribution and use in source and binary forms, with or without
- ** modification, are permitted provided that the following conditions are
- ** met:
- **   * Redistributions of source code must retain the above copyright
- **     notice, this list of conditions and the following disclaimer.
- **   * Redistributions in binary form must reproduce the above copyright
- **     notice, this list of conditions and the following disclaimer in
- **     the documentation and/or other materials provided with the
- **     distribution.
- **   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
- **     the names of its contributors may be used to endorse or promote
- **     products derived from this software without specific prior written
- **     permission.
- **
- ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
- **
- ****************************************************************************/
-
- #include <qtsingleapplication.h>
- #include <QtCore/QFile>
- #include <QtGui/QMainWindow>
- #include <QtGui/QPrinter>
- #include <QtGui/QPainter>
- #include <QtGui/QTextEdit>
- #include <QtGui/QMdiArea>
- #include <QtCore/QTextStream>
-
- class MainWindow : public QMainWindow
- {
-     Q_OBJECT
- public:
-     MainWindow();
-
- public slots:
-     void handleMessage(const QString& message);
-
- signals:
-     void needToShow();
-
- private:
-     QMdiArea *workspace;
- };
-

The user interface in this application is a QMainWindow subclass with a QMdiArea as the central widget. It implements a slot handleMessage() that will be connected to the messageReceived() signal of the QtSingleApplication class.

-
 MainWindow::MainWindow()
- {
-     workspace = new QMdiArea(this);
-
-     setCentralWidget(workspace);
- }
-

The MainWindow constructor creates a minimal user interface.

-
 void MainWindow::handleMessage(const QString& message)
- {
-     enum Action {
-         Nothing,
-         Open,
-         Print
-     } action;
-
-     action = Nothing;
-     QString filename = message;
-     if (message.toLower().startsWith("/print ")) {
-         filename = filename.mid(7);
-         action = Print;
-     } else if (!message.isEmpty()) {
-         action = Open;
-     }
-     if (action == Nothing) {
-         emit needToShow();
-         return;
-     }
-
-     QFile file(filename);
-     QString contents;
-     if (file.open(QIODevice::ReadOnly))
-         contents = file.readAll();
-     else
-         contents = "[[Error: Could not load file " + filename + "]]";
-
-     QTextEdit *view = new QTextEdit;
-     view->setPlainText(contents);
-
-     switch(action) {
-

The handleMessage() slot interprets the message passed in as a filename that can be prepended with /print to indicate that the file should just be printed rather than loaded.

-
     case Print:
-         {
-             QPrinter printer;
-             view->print(&printer);
-             delete view;
-         }
-         break;
-
-     case Open:
-         {
-             workspace->addSubWindow(view);
-             view->setWindowTitle(message);
-             view->show();
-             emit needToShow();
-         }
-         break;
-     default:
-         break;
-     };
- }
-

Loading the file will also activate the window.

-
 #include "main.moc"
-
- int main(int argc, char **argv)
- {
-     QtSingleApplication instance("File loader QtSingleApplication example", argc, argv);
-     QString message;
-     for (int a = 1; a < argc; ++a) {
-         message += argv[a];
-         if (a < argc-1)
-             message += " ";
-     }
-
-     if (instance.sendMessage(message))
-         return 0;
-

The main entry point function creates a QtSingleApplication object, and creates a message to send to a running instance of the application. If the message was sent successfully the process exits immediately.

-
     MainWindow mw;
-     mw.handleMessage(message);
-     mw.show();
-
-     QObject::connect(&instance, SIGNAL(messageReceived(const QString&)),
-                      &mw, SLOT(handleMessage(const QString&)));
-
-     instance.setActivationWindow(&mw, false);
-     QObject::connect(&mw, SIGNAL(needToShow()), &instance, SLOT(activateWindow()));
-
-     return instance.exec();
- }
-

If the message could not be sent the application starts up. Note that false is passed to the call to setActivationWindow() to prevent automatic activation for every message received, e.g. when the application should just print a file. Instead, the message handling function determines whether activation is requested, and signals that by emitting the needToShow() signal. This is then simply connected directly to QtSingleApplication's activateWindow() slot.

-


- - - - -
Copyright © 2010 Nokia Corporation and/or its subsidiary(-ies)Trademarks
Qt Solutions
- diff --git a/src/qtsingleapplication/doc/html/qtsingleapplication-example-trivial.html b/src/qtsingleapplication/doc/html/qtsingleapplication-example-trivial.html deleted file mode 100644 index 5e60cfa4..00000000 --- a/src/qtsingleapplication/doc/html/qtsingleapplication-example-trivial.html +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - A Trivial Example - - - - - - - -
  Home

A Trivial Example
-

-

The application in this example has a log-view that displays messages sent by further instances of the same application.

-

The example demonstrates the use of the QtSingleApplication class to detect and communicate with a running instance of the application using the sendMessage() API. The messageReceived() signal is used to display received messages in a QTextEdit log.

-
 /****************************************************************************
- **
- ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
- ** Contact: http://www.qt-project.org/legal
- **
- ** This file is part of the Qt Solutions component.
- **
- ** You may use this file under the terms of the BSD license as follows:
- **
- ** "Redistribution and use in source and binary forms, with or without
- ** modification, are permitted provided that the following conditions are
- ** met:
- **   * Redistributions of source code must retain the above copyright
- **     notice, this list of conditions and the following disclaimer.
- **   * Redistributions in binary form must reproduce the above copyright
- **     notice, this list of conditions and the following disclaimer in
- **     the documentation and/or other materials provided with the
- **     distribution.
- **   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
- **     the names of its contributors may be used to endorse or promote
- **     products derived from this software without specific prior written
- **     permission.
- **
- ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
- **
- ****************************************************************************/
-
- #include <qtsingleapplication.h>
- #include <QtGui/QTextEdit>
-
- class TextEdit : public QTextEdit
- {
-     Q_OBJECT
- public:
-     TextEdit(QWidget *parent = 0)
-         : QTextEdit(parent)
-     {}
- public slots:
-     void append(const QString &str)
-     {
-         QTextEdit::append(str);
-     }
- };
-
- #include "main.moc"
-
- int main(int argc, char **argv)
- {
-     QtSingleApplication instance(argc, argv);
-

The example has only the main entry point function. A QtSingleApplication object is created immediately.

-
     if (instance.sendMessage("Wake up!"))
-         return 0;
-

If another instance of this application is already running, sendMessage() will succeed, and this instance just exits immediately.

-
     TextEdit logview;
-     logview.setReadOnly(true);
-     logview.show();
-

Otherwise the instance continues as normal and creates the user interface.

-
     instance.setActivationWindow(&logview);
-
-     QObject::connect(&instance, SIGNAL(messageReceived(const QString&)),
-                      &logview, SLOT(append(const QString&)));
-
-     return instance.exec();
-

The logview object is also set as the application's activation window. Every time a message is received, the window will be raised and activated automatically.

-

The messageReceived() signal is also connected to the QTextEdit's append() slot. Every message received from further instances of this application will be displayed in the log.

-

Finally the event loop is entered.

-


- - - - -
Copyright © 2010 Nokia Corporation and/or its subsidiary(-ies)Trademarks
Qt Solutions
- diff --git a/src/qtsingleapplication/doc/html/qtsingleapplication-members.html b/src/qtsingleapplication/doc/html/qtsingleapplication-members.html deleted file mode 100644 index c995ce3f..00000000 --- a/src/qtsingleapplication/doc/html/qtsingleapplication-members.html +++ /dev/null @@ -1,235 +0,0 @@ - - - - - - List of All Members for QtSingleApplication - - - - - - - -
  Home

List of All Members for QtSingleApplication

-

This is the complete list of members for QtSingleApplication, including inherited members.

-

- -
-

-


- - - - -
Copyright © 2010 Nokia Corporation and/or its subsidiary(-ies)Trademarks
Qt Solutions
- diff --git a/src/qtsingleapplication/doc/html/qtsingleapplication-obsolete.html b/src/qtsingleapplication/doc/html/qtsingleapplication-obsolete.html deleted file mode 100644 index 0d07dfae..00000000 --- a/src/qtsingleapplication/doc/html/qtsingleapplication-obsolete.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - Obsolete Members for QtSingleApplication - - - - - - - -
  Home

Obsolete Members for QtSingleApplication

-

The following class members are obsolete. They are provided to keep old source code working. We strongly advise against using them in new code.

-

-

Public Functions

- - -
void initialize ( bool dummy = true )   (obsolete)
-
-

Member Function Documentation

-

void QtSingleApplication::initialize ( bool dummy = true )

-


- - - - -
Copyright © 2010 Nokia Corporation and/or its subsidiary(-ies)Trademarks
Qt Solutions
- diff --git a/src/qtsingleapplication/doc/html/qtsingleapplication.dcf b/src/qtsingleapplication/doc/html/qtsingleapplication.dcf deleted file mode 100644 index d81f87fb..00000000 --- a/src/qtsingleapplication/doc/html/qtsingleapplication.dcf +++ /dev/null @@ -1,40 +0,0 @@ - - -
-
- QtSingleApplication - activateWindow - activationWindow - id - isRunning - messageReceived - sendMessage - setActivationWindow -
-
-
-
- QtSingleCoreApplication - id - isRunning - messageReceived - sendMessage -
-
-
-
-
- A non-GUI example -
-
- A Trivial Example -
-
- Loading Documents -
-
- Single Application -
-
-
- diff --git a/src/qtsingleapplication/doc/html/qtsingleapplication.html b/src/qtsingleapplication/doc/html/qtsingleapplication.html deleted file mode 100644 index 2754a3b9..00000000 --- a/src/qtsingleapplication/doc/html/qtsingleapplication.html +++ /dev/null @@ -1,162 +0,0 @@ - - - - - - QtSingleApplication Class Reference - - - - - - - -
  Home

QtSingleApplication Class Reference

-

The QtSingleApplication class provides an API to detect and communicate with running instances of an application. More...

-
 #include <QtSingleApplication>

Inherits QApplication.

- -
- -

Public Functions

- - - - - - - - - - - -
QtSingleApplication ( int & argc, char ** argv, bool GUIenabled = true )
QtSingleApplication ( const QString & appId, int & argc, char ** argv )
QtSingleApplication ( int & argc, char ** argv, Type type )
QtSingleApplication ( Display * dpy, Qt::HANDLE visual = 0, Qt::HANDLE cmap = 0 )
QtSingleApplication ( Display * dpy, int & argc, char ** argv, Qt::HANDLE visual = 0, Qt::HANDLE cmap = 0 )
QtSingleApplication ( Display * dpy, const QString & appId, int argc, char ** argv, Qt::HANDLE visual = 0, Qt::HANDLE cmap = 0 )
QWidget * activationWindow () const
QString id () const
bool isRunning ()
void setActivationWindow ( QWidget * aw, bool activateOnMessage = true )
- -
- -

Public Slots

- - - -
void activateWindow ()
bool sendMessage ( const QString & message, int timeout = 5000 )
- -
- -

Signals

- - -
void messageReceived ( const QString & message )
- -

Additional Inherited Members

- - -
-

Detailed Description

-

The QtSingleApplication class provides an API to detect and communicate with running instances of an application.

-

This class allows you to create applications where only one instance should be running at a time. I.e., if the user tries to launch another instance, the already running instance will be activated instead. Another usecase is a client-server system, where the first started instance will assume the role of server, and the later instances will act as clients of that server.

-

By default, the full path of the executable file is used to determine whether two processes are instances of the same application. You can also provide an explicit identifier string that will be compared instead.

-

The application should create the QtSingleApplication object early in the startup phase, and call isRunning() to find out if another instance of this application is already running. If isRunning() returns false, it means that no other instance is running, and this instance has assumed the role as the running instance. In this case, the application should continue with the initialization of the application user interface before entering the event loop with exec(), as normal.

-

The messageReceived() signal will be emitted when the running application receives messages from another instance of the same application. When a message is received it might be helpful to the user to raise the application so that it becomes visible. To facilitate this, QtSingleApplication provides the setActivationWindow() function and the activateWindow() slot.

-

If isRunning() returns true, another instance is already running. It may be alerted to the fact that another instance has started by using the sendMessage() function. Also data such as startup parameters (e.g. the name of the file the user wanted this new instance to open) can be passed to the running instance with this function. Then, the application should terminate (or enter client mode).

-

If isRunning() returns true, but sendMessage() fails, that is an indication that the running instance is frozen.

-

Here's an example that shows how to convert an existing application to use QtSingleApplication. It is very simple and does not make use of all QtSingleApplication's functionality (see the examples for that).

-
 // Original
- int main(int argc, char **argv)
- {
-     QApplication app(argc, argv);
-
-     MyMainWidget mmw;
-     mmw.show();
-     return app.exec();
- }
-
- // Single instance
- int main(int argc, char **argv)
- {
-     QtSingleApplication app(argc, argv);
-
-     if (app.isRunning())
-         return !app.sendMessage(someDataString);
-
-     MyMainWidget mmw;
-     app.setActivationWindow(&mmw);
-     mmw.show();
-     return app.exec();
- }
-

Once this QtSingleApplication instance is destroyed (normally when the process exits or crashes), when the user next attempts to run the application this instance will not, of course, be encountered. The next instance to call isRunning() or sendMessage() will assume the role as the new running instance.

-

For console (non-GUI) applications, QtSingleCoreApplication may be used instead of this class, to avoid the dependency on the QtGui library.

-

See also QtSingleCoreApplication.

-
-

Member Function Documentation

-

QtSingleApplication::QtSingleApplication ( int & argc, char ** argv, bool GUIenabled = true )

-

Creates a QtSingleApplication object. The application identifier will be QCoreApplication::applicationFilePath(). argc, argv, and GUIenabled are passed on to the QAppliation constructor.

-

If you are creating a console application (i.e. setting GUIenabled to false), you may consider using QtSingleCoreApplication instead.

-

QtSingleApplication::QtSingleApplication ( const QString & appId, int & argc, char ** argv )

-

Creates a QtSingleApplication object with the application identifier appId. argc and argv are passed on to the QAppliation constructor.

-

QtSingleApplication::QtSingleApplication ( int & argc, char ** argv, Type type )

-

Creates a QtSingleApplication object. The application identifier will be QCoreApplication::applicationFilePath(). argc, argv, and type are passed on to the QAppliation constructor.

-

QtSingleApplication::QtSingleApplication ( Display * dpy, Qt::HANDLE visual = 0, Qt::HANDLE cmap = 0 )

-

Special constructor for X11, ref. the documentation of QApplication's corresponding constructor. The application identifier will be QCoreApplication::applicationFilePath(). dpy, visual, and cmap are passed on to the QApplication constructor.

-

QtSingleApplication::QtSingleApplication ( Display * dpy, int & argc, char ** argv, Qt::HANDLE visual = 0, Qt::HANDLE cmap = 0 )

-

Special constructor for X11, ref. the documentation of QApplication's corresponding constructor. The application identifier will be QCoreApplication::applicationFilePath(). dpy, argc, argv, visual, and cmap are passed on to the QApplication constructor.

-

QtSingleApplication::QtSingleApplication ( Display * dpy, const QString & appId, int argc, char ** argv, Qt::HANDLE visual = 0, Qt::HANDLE cmap = 0 )

-

Special constructor for X11, ref. the documentation of QApplication's corresponding constructor. The application identifier will be appId. dpy, argc, argv, visual, and cmap are passed on to the QApplication constructor.

-

void QtSingleApplication::activateWindow ()   [slot]

-

De-minimizes, raises, and activates this application's activation window. This function does nothing if no activation window has been set.

-

This is a convenience function to show the user that this application instance has been activated when he has tried to start another instance.

-

This function should typically be called in response to the messageReceived() signal. By default, that will happen automatically, if an activation window has been set.

-

See also setActivationWindow(), messageReceived(), and initialize().

-

QWidget * QtSingleApplication::activationWindow () const

-

Returns the applications activation window if one has been set by calling setActivationWindow(), otherwise returns 0.

-

See also setActivationWindow().

-

QString QtSingleApplication::id () const

-

Returns the application identifier. Two processes with the same identifier will be regarded as instances of the same application.

-

bool QtSingleApplication::isRunning ()

-

Returns true if another instance of this application is running; otherwise false.

-

This function does not find instances of this application that are being run by a different user (on Windows: that are running in another session).

-

See also sendMessage().

-

void QtSingleApplication::messageReceived ( const QString & message )   [signal]

-

This signal is emitted when the current instance receives a message from another instance of this application.

-

See also sendMessage(), setActivationWindow(), and activateWindow().

-

bool QtSingleApplication::sendMessage ( const QString & message, int timeout = 5000 )   [slot]

-

Tries to send the text message to the currently running instance. The QtSingleApplication object in the running instance will emit the messageReceived() signal when it receives the message.

-

This function returns true if the message has been sent to, and processed by, the current instance. If there is no instance currently running, or if the running instance fails to process the message within timeout milliseconds, this function return false.

-

See also isRunning() and messageReceived().

-

void QtSingleApplication::setActivationWindow ( QWidget * aw, bool activateOnMessage = true )

-

Sets the activation window of this application to aw. The activation window is the widget that will be activated by activateWindow(). This is typically the application's main window.

-

If activateOnMessage is true (the default), the window will be activated automatically every time a message is received, just prior to the messageReceived() signal being emitted.

-

See also activationWindow(), activateWindow(), and messageReceived().

-


- - - - -
Copyright © 2010 Nokia Corporation and/or its subsidiary(-ies)Trademarks
Qt Solutions
- diff --git a/src/qtsingleapplication/doc/html/qtsingleapplication.index b/src/qtsingleapplication/doc/html/qtsingleapplication.index deleted file mode 100644 index 56052c2a..00000000 --- a/src/qtsingleapplication/doc/html/qtsingleapplication.index +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/qtsingleapplication/doc/html/qtsingleapplication.qhp b/src/qtsingleapplication/doc/html/qtsingleapplication.qhp deleted file mode 100644 index ff42d9df..00000000 --- a/src/qtsingleapplication/doc/html/qtsingleapplication.qhp +++ /dev/null @@ -1,53 +0,0 @@ - - - com.nokia.qtsolutions.qtsingleapplication_head - qdoc - - qt - solutions - qtsingleapplication - - - qt - solutions - qtsingleapplication - -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - qtsingleapplication.html - index.html - qtsingleapplication-example-trivial.html - qtsinglecoreapplication.html - qtsingleapplication-example-loader.html - qtsinglecoreapplication-example-console.html - classic.css - images/qt-logo.png - - - diff --git a/src/qtsingleapplication/doc/html/qtsinglecoreapplication-example-console.html b/src/qtsingleapplication/doc/html/qtsinglecoreapplication-example-console.html deleted file mode 100644 index 18a9ae89..00000000 --- a/src/qtsingleapplication/doc/html/qtsinglecoreapplication-example-console.html +++ /dev/null @@ -1,118 +0,0 @@ - - - - - - A non-GUI example - - - - - - - -
  Home

A non-GUI example
-

-

This example shows how to use the single-application functionality in a console application. It does not require the QtGui library at all.

-

The only differences from the GUI application usage demonstrated in the other examples are:

-

1) The .pro file should include qtsinglecoreapplication.pri instead of qtsingleapplication.pri

-

2) The class name is QtSingleCoreApplication instead of QtSingleApplication.

-

3) No calls are made regarding window activation, for obvious reasons.

-

console.pro:

-
 TEMPLATE   = app
- CONFIG    += console
- SOURCES   += main.cpp
- include(../../src/qtsinglecoreapplication.pri)
- QT -= gui
-

main.cpp:

-
 /****************************************************************************
- **
- ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
- ** Contact: http://www.qt-project.org/legal
- **
- ** This file is part of the Qt Solutions component.
- **
- ** You may use this file under the terms of the BSD license as follows:
- **
- ** "Redistribution and use in source and binary forms, with or without
- ** modification, are permitted provided that the following conditions are
- ** met:
- **   * Redistributions of source code must retain the above copyright
- **     notice, this list of conditions and the following disclaimer.
- **   * Redistributions in binary form must reproduce the above copyright
- **     notice, this list of conditions and the following disclaimer in
- **     the documentation and/or other materials provided with the
- **     distribution.
- **   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
- **     the names of its contributors may be used to endorse or promote
- **     products derived from this software without specific prior written
- **     permission.
- **
- ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
- **
- ****************************************************************************/
-
- #include "qtsinglecoreapplication.h"
- #include <QtCore/QDebug>
-
- void report(const QString& msg)
- {
-     qDebug("[%i] %s", (int)QCoreApplication::applicationPid(), qPrintable(msg));
- }
-
- class MainClass : public QObject
- {
-     Q_OBJECT
- public:
-     MainClass()
-         : QObject()
-         {}
-
- public slots:
-     void handleMessage(const QString& message)
-         {
-             report( "Message received: \"" + message + "\"");
-         }
- };
-
- int main(int argc, char **argv)
- {
-     report("Starting up");
-
-     QtSingleCoreApplication app(argc, argv);
-
-     if (app.isRunning()) {
-         QString msg(QString("Hi master, I am %1.").arg(QCoreApplication::applicationPid()));
-         bool sentok = app.sendMessage(msg, 2000);
-         QString rep("Another instance is running, so I will exit.");
-         rep += sentok ? " Message sent ok." : " Message sending failed; the other instance may be frozen.";
-         report(rep);
-         return 0;
-     } else {
-         report("No other instance is running; so I will.");
-         MainClass mainObj;
-         QObject::connect(&app, SIGNAL(messageReceived(const QString&)),
-                          &mainObj, SLOT(handleMessage(const QString&)));
-         return app.exec();
-     }
- }
-
- #include "main.moc"
-


- - - - -
Copyright © 2010 Nokia Corporation and/or its subsidiary(-ies)Trademarks
Qt Solutions
- diff --git a/src/qtsingleapplication/doc/html/qtsinglecoreapplication-members.html b/src/qtsingleapplication/doc/html/qtsinglecoreapplication-members.html deleted file mode 100644 index 69fb8581..00000000 --- a/src/qtsingleapplication/doc/html/qtsinglecoreapplication-members.html +++ /dev/null @@ -1,126 +0,0 @@ - - - - - - List of All Members for QtSingleCoreApplication - - - - - - - -
  Home

List of All Members for QtSingleCoreApplication

-

This is the complete list of members for QtSingleCoreApplication, including inherited members.

-

- -
-

-


- - - - -
Copyright © 2010 Nokia Corporation and/or its subsidiary(-ies)Trademarks
Qt Solutions
- diff --git a/src/qtsingleapplication/doc/html/qtsinglecoreapplication.html b/src/qtsingleapplication/doc/html/qtsinglecoreapplication.html deleted file mode 100644 index a20cf2f4..00000000 --- a/src/qtsingleapplication/doc/html/qtsinglecoreapplication.html +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - QtSingleCoreApplication Class Reference - - - - - - - -
  Home

QtSingleCoreApplication Class Reference

-

A variant of the QtSingleApplication class for non-GUI applications. More...

-
 #include <QtSingleCoreApplication>

Inherits QCoreApplication.

- -
- -

Public Functions

- - - - - -
QtSingleCoreApplication ( int & argc, char ** argv )
QtSingleCoreApplication ( const QString & appId, int & argc, char ** argv )
QString id () const
bool isRunning ()
- -
- -

Public Slots

- - -
bool sendMessage ( const QString & message, int timeout = 5000 )
- -
- -

Signals

- - -
void messageReceived ( const QString & message )
- -

Additional Inherited Members

- - -
-

Detailed Description

-

A variant of the QtSingleApplication class for non-GUI applications.

-

This class is a variant of QtSingleApplication suited for use in console (non-GUI) applications. It is an extension of QCoreApplication (instead of QApplication). It does not require the QtGui library.

-

The API and usage is identical to QtSingleApplication, except that functions relating to the "activation window" are not present, for obvious reasons. Please refer to the QtSingleApplication documentation for explanation of the usage.

-

A QtSingleCoreApplication instance can communicate to a QtSingleApplication instance if they share the same application id. Hence, this class can be used to create a light-weight command-line tool that sends commands to a GUI application.

-

See also QtSingleApplication.

-
-

Member Function Documentation

-

QtSingleCoreApplication::QtSingleCoreApplication ( int & argc, char ** argv )

-

Creates a QtSingleCoreApplication object. The application identifier will be QCoreApplication::applicationFilePath(). argc and argv are passed on to the QCoreAppliation constructor.

-

QtSingleCoreApplication::QtSingleCoreApplication ( const QString & appId, int & argc, char ** argv )

-

Creates a QtSingleCoreApplication object with the application identifier appId. argc and argv are passed on to the QCoreAppliation constructor.

-

QString QtSingleCoreApplication::id () const

-

Returns the application identifier. Two processes with the same identifier will be regarded as instances of the same application.

-

bool QtSingleCoreApplication::isRunning ()

-

Returns true if another instance of this application is running; otherwise false.

-

This function does not find instances of this application that are being run by a different user (on Windows: that are running in another session).

-

See also sendMessage().

-

void QtSingleCoreApplication::messageReceived ( const QString & message )   [signal]

-

This signal is emitted when the current instance receives a message from another instance of this application.

-

See also sendMessage().

-

bool QtSingleCoreApplication::sendMessage ( const QString & message, int timeout = 5000 )   [slot]

-

Tries to send the text message to the currently running instance. The QtSingleCoreApplication object in the running instance will emit the messageReceived() signal when it receives the message.

-

This function returns true if the message has been sent to, and processed by, the current instance. If there is no instance currently running, or if the running instance fails to process the message within timeout milliseconds, this function return false.

-

See also isRunning() and messageReceived().

-


- - - - -
Copyright © 2010 Nokia Corporation and/or its subsidiary(-ies)Trademarks
Qt Solutions
- diff --git a/src/qtsingleapplication/doc/images/qt-logo.png b/src/qtsingleapplication/doc/images/qt-logo.png deleted file mode 100644 index 794162f5af58e7b38beebf472842fc9703ede19b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4075 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 diff --git a/src/qtsingleapplication/doc/index.qdoc b/src/qtsingleapplication/doc/index.qdoc deleted file mode 100644 index 6a2a768a..00000000 --- a/src/qtsingleapplication/doc/index.qdoc +++ /dev/null @@ -1,87 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Solutions component. -** -** $QT_BEGIN_LICENSE:BSD$ -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names -** of its contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -/*! - \page index.html - \title Single Application - - \section1 Description - - The QtSingleApplication component provides support - for applications that can be only started once per user. - - - - For some applications it is useful or even critical that they are started - only once by any user. Future attempts to start the application should - activate any already running instance, and possibly perform requested - actions, e.g. loading a file, in that instance. - - The QtSingleApplication class provides an interface to detect a running - instance, and to send command strings to that instance. - For console (non-GUI) applications, the QtSingleCoreApplication variant is provided, which avoids dependency on QtGui. - - - - - \section1 Classes - \list - \i QtSingleApplication \i QtSingleCoreApplication\endlist - - \section1 Examples - \list - \i \link qtsingleapplication-example-trivial.html A Trivial Example \endlink \i \link qtsingleapplication-example-loader.html Loading Documents \endlink \i \link qtsinglecoreapplication-example-console.html A Non-GUI Example \endlink \endlist - - - - - - - \section1 Tested platforms - \list - \i Qt 4.4, 4.5 / Windows XP / MSVC.NET 2005 - \i Qt 4.4, 4.5 / Linux / gcc - \i Qt 4.4, 4.5 / MacOS X 10.5 / gcc - \endlist - - - - -*/ diff --git a/src/qtsingleapplication/examples/console/console.pro b/src/qtsingleapplication/examples/console/console.pro deleted file mode 100644 index e0390e23..00000000 --- a/src/qtsingleapplication/examples/console/console.pro +++ /dev/null @@ -1,5 +0,0 @@ -TEMPLATE = app -CONFIG += console -SOURCES += main.cpp -include(../../src/qtsinglecoreapplication.pri) -QT -= gui diff --git a/src/qtsingleapplication/examples/console/console.qdoc b/src/qtsingleapplication/examples/console/console.qdoc deleted file mode 100644 index 77b0d736..00000000 --- a/src/qtsingleapplication/examples/console/console.qdoc +++ /dev/null @@ -1,65 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Solutions component. -** -** $QT_BEGIN_LICENSE:BSD$ -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names -** of its contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -/*! \page qtsinglecoreapplication-example-console.html - \title A non-GUI example - - This example shows how to use the single-application functionality - in a console application. It does not require the \c QtGui library - at all. - - The only differences from the GUI application usage demonstrated - in the other examples are: - - 1) The \c.pro file should include \c qtsinglecoreapplication.pri - instead of \c qtsingleapplication.pri - - 2) The class name is \c QtSingleCoreApplication instead of \c - QtSingleApplication. - - 3) No calls are made regarding window activation, for obvious reasons. - - console.pro: - \quotefile console/console.pro - - main.cpp: - \quotefile console/main.cpp - -*/ diff --git a/src/qtsingleapplication/examples/console/main.cpp b/src/qtsingleapplication/examples/console/main.cpp deleted file mode 100644 index 652feb93..00000000 --- a/src/qtsingleapplication/examples/console/main.cpp +++ /dev/null @@ -1,89 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Solutions component. -** -** $QT_BEGIN_LICENSE:BSD$ -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names -** of its contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - - -#include "qtsinglecoreapplication.h" -#include - - -void report(const QString& msg) -{ - qDebug("[%i] %s", (int)QCoreApplication::applicationPid(), qPrintable(msg)); -} - -class MainClass : public QObject -{ - Q_OBJECT -public: - MainClass() - : QObject() - {} - -public slots: - void handleMessage(const QString& message) - { - report( "Message received: \"" + message + "\""); - } -}; - -int main(int argc, char **argv) -{ - report("Starting up"); - - QtSingleCoreApplication app(argc, argv); - - if (app.isRunning()) { - QString msg(QString("Hi master, I am %1.").arg(QCoreApplication::applicationPid())); - bool sentok = app.sendMessage(msg, 2000); - QString rep("Another instance is running, so I will exit."); - rep += sentok ? " Message sent ok." : " Message sending failed; the other instance may be frozen."; - report(rep); - return 0; - } else { - report("No other instance is running; so I will."); - MainClass mainObj; - QObject::connect(&app, SIGNAL(messageReceived(const QString&)), - &mainObj, SLOT(handleMessage(const QString&))); - return app.exec(); - } -} - - -#include "main.moc" diff --git a/src/qtsingleapplication/examples/examples.pro b/src/qtsingleapplication/examples/examples.pro deleted file mode 100644 index 36b8fd38..00000000 --- a/src/qtsingleapplication/examples/examples.pro +++ /dev/null @@ -1,4 +0,0 @@ -TEMPLATE = subdirs -SUBDIRS = trivial \ - loader \ - console diff --git a/src/qtsingleapplication/examples/loader/file1.qsl b/src/qtsingleapplication/examples/loader/file1.qsl deleted file mode 100644 index 50fcd26d..00000000 --- a/src/qtsingleapplication/examples/loader/file1.qsl +++ /dev/null @@ -1 +0,0 @@ -File 1 diff --git a/src/qtsingleapplication/examples/loader/file2.qsl b/src/qtsingleapplication/examples/loader/file2.qsl deleted file mode 100644 index 4475433e..00000000 --- a/src/qtsingleapplication/examples/loader/file2.qsl +++ /dev/null @@ -1 +0,0 @@ -File 2 diff --git a/src/qtsingleapplication/examples/loader/loader.pro b/src/qtsingleapplication/examples/loader/loader.pro deleted file mode 100644 index 3e52586b..00000000 --- a/src/qtsingleapplication/examples/loader/loader.pro +++ /dev/null @@ -1,6 +0,0 @@ -greaterThan(QT_MAJOR_VERSION, 4): QT += printsupport -TEMPLATE = app - -include(../../src/qtsingleapplication.pri) - -SOURCES += main.cpp diff --git a/src/qtsingleapplication/examples/loader/loader.qdoc b/src/qtsingleapplication/examples/loader/loader.qdoc deleted file mode 100644 index bfd15d06..00000000 --- a/src/qtsingleapplication/examples/loader/loader.qdoc +++ /dev/null @@ -1,81 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Solutions component. -** -** $QT_BEGIN_LICENSE:BSD$ -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names -** of its contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -/*! \page qtsingleapplication-example-loader.html - \title Loading Documents - - The application in this example loads or prints the documents - passed as commandline parameters to further instances of this - application. - - \quotefromfile loader/main.cpp - \printuntil }; - The user interface in this application is a QMainWindow subclass - with a QMdiArea as the central widget. It implements a slot - \c handleMessage() that will be connected to the messageReceived() - signal of the QtSingleApplication class. - - \printuntil } - The MainWindow constructor creates a minimal user interface. - - \printto case Print: - The handleMessage() slot interprets the message passed in as a - filename that can be prepended with \e /print to indicate that - the file should just be printed rather than loaded. - - \printto #include - Loading the file will also activate the window. - - \printto mw - The \c main entry point function creates a QtSingleApplication - object, and creates a message to send to a running instance - of the application. If the message was sent successfully the - process exits immediately. - - \printuntil } - If the message could not be sent the application starts up. Note - that \c false is passed to the call to setActivationWindow() to - prevent automatic activation for every message received, e.g. when - the application should just print a file. Instead, the message - handling function determines whether activation is requested, and - signals that by emitting the needToShow() signal. This is then - simply connected directly to QtSingleApplication's - activateWindow() slot. -*/ diff --git a/src/qtsingleapplication/examples/loader/main.cpp b/src/qtsingleapplication/examples/loader/main.cpp deleted file mode 100644 index f55e57ac..00000000 --- a/src/qtsingleapplication/examples/loader/main.cpp +++ /dev/null @@ -1,152 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Solutions component. -** -** $QT_BEGIN_LICENSE:BSD$ -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names -** of its contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include - -class MainWindow : public QMainWindow -{ - Q_OBJECT -public: - MainWindow(); - -public slots: - void handleMessage(const QString& message); - -signals: - void needToShow(); - -private: - QMdiArea *workspace; -}; - -MainWindow::MainWindow() -{ - workspace = new QMdiArea(this); - - setCentralWidget(workspace); -} - -void MainWindow::handleMessage(const QString& message) -{ - enum Action { - Nothing, - Open, - Print - } action; - - action = Nothing; - QString filename = message; - if (message.toLower().startsWith("/print ")) { - filename = filename.mid(7); - action = Print; - } else if (!message.isEmpty()) { - action = Open; - } - if (action == Nothing) { - emit needToShow(); - return; - } - - QFile file(filename); - QString contents; - if (file.open(QIODevice::ReadOnly)) - contents = file.readAll(); - else - contents = "[[Error: Could not load file " + filename + "]]"; - - QTextEdit *view = new QTextEdit; - view->setPlainText(contents); - - switch(action) { - case Print: - { - QPrinter printer; - view->print(&printer); - delete view; - } - break; - - case Open: - { - workspace->addSubWindow(view); - view->setWindowTitle(message); - view->show(); - emit needToShow(); - } - break; - default: - break; - }; -} - -#include "main.moc" - -int main(int argc, char **argv) -{ - QtSingleApplication instance("File loader QtSingleApplication example", argc, argv); - QString message; - for (int a = 1; a < argc; ++a) { - message += argv[a]; - if (a < argc-1) - message += " "; - } - - if (instance.sendMessage(message)) - return 0; - - MainWindow mw; - mw.handleMessage(message); - mw.show(); - - QObject::connect(&instance, SIGNAL(messageReceived(const QString&)), - &mw, SLOT(handleMessage(const QString&))); - - instance.setActivationWindow(&mw, false); - QObject::connect(&mw, SIGNAL(needToShow()), &instance, SLOT(activateWindow())); - - return instance.exec(); -} diff --git a/src/qtsingleapplication/examples/trivial/main.cpp b/src/qtsingleapplication/examples/trivial/main.cpp deleted file mode 100644 index 69e102f3..00000000 --- a/src/qtsingleapplication/examples/trivial/main.cpp +++ /dev/null @@ -1,78 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Solutions component. -** -** $QT_BEGIN_LICENSE:BSD$ -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names -** of its contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include -#include - -class TextEdit : public QTextEdit -{ - Q_OBJECT -public: - TextEdit(QWidget *parent = 0) - : QTextEdit(parent) - {} -public slots: - void append(const QString &str) - { - QTextEdit::append(str); - } -}; - -#include "main.moc" - - - -int main(int argc, char **argv) -{ - QtSingleApplication instance(argc, argv); - if (instance.sendMessage("Wake up!")) - return 0; - - TextEdit logview; - logview.setReadOnly(true); - logview.show(); - - instance.setActivationWindow(&logview); - - QObject::connect(&instance, SIGNAL(messageReceived(const QString&)), - &logview, SLOT(append(const QString&))); - - return instance.exec(); -} diff --git a/src/qtsingleapplication/examples/trivial/trivial.pro b/src/qtsingleapplication/examples/trivial/trivial.pro deleted file mode 100644 index 673497a4..00000000 --- a/src/qtsingleapplication/examples/trivial/trivial.pro +++ /dev/null @@ -1,5 +0,0 @@ -TEMPLATE = app - -include(../../src/qtsingleapplication.pri) - -SOURCES += main.cpp diff --git a/src/qtsingleapplication/examples/trivial/trivial.qdoc b/src/qtsingleapplication/examples/trivial/trivial.qdoc deleted file mode 100644 index 9491cb65..00000000 --- a/src/qtsingleapplication/examples/trivial/trivial.qdoc +++ /dev/null @@ -1,76 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Solutions component. -** -** $QT_BEGIN_LICENSE:BSD$ -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names -** of its contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -/*! \page qtsingleapplication-example-trivial.html - \title A Trivial Example - - The application in this example has a log-view that displays - messages sent by further instances of the same application. - - The example demonstrates the use of the QtSingleApplication - class to detect and communicate with a running instance of - the application using the sendMessage() API. The messageReceived() - signal is used to display received messages in a QTextEdit log. - - \quotefromfile trivial/main.cpp - \printuntil instance - The example has only the \c main entry point function. - A QtSingleApplication object is created immediately. - - \printuntil return - If another instance of this application is already running, - sendMessage() will succeed, and this instance just exits - immediately. - - \printuntil show() - Otherwise the instance continues as normal and creates the - user interface. - - \printuntil return instance.exec(); - The \c logview object is also set as the application's activation - window. Every time a message is received, the window will be raised - and activated automatically. - - The messageReceived() signal is also connected to the QTextEdit's - append() slot. Every message received from further instances of - this application will be displayed in the log. - - Finally the event loop is entered. -*/ diff --git a/src/qtsingleapplication/qtsingleapplication.pro b/src/qtsingleapplication/qtsingleapplication.pro deleted file mode 100644 index 07257c5d..00000000 --- a/src/qtsingleapplication/qtsingleapplication.pro +++ /dev/null @@ -1,5 +0,0 @@ -TEMPLATE=subdirs -CONFIG += ordered -include(common.pri) -qtsingleapplication-uselib:SUBDIRS=buildlib -SUBDIRS+=examples diff --git a/src/qtsingleapplication/src/QtLockedFile b/src/qtsingleapplication/src/QtLockedFile deleted file mode 100644 index 16b48ba9..00000000 --- a/src/qtsingleapplication/src/QtLockedFile +++ /dev/null @@ -1 +0,0 @@ -#include "qtlockedfile.h" diff --git a/src/qtsingleapplication/src/QtSingleApplication b/src/qtsingleapplication/src/QtSingleApplication deleted file mode 100644 index d111bf72..00000000 --- a/src/qtsingleapplication/src/QtSingleApplication +++ /dev/null @@ -1 +0,0 @@ -#include "qtsingleapplication.h" diff --git a/src/qtsingleapplication/src/qtlocalpeer.cpp b/src/qtsingleapplication/src/qtlocalpeer.cpp deleted file mode 100644 index f3c4546b..00000000 --- a/src/qtsingleapplication/src/qtlocalpeer.cpp +++ /dev/null @@ -1,213 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Solutions component. -** -** $QT_BEGIN_LICENSE:BSD$ -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names -** of its contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - - -#include "qtlocalpeer.h" -#include -#include -#include - -#if defined(Q_OS_WIN) -#include -#include -typedef BOOL(WINAPI*PProcessIdToSessionId)(DWORD,DWORD*); -static PProcessIdToSessionId pProcessIdToSessionId = 0; -#endif -#if defined(Q_OS_UNIX) -#include -#include -#include -#endif - -namespace QtLP_Private { -#include "qtlockedfile.cpp" -#if defined(Q_OS_WIN) -#include "qtlockedfile_win.cpp" -#else -#include "qtlockedfile_unix.cpp" -#endif -} - -const char* QtLocalPeer::ack = "ack"; - -QtLocalPeer::QtLocalPeer(QObject* parent, const QString &appId) - : QObject(parent), id(appId) -{ - QString prefix = id; - if (id.isEmpty()) { - id = QCoreApplication::applicationFilePath(); -#if defined(Q_OS_WIN) - id = id.toLower(); -#endif - prefix = id.section(QLatin1Char('/'), -1); - } - prefix.remove(QRegExp("[^a-zA-Z]")); - prefix.truncate(6); - - QByteArray idc = id.toUtf8(); - quint16 idNum = qChecksum(idc.constData(), idc.size()); - socketName = QLatin1String("qtsingleapp-") + prefix - + QLatin1Char('-') + QString::number(idNum, 16); - -#if defined(Q_OS_WIN) - if (!pProcessIdToSessionId) { - QLibrary lib("kernel32"); - pProcessIdToSessionId = (PProcessIdToSessionId)lib.resolve("ProcessIdToSessionId"); - } - if (pProcessIdToSessionId) { - DWORD sessionId = 0; - pProcessIdToSessionId(GetCurrentProcessId(), &sessionId); - socketName += QLatin1Char('-') + QString::number(sessionId, 16); - } -#else - socketName += QLatin1Char('-') + QString::number(::getuid(), 16); -#endif - - server = new QLocalServer(this); - QString lockName = QDir(QDir::tempPath()).absolutePath() - + QLatin1Char('/') + socketName - + QLatin1String("-lockfile"); - lockFile.setFileName(lockName); - lockFile.open(QIODevice::ReadWrite); -} - - - -bool QtLocalPeer::isClient() -{ - if (lockFile.isLocked()) - return false; - - if (!lockFile.lock(QtLP_Private::QtLockedFile::WriteLock, false)) - return true; - - bool res = server->listen(socketName); -#if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(4,5,0)) - // ### Workaround - if (!res && server->serverError() == QAbstractSocket::AddressInUseError) { - QFile::remove(QDir::cleanPath(QDir::tempPath())+QLatin1Char('/')+socketName); - res = server->listen(socketName); - } -#endif - if (!res) - qWarning("QtSingleCoreApplication: listen on local socket failed, %s", qPrintable(server->errorString())); - QObject::connect(server, SIGNAL(newConnection()), SLOT(receiveConnection())); - return false; -} - - -bool QtLocalPeer::sendMessage(const QString &message, int timeout) -{ - if (!isClient()) - return false; - - QLocalSocket socket; - bool connOk = false; - for(int i = 0; i < 2; i++) { - // Try twice, in case the other instance is just starting up - socket.connectToServer(socketName); - connOk = socket.waitForConnected(timeout/2); - if (connOk || i) - break; - int ms = 250; -#if defined(Q_OS_WIN) - Sleep(DWORD(ms)); -#else - struct timespec ts = { ms / 1000, (ms % 1000) * 1000 * 1000 }; - nanosleep(&ts, NULL); -#endif - } - if (!connOk) - return false; - - QByteArray uMsg(message.toUtf8()); - QDataStream ds(&socket); - ds.writeBytes(uMsg.constData(), uMsg.size()); - bool res = socket.waitForBytesWritten(timeout); - if (res) { - res &= socket.waitForReadyRead(timeout); // wait for ack - if (res) - res &= (socket.read(qstrlen(ack)) == ack); - } - return res; -} - - -void QtLocalPeer::receiveConnection() -{ - QLocalSocket* socket = server->nextPendingConnection(); - if (!socket) - return; - - while (true) { - if (socket->state() == QLocalSocket::UnconnectedState) { - qWarning("QtLocalPeer: Peer disconnected"); - delete socket; - return; - } - if (socket->bytesAvailable() >= qint64(sizeof(quint32))) - break; - socket->waitForReadyRead(); - } - - QDataStream ds(socket); - QByteArray uMsg; - quint32 remaining; - ds >> remaining; - uMsg.resize(remaining); - int got = 0; - char* uMsgBuf = uMsg.data(); - do { - got = ds.readRawData(uMsgBuf, remaining); - remaining -= got; - uMsgBuf += got; - } while (remaining && got >= 0 && socket->waitForReadyRead(2000)); - if (got < 0) { - qWarning("QtLocalPeer: Message reception failed %s", socket->errorString().toLatin1().constData()); - delete socket; - return; - } - QString message(QString::fromUtf8(uMsg)); - socket->write(ack, qstrlen(ack)); - socket->waitForBytesWritten(1000); - socket->waitForDisconnected(1000); // make sure client reads ack - delete socket; - emit messageReceived(message); //### (might take a long time to return) -} diff --git a/src/qtsingleapplication/src/qtlocalpeer.h b/src/qtsingleapplication/src/qtlocalpeer.h deleted file mode 100644 index 1b533b1a..00000000 --- a/src/qtsingleapplication/src/qtlocalpeer.h +++ /dev/null @@ -1,77 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Solutions component. -** -** $QT_BEGIN_LICENSE:BSD$ -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names -** of its contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef QTLOCALPEER_H -#define QTLOCALPEER_H - -#include -#include -#include - -#include "qtlockedfile.h" - -class QtLocalPeer : public QObject -{ - Q_OBJECT - -public: - QtLocalPeer(QObject *parent = 0, const QString &appId = QString()); - bool isClient(); - bool sendMessage(const QString &message, int timeout); - QString applicationId() const - { return id; } - -Q_SIGNALS: - void messageReceived(const QString &message); - -protected Q_SLOTS: - void receiveConnection(); - -protected: - QString id; - QString socketName; - QLocalServer* server; - QtLP_Private::QtLockedFile lockFile; - -private: - static const char* ack; -}; - -#endif // QTLOCALPEER_H diff --git a/src/qtsingleapplication/src/qtlockedfile.cpp b/src/qtsingleapplication/src/qtlockedfile.cpp deleted file mode 100644 index c142a863..00000000 --- a/src/qtsingleapplication/src/qtlockedfile.cpp +++ /dev/null @@ -1,193 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Solutions component. -** -** $QT_BEGIN_LICENSE:BSD$ -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names -** of its contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "qtlockedfile.h" - -/*! - \class QtLockedFile - - \brief The QtLockedFile class extends QFile with advisory locking - functions. - - A file may be locked in read or write mode. Multiple instances of - \e QtLockedFile, created in multiple processes running on the same - machine, may have a file locked in read mode. Exactly one instance - may have it locked in write mode. A read and a write lock cannot - exist simultaneously on the same file. - - The file locks are advisory. This means that nothing prevents - another process from manipulating a locked file using QFile or - file system functions offered by the OS. Serialization is only - guaranteed if all processes that access the file use - QLockedFile. Also, while holding a lock on a file, a process - must not open the same file again (through any API), or locks - can be unexpectedly lost. - - The lock provided by an instance of \e QtLockedFile is released - whenever the program terminates. This is true even when the - program crashes and no destructors are called. -*/ - -/*! \enum QtLockedFile::LockMode - - This enum describes the available lock modes. - - \value ReadLock A read lock. - \value WriteLock A write lock. - \value NoLock Neither a read lock nor a write lock. -*/ - -/*! - Constructs an unlocked \e QtLockedFile object. This constructor - behaves in the same way as \e QFile::QFile(). - - \sa QFile::QFile() -*/ -QtLockedFile::QtLockedFile() - : QFile() -{ -#ifdef Q_OS_WIN - wmutex = 0; - rmutex = 0; -#endif - m_lock_mode = NoLock; -} - -/*! - Constructs an unlocked QtLockedFile object with file \a name. This - constructor behaves in the same way as \e QFile::QFile(const - QString&). - - \sa QFile::QFile() -*/ -QtLockedFile::QtLockedFile(const QString &name) - : QFile(name) -{ -#ifdef Q_OS_WIN - wmutex = 0; - rmutex = 0; -#endif - m_lock_mode = NoLock; -} - -/*! - Opens the file in OpenMode \a mode. - - This is identical to QFile::open(), with the one exception that the - Truncate mode flag is disallowed. Truncation would conflict with the - advisory file locking, since the file would be modified before the - write lock is obtained. If truncation is required, use resize(0) - after obtaining the write lock. - - Returns true if successful; otherwise false. - - \sa QFile::open(), QFile::resize() -*/ -bool QtLockedFile::open(OpenMode mode) -{ - if (mode & QIODevice::Truncate) { - qWarning("QtLockedFile::open(): Truncate mode not allowed."); - return false; - } - return QFile::open(mode); -} - -/*! - Returns \e true if this object has a in read or write lock; - otherwise returns \e false. - - \sa lockMode() -*/ -bool QtLockedFile::isLocked() const -{ - return m_lock_mode != NoLock; -} - -/*! - Returns the type of lock currently held by this object, or \e - QtLockedFile::NoLock. - - \sa isLocked() -*/ -QtLockedFile::LockMode QtLockedFile::lockMode() const -{ - return m_lock_mode; -} - -/*! - \fn bool QtLockedFile::lock(LockMode mode, bool block = true) - - Obtains a lock of type \a mode. The file must be opened before it - can be locked. - - If \a block is true, this function will block until the lock is - aquired. If \a block is false, this function returns \e false - immediately if the lock cannot be aquired. - - If this object already has a lock of type \a mode, this function - returns \e true immediately. If this object has a lock of a - different type than \a mode, the lock is first released and then a - new lock is obtained. - - This function returns \e true if, after it executes, the file is - locked by this object, and \e false otherwise. - - \sa unlock(), isLocked(), lockMode() -*/ - -/*! - \fn bool QtLockedFile::unlock() - - Releases a lock. - - If the object has no lock, this function returns immediately. - - This function returns \e true if, after it executes, the file is - not locked by this object, and \e false otherwise. - - \sa lock(), isLocked(), lockMode() -*/ - -/*! - \fn QtLockedFile::~QtLockedFile() - - Destroys the \e QtLockedFile object. If any locks were held, they - are released. -*/ diff --git a/src/qtsingleapplication/src/qtlockedfile.h b/src/qtsingleapplication/src/qtlockedfile.h deleted file mode 100644 index 84c18e5c..00000000 --- a/src/qtsingleapplication/src/qtlockedfile.h +++ /dev/null @@ -1,97 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Solutions component. -** -** $QT_BEGIN_LICENSE:BSD$ -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names -** of its contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef QTLOCKEDFILE_H -#define QTLOCKEDFILE_H - -#include -#ifdef Q_OS_WIN -#include -#endif - -#if defined(Q_OS_WIN) -# if !defined(QT_QTLOCKEDFILE_EXPORT) && !defined(QT_QTLOCKEDFILE_IMPORT) -# define QT_QTLOCKEDFILE_EXPORT -# elif defined(QT_QTLOCKEDFILE_IMPORT) -# if defined(QT_QTLOCKEDFILE_EXPORT) -# undef QT_QTLOCKEDFILE_EXPORT -# endif -# define QT_QTLOCKEDFILE_EXPORT __declspec(dllimport) -# elif defined(QT_QTLOCKEDFILE_EXPORT) -# undef QT_QTLOCKEDFILE_EXPORT -# define QT_QTLOCKEDFILE_EXPORT __declspec(dllexport) -# endif -#else -# define QT_QTLOCKEDFILE_EXPORT -#endif - -namespace QtLP_Private { - -class QT_QTLOCKEDFILE_EXPORT QtLockedFile : public QFile -{ -public: - enum LockMode { NoLock = 0, ReadLock, WriteLock }; - - QtLockedFile(); - QtLockedFile(const QString &name); - ~QtLockedFile(); - - bool open(OpenMode mode); - - bool lock(LockMode mode, bool block = true); - bool unlock(); - bool isLocked() const; - LockMode lockMode() const; - -private: -#ifdef Q_OS_WIN - Qt::HANDLE wmutex; - Qt::HANDLE rmutex; - QVector rmutexes; - QString mutexname; - - Qt::HANDLE getMutexHandle(int idx, bool doCreate); - bool waitMutex(Qt::HANDLE mutex, bool doBlock); - -#endif - LockMode m_lock_mode; -}; -} -#endif diff --git a/src/qtsingleapplication/src/qtlockedfile_unix.cpp b/src/qtsingleapplication/src/qtlockedfile_unix.cpp deleted file mode 100644 index 976c1b9e..00000000 --- a/src/qtsingleapplication/src/qtlockedfile_unix.cpp +++ /dev/null @@ -1,115 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Solutions component. -** -** $QT_BEGIN_LICENSE:BSD$ -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names -** of its contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include -#include -#include -#include - -#include "qtlockedfile.h" - -bool QtLockedFile::lock(LockMode mode, bool block) -{ - if (!isOpen()) { - qWarning("QtLockedFile::lock(): file is not opened"); - return false; - } - - if (mode == NoLock) - return unlock(); - - if (mode == m_lock_mode) - return true; - - if (m_lock_mode != NoLock) - unlock(); - - struct flock fl; - fl.l_whence = SEEK_SET; - fl.l_start = 0; - fl.l_len = 0; - fl.l_type = (mode == ReadLock) ? F_RDLCK : F_WRLCK; - int cmd = block ? F_SETLKW : F_SETLK; - int ret = fcntl(handle(), cmd, &fl); - - if (ret == -1) { - if (errno != EINTR && errno != EAGAIN) - qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno)); - return false; - } - - - m_lock_mode = mode; - return true; -} - - -bool QtLockedFile::unlock() -{ - if (!isOpen()) { - qWarning("QtLockedFile::unlock(): file is not opened"); - return false; - } - - if (!isLocked()) - return true; - - struct flock fl; - fl.l_whence = SEEK_SET; - fl.l_start = 0; - fl.l_len = 0; - fl.l_type = F_UNLCK; - int ret = fcntl(handle(), F_SETLKW, &fl); - - if (ret == -1) { - qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno)); - return false; - } - - m_lock_mode = NoLock; - return true; -} - -QtLockedFile::~QtLockedFile() -{ - if (isOpen()) - unlock(); -} - diff --git a/src/qtsingleapplication/src/qtlockedfile_win.cpp b/src/qtsingleapplication/src/qtlockedfile_win.cpp deleted file mode 100644 index 5e212620..00000000 --- a/src/qtsingleapplication/src/qtlockedfile_win.cpp +++ /dev/null @@ -1,211 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Solutions component. -** -** $QT_BEGIN_LICENSE:BSD$ -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names -** of its contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "qtlockedfile.h" -#include -#include - -#define MUTEX_PREFIX "QtLockedFile mutex " -// Maximum number of concurrent read locks. Must not be greater than MAXIMUM_WAIT_OBJECTS -#define MAX_READERS MAXIMUM_WAIT_OBJECTS - -#if QT_VERSION >= 0x050000 -#define QT_WA(unicode, ansi) unicode -#endif - -Qt::HANDLE QtLockedFile::getMutexHandle(int idx, bool doCreate) -{ - if (mutexname.isEmpty()) { - QFileInfo fi(*this); - mutexname = QString::fromLatin1(MUTEX_PREFIX) - + fi.absoluteFilePath().toLower(); - } - QString mname(mutexname); - if (idx >= 0) - mname += QString::number(idx); - - Qt::HANDLE mutex; - if (doCreate) { - QT_WA( { mutex = CreateMutexW(NULL, FALSE, (TCHAR*)mname.utf16()); }, - { mutex = CreateMutexA(NULL, FALSE, mname.toLocal8Bit().constData()); } ); - if (!mutex) { - qErrnoWarning("QtLockedFile::lock(): CreateMutex failed"); - return 0; - } - } - else { - QT_WA( { mutex = OpenMutexW(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, (TCHAR*)mname.utf16()); }, - { mutex = OpenMutexA(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, mname.toLocal8Bit().constData()); } ); - if (!mutex) { - if (GetLastError() != ERROR_FILE_NOT_FOUND) - qErrnoWarning("QtLockedFile::lock(): OpenMutex failed"); - return 0; - } - } - return mutex; -} - -bool QtLockedFile::waitMutex(Qt::HANDLE mutex, bool doBlock) -{ - Q_ASSERT(mutex); - DWORD res = WaitForSingleObject(mutex, doBlock ? INFINITE : 0); - switch (res) { - case WAIT_OBJECT_0: - case WAIT_ABANDONED: - return true; - break; - case WAIT_TIMEOUT: - break; - default: - qErrnoWarning("QtLockedFile::lock(): WaitForSingleObject failed"); - } - return false; -} - - - -bool QtLockedFile::lock(LockMode mode, bool block) -{ - if (!isOpen()) { - qWarning("QtLockedFile::lock(): file is not opened"); - return false; - } - - if (mode == NoLock) - return unlock(); - - if (mode == m_lock_mode) - return true; - - if (m_lock_mode != NoLock) - unlock(); - - if (!wmutex && !(wmutex = getMutexHandle(-1, true))) - return false; - - if (!waitMutex(wmutex, block)) - return false; - - if (mode == ReadLock) { - int idx = 0; - for (; idx < MAX_READERS; idx++) { - rmutex = getMutexHandle(idx, false); - if (!rmutex || waitMutex(rmutex, false)) - break; - CloseHandle(rmutex); - } - bool ok = true; - if (idx >= MAX_READERS) { - qWarning("QtLockedFile::lock(): too many readers"); - rmutex = 0; - ok = false; - } - else if (!rmutex) { - rmutex = getMutexHandle(idx, true); - if (!rmutex || !waitMutex(rmutex, false)) - ok = false; - } - if (!ok && rmutex) { - CloseHandle(rmutex); - rmutex = 0; - } - ReleaseMutex(wmutex); - if (!ok) - return false; - } - else { - Q_ASSERT(rmutexes.isEmpty()); - for (int i = 0; i < MAX_READERS; i++) { - Qt::HANDLE mutex = getMutexHandle(i, false); - if (mutex) - rmutexes.append(mutex); - } - if (rmutexes.size()) { - DWORD res = WaitForMultipleObjects(rmutexes.size(), rmutexes.constData(), - TRUE, block ? INFINITE : 0); - if (res != WAIT_OBJECT_0 && res != WAIT_ABANDONED) { - if (res != WAIT_TIMEOUT) - qErrnoWarning("QtLockedFile::lock(): WaitForMultipleObjects failed"); - m_lock_mode = WriteLock; // trick unlock() to clean up - semiyucky - unlock(); - return false; - } - } - } - - m_lock_mode = mode; - return true; -} - -bool QtLockedFile::unlock() -{ - if (!isOpen()) { - qWarning("QtLockedFile::unlock(): file is not opened"); - return false; - } - - if (!isLocked()) - return true; - - if (m_lock_mode == ReadLock) { - ReleaseMutex(rmutex); - CloseHandle(rmutex); - rmutex = 0; - } - else { - foreach(Qt::HANDLE mutex, rmutexes) { - ReleaseMutex(mutex); - CloseHandle(mutex); - } - rmutexes.clear(); - ReleaseMutex(wmutex); - } - - m_lock_mode = QtLockedFile::NoLock; - return true; -} - -QtLockedFile::~QtLockedFile() -{ - if (isOpen()) - unlock(); - if (wmutex) - CloseHandle(wmutex); -} diff --git a/src/qtsingleapplication/src/qtsingleapplication.cpp b/src/qtsingleapplication/src/qtsingleapplication.cpp deleted file mode 100644 index d0fb15d7..00000000 --- a/src/qtsingleapplication/src/qtsingleapplication.cpp +++ /dev/null @@ -1,347 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Solutions component. -** -** $QT_BEGIN_LICENSE:BSD$ -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names -** of its contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - - -#include "qtsingleapplication.h" -#include "qtlocalpeer.h" -#include - - -/*! - \class QtSingleApplication qtsingleapplication.h - \brief The QtSingleApplication class provides an API to detect and - communicate with running instances of an application. - - This class allows you to create applications where only one - instance should be running at a time. I.e., if the user tries to - launch another instance, the already running instance will be - activated instead. Another usecase is a client-server system, - where the first started instance will assume the role of server, - and the later instances will act as clients of that server. - - By default, the full path of the executable file is used to - determine whether two processes are instances of the same - application. You can also provide an explicit identifier string - that will be compared instead. - - The application should create the QtSingleApplication object early - in the startup phase, and call isRunning() to find out if another - instance of this application is already running. If isRunning() - returns false, it means that no other instance is running, and - this instance has assumed the role as the running instance. In - this case, the application should continue with the initialization - of the application user interface before entering the event loop - with exec(), as normal. - - The messageReceived() signal will be emitted when the running - application receives messages from another instance of the same - application. When a message is received it might be helpful to the - user to raise the application so that it becomes visible. To - facilitate this, QtSingleApplication provides the - setActivationWindow() function and the activateWindow() slot. - - If isRunning() returns true, another instance is already - running. It may be alerted to the fact that another instance has - started by using the sendMessage() function. Also data such as - startup parameters (e.g. the name of the file the user wanted this - new instance to open) can be passed to the running instance with - this function. Then, the application should terminate (or enter - client mode). - - If isRunning() returns true, but sendMessage() fails, that is an - indication that the running instance is frozen. - - Here's an example that shows how to convert an existing - application to use QtSingleApplication. It is very simple and does - not make use of all QtSingleApplication's functionality (see the - examples for that). - - \code - // Original - int main(int argc, char **argv) - { - QApplication app(argc, argv); - - MyMainWidget mmw; - mmw.show(); - return app.exec(); - } - - // Single instance - int main(int argc, char **argv) - { - QtSingleApplication app(argc, argv); - - if (app.isRunning()) - return !app.sendMessage(someDataString); - - MyMainWidget mmw; - app.setActivationWindow(&mmw); - mmw.show(); - return app.exec(); - } - \endcode - - Once this QtSingleApplication instance is destroyed (normally when - the process exits or crashes), when the user next attempts to run the - application this instance will not, of course, be encountered. The - next instance to call isRunning() or sendMessage() will assume the - role as the new running instance. - - For console (non-GUI) applications, QtSingleCoreApplication may be - used instead of this class, to avoid the dependency on the QtGui - library. - - \sa QtSingleCoreApplication -*/ - - -void QtSingleApplication::sysInit(const QString &appId) -{ - actWin = 0; - peer = new QtLocalPeer(this, appId); - connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&))); -} - - -/*! - Creates a QtSingleApplication object. The application identifier - will be QCoreApplication::applicationFilePath(). \a argc, \a - argv, and \a GUIenabled are passed on to the QAppliation constructor. - - If you are creating a console application (i.e. setting \a - GUIenabled to false), you may consider using - QtSingleCoreApplication instead. -*/ - -QtSingleApplication::QtSingleApplication(int &argc, char **argv, bool GUIenabled) - : QApplication(argc, argv, GUIenabled) -{ - sysInit(); -} - - -/*! - Creates a QtSingleApplication object with the application - identifier \a appId. \a argc and \a argv are passed on to the - QAppliation constructor. -*/ - -QtSingleApplication::QtSingleApplication(const QString &appId, int &argc, char **argv) - : QApplication(argc, argv) -{ - sysInit(appId); -} - -#if QT_VERSION < 0x050000 - -/*! - Creates a QtSingleApplication object. The application identifier - will be QCoreApplication::applicationFilePath(). \a argc, \a - argv, and \a type are passed on to the QAppliation constructor. -*/ -QtSingleApplication::QtSingleApplication(int &argc, char **argv, Type type) - : QApplication(argc, argv, type) -{ - sysInit(); -} - - -# if defined(Q_WS_X11) -/*! - Special constructor for X11, ref. the documentation of - QApplication's corresponding constructor. The application identifier - will be QCoreApplication::applicationFilePath(). \a dpy, \a visual, - and \a cmap are passed on to the QApplication constructor. -*/ -QtSingleApplication::QtSingleApplication(Display* dpy, Qt::HANDLE visual, Qt::HANDLE cmap) - : QApplication(dpy, visual, cmap) -{ - sysInit(); -} - -/*! - Special constructor for X11, ref. the documentation of - QApplication's corresponding constructor. The application identifier - will be QCoreApplication::applicationFilePath(). \a dpy, \a argc, \a - argv, \a visual, and \a cmap are passed on to the QApplication - constructor. -*/ -QtSingleApplication::QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap) - : QApplication(dpy, argc, argv, visual, cmap) -{ - sysInit(); -} - -/*! - Special constructor for X11, ref. the documentation of - QApplication's corresponding constructor. The application identifier - will be \a appId. \a dpy, \a argc, \a - argv, \a visual, and \a cmap are passed on to the QApplication - constructor. -*/ -QtSingleApplication::QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap) - : QApplication(dpy, argc, argv, visual, cmap) -{ - sysInit(appId); -} -# endif // Q_WS_X11 -#endif // QT_VERSION < 0x050000 - - -/*! - Returns true if another instance of this application is running; - otherwise false. - - This function does not find instances of this application that are - being run by a different user (on Windows: that are running in - another session). - - \sa sendMessage() -*/ - -bool QtSingleApplication::isRunning() -{ - return peer->isClient(); -} - - -/*! - Tries to send the text \a message to the currently running - instance. The QtSingleApplication object in the running instance - will emit the messageReceived() signal when it receives the - message. - - This function returns true if the message has been sent to, and - processed by, the current instance. If there is no instance - currently running, or if the running instance fails to process the - message within \a timeout milliseconds, this function return false. - - \sa isRunning(), messageReceived() -*/ -bool QtSingleApplication::sendMessage(const QString &message, int timeout) -{ - return peer->sendMessage(message, timeout); -} - - -/*! - Returns the application identifier. Two processes with the same - identifier will be regarded as instances of the same application. -*/ -QString QtSingleApplication::id() const -{ - return peer->applicationId(); -} - - -/*! - Sets the activation window of this application to \a aw. The - activation window is the widget that will be activated by - activateWindow(). This is typically the application's main window. - - If \a activateOnMessage is true (the default), the window will be - activated automatically every time a message is received, just prior - to the messageReceived() signal being emitted. - - \sa activateWindow(), messageReceived() -*/ - -void QtSingleApplication::setActivationWindow(QWidget* aw, bool activateOnMessage) -{ - actWin = aw; - if (activateOnMessage) - connect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow())); - else - disconnect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow())); -} - - -/*! - Returns the applications activation window if one has been set by - calling setActivationWindow(), otherwise returns 0. - - \sa setActivationWindow() -*/ -QWidget* QtSingleApplication::activationWindow() const -{ - return actWin; -} - - -/*! - De-minimizes, raises, and activates this application's activation window. - This function does nothing if no activation window has been set. - - This is a convenience function to show the user that this - application instance has been activated when he has tried to start - another instance. - - This function should typically be called in response to the - messageReceived() signal. By default, that will happen - automatically, if an activation window has been set. - - \sa setActivationWindow(), messageReceived(), initialize() -*/ -void QtSingleApplication::activateWindow() -{ - if (actWin) { - actWin->setWindowState(actWin->windowState() & ~Qt::WindowMinimized); - actWin->raise(); - actWin->activateWindow(); - } -} - - -/*! - \fn void QtSingleApplication::messageReceived(const QString& message) - - This signal is emitted when the current instance receives a \a - message from another instance of this application. - - \sa sendMessage(), setActivationWindow(), activateWindow() -*/ - - -/*! - \fn void QtSingleApplication::initialize(bool dummy = true) - - \obsolete -*/ diff --git a/src/qtsingleapplication/src/qtsingleapplication.h b/src/qtsingleapplication/src/qtsingleapplication.h deleted file mode 100644 index 049406f7..00000000 --- a/src/qtsingleapplication/src/qtsingleapplication.h +++ /dev/null @@ -1,105 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Solutions component. -** -** $QT_BEGIN_LICENSE:BSD$ -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names -** of its contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef QTSINGLEAPPLICATION_H -#define QTSINGLEAPPLICATION_H - -#include - -class QtLocalPeer; - -#if defined(Q_OS_WIN) -# if !defined(QT_QTSINGLEAPPLICATION_EXPORT) && !defined(QT_QTSINGLEAPPLICATION_IMPORT) -# define QT_QTSINGLEAPPLICATION_EXPORT -# elif defined(QT_QTSINGLEAPPLICATION_IMPORT) -# if defined(QT_QTSINGLEAPPLICATION_EXPORT) -# undef QT_QTSINGLEAPPLICATION_EXPORT -# endif -# define QT_QTSINGLEAPPLICATION_EXPORT __declspec(dllimport) -# elif defined(QT_QTSINGLEAPPLICATION_EXPORT) -# undef QT_QTSINGLEAPPLICATION_EXPORT -# define QT_QTSINGLEAPPLICATION_EXPORT __declspec(dllexport) -# endif -#else -# define QT_QTSINGLEAPPLICATION_EXPORT -#endif - -class QT_QTSINGLEAPPLICATION_EXPORT QtSingleApplication : public QApplication -{ - Q_OBJECT - -public: - QtSingleApplication(int &argc, char **argv, bool GUIenabled = true); - QtSingleApplication(const QString &id, int &argc, char **argv); -#if QT_VERSION < 0x050000 - QtSingleApplication(int &argc, char **argv, Type type); -# if defined(Q_WS_X11) - QtSingleApplication(Display* dpy, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0); - QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE cmap= 0); - QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0); -# endif // Q_WS_X11 -#endif // QT_VERSION < 0x050000 - - bool isRunning(); - QString id() const; - - void setActivationWindow(QWidget* aw, bool activateOnMessage = true); - QWidget* activationWindow() const; - - // Obsolete: - void initialize(bool dummy = true) - { isRunning(); Q_UNUSED(dummy) } - -public Q_SLOTS: - bool sendMessage(const QString &message, int timeout = 5000); - void activateWindow(); - - -Q_SIGNALS: - void messageReceived(const QString &message); - - -private: - void sysInit(const QString &appId = QString()); - QtLocalPeer *peer; - QWidget *actWin; -}; - -#endif // QTSINGLEAPPLICATION_H diff --git a/src/qtsingleapplication/src/qtsingleapplication.pri b/src/qtsingleapplication/src/qtsingleapplication.pri deleted file mode 100644 index 6f2bced9..00000000 --- a/src/qtsingleapplication/src/qtsingleapplication.pri +++ /dev/null @@ -1,17 +0,0 @@ -include(../common.pri) -INCLUDEPATH += $$PWD -DEPENDPATH += $$PWD -QT *= network -greaterThan(QT_MAJOR_VERSION, 4): QT *= widgets - -qtsingleapplication-uselib:!qtsingleapplication-buildlib { - LIBS += -L$$QTSINGLEAPPLICATION_LIBDIR -l$$QTSINGLEAPPLICATION_LIBNAME -} else { - SOURCES += $$PWD/qtsingleapplication.cpp $$PWD/qtlocalpeer.cpp - HEADERS += $$PWD/qtsingleapplication.h $$PWD/qtlocalpeer.h -} - -win32 { - contains(TEMPLATE, lib):contains(CONFIG, shared):DEFINES += QT_QTSINGLEAPPLICATION_EXPORT - else:qtsingleapplication-uselib:DEFINES += QT_QTSINGLEAPPLICATION_IMPORT -} diff --git a/src/qtsingleapplication/src/qtsinglecoreapplication.cpp b/src/qtsingleapplication/src/qtsinglecoreapplication.cpp deleted file mode 100644 index 56345373..00000000 --- a/src/qtsingleapplication/src/qtsinglecoreapplication.cpp +++ /dev/null @@ -1,149 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Solutions component. -** -** $QT_BEGIN_LICENSE:BSD$ -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names -** of its contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - - -#include "qtsinglecoreapplication.h" -#include "qtlocalpeer.h" - -/*! - \class QtSingleCoreApplication qtsinglecoreapplication.h - \brief A variant of the QtSingleApplication class for non-GUI applications. - - This class is a variant of QtSingleApplication suited for use in - console (non-GUI) applications. It is an extension of - QCoreApplication (instead of QApplication). It does not require - the QtGui library. - - The API and usage is identical to QtSingleApplication, except that - functions relating to the "activation window" are not present, for - obvious reasons. Please refer to the QtSingleApplication - documentation for explanation of the usage. - - A QtSingleCoreApplication instance can communicate to a - QtSingleApplication instance if they share the same application - id. Hence, this class can be used to create a light-weight - command-line tool that sends commands to a GUI application. - - \sa QtSingleApplication -*/ - -/*! - Creates a QtSingleCoreApplication object. The application identifier - will be QCoreApplication::applicationFilePath(). \a argc and \a - argv are passed on to the QCoreAppliation constructor. -*/ - -QtSingleCoreApplication::QtSingleCoreApplication(int &argc, char **argv) - : QCoreApplication(argc, argv) -{ - peer = new QtLocalPeer(this); - connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&))); -} - - -/*! - Creates a QtSingleCoreApplication object with the application - identifier \a appId. \a argc and \a argv are passed on to the - QCoreAppliation constructor. -*/ -QtSingleCoreApplication::QtSingleCoreApplication(const QString &appId, int &argc, char **argv) - : QCoreApplication(argc, argv) -{ - peer = new QtLocalPeer(this, appId); - connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&))); -} - - -/*! - Returns true if another instance of this application is running; - otherwise false. - - This function does not find instances of this application that are - being run by a different user (on Windows: that are running in - another session). - - \sa sendMessage() -*/ - -bool QtSingleCoreApplication::isRunning() -{ - return peer->isClient(); -} - - -/*! - Tries to send the text \a message to the currently running - instance. The QtSingleCoreApplication object in the running instance - will emit the messageReceived() signal when it receives the - message. - - This function returns true if the message has been sent to, and - processed by, the current instance. If there is no instance - currently running, or if the running instance fails to process the - message within \a timeout milliseconds, this function return false. - - \sa isRunning(), messageReceived() -*/ - -bool QtSingleCoreApplication::sendMessage(const QString &message, int timeout) -{ - return peer->sendMessage(message, timeout); -} - - -/*! - Returns the application identifier. Two processes with the same - identifier will be regarded as instances of the same application. -*/ - -QString QtSingleCoreApplication::id() const -{ - return peer->applicationId(); -} - - -/*! - \fn void QtSingleCoreApplication::messageReceived(const QString& message) - - This signal is emitted when the current instance receives a \a - message from another instance of this application. - - \sa sendMessage() -*/ diff --git a/src/qtsingleapplication/src/qtsinglecoreapplication.h b/src/qtsingleapplication/src/qtsinglecoreapplication.h deleted file mode 100644 index b87fffe4..00000000 --- a/src/qtsingleapplication/src/qtsinglecoreapplication.h +++ /dev/null @@ -1,71 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Solutions component. -** -** $QT_BEGIN_LICENSE:BSD$ -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names -** of its contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef QTSINGLECOREAPPLICATION_H -#define QTSINGLECOREAPPLICATION_H - -#include - -class QtLocalPeer; - -class QtSingleCoreApplication : public QCoreApplication -{ - Q_OBJECT - -public: - QtSingleCoreApplication(int &argc, char **argv); - QtSingleCoreApplication(const QString &id, int &argc, char **argv); - - bool isRunning(); - QString id() const; - -public Q_SLOTS: - bool sendMessage(const QString &message, int timeout = 5000); - - -Q_SIGNALS: - void messageReceived(const QString &message); - - -private: - QtLocalPeer* peer; -}; - -#endif // QTSINGLECOREAPPLICATION_H diff --git a/src/qtsingleapplication/src/qtsinglecoreapplication.pri b/src/qtsingleapplication/src/qtsinglecoreapplication.pri deleted file mode 100644 index d2d6cc3e..00000000 --- a/src/qtsingleapplication/src/qtsinglecoreapplication.pri +++ /dev/null @@ -1,10 +0,0 @@ -INCLUDEPATH += $$PWD -DEPENDPATH += $$PWD -HEADERS += $$PWD/qtsinglecoreapplication.h $$PWD/qtlocalpeer.h -SOURCES += $$PWD/qtsinglecoreapplication.cpp $$PWD/qtlocalpeer.cpp - -QT *= network - -win32:contains(TEMPLATE, lib):contains(CONFIG, shared) { - DEFINES += QT_QTSINGLECOREAPPLICATION_EXPORT=__declspec(dllexport) -} diff --git a/src/singleapplication/CHANGELOG.md b/src/singleapplication/CHANGELOG.md new file mode 100644 index 00000000..4368d0f5 --- /dev/null +++ b/src/singleapplication/CHANGELOG.md @@ -0,0 +1,306 @@ +Changelog +========= + +If by accident I have forgotten to credit someone in the CHANGELOG, email me and I will fix it. + + +__3.3.1__ +--------- + +* Added support for _AppImage_ dynamic executable paths. - _Michael Klein_ + +__3.3.0__ +--------- + +* Fixed message fragmentation issue causing crashes and incorrectly / inconsistently received messages. - _Nils Jeisecke_ + +__3.2.0__ +--------- + +* Added support for Qt 6 - _Jonas Kvinge_ +* Fixed warning in `Qt 5.9` with `min`/`max` functions on Windows - _Nick Korotysh_ +* Fix return value of connectToPrimary() when connect is successful - _Jonas Kvinge_ +* Fix build issue with MinGW GCC pedantic mode - _Iakov Kirilenko_ +* Fixed conversion from `int` to `quint32` and Clang Tidy warnings - _Hennadii Chernyshchyk_ + +__3.1.5__ +--------- + +* Improved library stability in edge cases and very rapid process initialisation +* Fixed Bug where the shared memory block may have been modified without a lock +* Fixed Bug causing `instanceStarted()` to not get emitted when a second instance + has been started before the primary has initiated it's `QLocalServer`. + +__3.1.4__ +--------- +* Officially supporting and build-testing against Qt 5.15 +* Fixed an MSVC C4996 warning that suggests using `strncpy_s`. + + _Hennadii Chernyshchyk_ + +__3.1.3.1__ +--------- +* CMake build system improvements +* Fixed Clang Tidy warnings + + _Hennadii Chernyshchyk_ + +__3.1.3__ +--------- +* Improved `CMakeLists.txt` + + _Hennadii Chernyshchyk_ + +__3.1.2__ +--------- + +* Fix a crash when exiting an application on Android and iOS + + _Emeric Grange_ + +__3.1.1a__ +---------- + +* Added currentUser() method that returns the user the current instance is running as. + + _Leander Schulten_ + +__3.1.0a__ +---------- + +* Added primaryUser() method that returns the user the primary instance is running as. + +__3.0.19__ +---------- + +* Fixed code warning for depricated functions in Qt 5.10 related to `QTime` and `qrand()`. + + _Hennadii Chernyshchyk_ + _Anton Filimonov_ + _Jonas Kvinge_ + +__3.0.18__ +---------- + +* Fallback to standard QApplication class on iOS and Android systems where + the library is not supported. + +* Added Build CI tests to verify the library builds successfully on Linux, Windows and MacOS across multiple Qt versions. + + _Anton Filimonov_ + +__3.0.17__ +---------- + +* Fixed compilation warning/error caused by `geteuid()` on unix based systems. + + _Iakov Kirilenko_ + +* Added CMake support + + _Hennadii Chernyshchyk_ + +__3.0.16__ +---------- + +* Use geteuid and getpwuid to get username on Unix, fallback to environment variable. + + _Jonas Kvinge_ + +__3.0.15__ +---------- + +* Bug Fix: sendMessage() might return false even though data was actually written. + + _Jonas Kvinge_ + +__3.0.14__ +---------- + +* Fixed uninitialised variables in the `SingleApplicationPrivate` constructor. + +__3.0.13a__ +---------- + +* Process socket events asynchronously +* Fix undefined variable error on Windows + + _Francis Giraldeau_ + +__3.0.12a__ +---------- + +* Removed signal handling. + +__3.0.11a__ +---------- + +* Fixed bug where the message sent by the second process was not received + correctly when the message is sent immediately following a connection. + + _Francis Giraldeau_ + +* Refactored code and implemented shared memory block consistency checks + via `qChecksum()` (CRC-16). +* Explicit `qWarning` and `qCritical` when the library is unable to initialise + correctly. + +__3.0.10__ +---------- + +* Removed C style casts and eliminated all clang warnings. Fixed `instanceId` + reading from only one byte in the message deserialization. Cleaned up + serialization code using `QDataStream`. Changed connection type to use + `quint8 enum` rather than `char`. +* Renamed `SingleAppConnectionType` to `ConnectionType`. Added initialization + values to all `ConnectionType` enum cases. + + _Jedidiah Buck McCready_ + +__3.0.9__ +--------- + +* Added SingleApplicationPrivate::primaryPid() as a solution to allow + bringing the primary window of an application to the foreground on + Windows. + + _Eelco van Dam from Peacs BV_ + +__3.0.8__ +--------- + +* Bug fix - changed QApplication::instance() to QCoreApplication::instance() + + _Evgeniy Bazhenov_ + +__3.0.7a__ +---------- + +* Fixed compilation error with Mingw32 in MXE thanks to Vitaly Tonkacheyev. +* Removed QMutex used for thread safe behaviour. The implementation now uses + QCoreApplication::instance() to get an instance to SingleApplication for + memory deallocation. + +__3.0.6a__ +---------- + +* Reverted GetUserName API usage on Windows. Fixed bug with missing library. +* Fixed bug in the Calculator example, preventing it's window to be raised + on Windows. + + Special thanks to Charles Gunawan. + +__3.0.5a__ +---------- + +* Fixed a memory leak in the SingleApplicationPrivate destructor. + + _Sergei Moiseev_ + +__3.0.4a__ +---------- + +* Fixed shadow and uninitialised variable warnings. + + _Paul Walmsley_ + +__3.0.3a__ +---------- + +* Removed Microsoft Windows specific code for getting username due to + multiple problems and compiler differences on Windows platforms. On + Windows the shared memory block in User mode now includes the user's + home path (which contains the user's username). + +* Explicitly getting absolute path of the user's home directory as on Unix + a relative path (`~`) may be returned. + +__3.0.2a__ +---------- + +* Fixed bug on Windows when username containing wide characters causes the + library to crash. + + _Le Liu_ + +__3.0.1a__ +---------- + +* Allows the application path and version to be excluded from the server name + hash. The following flags were added for this purpose: + * `SingleApplication::Mode::ExcludeAppVersion` + * `SingleApplication::Mode::ExcludeAppPath` +* Allow a non elevated process to connect to a local server created by an + elevated process run by the same user on Windows +* Fixes a problem with upper case letters in paths on Windows + + _Le Liu_ + +__v3.0a__ +--------- + +* Deprecated secondary instances count. +* Added a sendMessage() method to send a message to the primary instance. +* Added a receivedMessage() signal, emitted when a message is received from a + secondary instance. +* The SingleApplication constructor's third parameter is now a bool + specifying if the current instance should be allowed to run as a secondary + instance if there is already a primary instance. +* The SingleApplication constructor accept a fourth parameter specifying if + the SingleApplication block should be User-wide or System-wide. +* SingleApplication no longer relies on `applicationName` and + `organizationName` to be set. It instead concatenates all of the following + data and computes a `SHA256` hash which is used as the key of the + `QSharedMemory` block and the `QLocalServer`. Since at least + `applicationFilePath` is always present there is no need to explicitly set + any of the following prior to initialising `SingleApplication`. + * `QCoreApplication::applicationName` + * `QCoreApplication::applicationVersion` + * `QCoreApplication::applicationFilePath` + * `QCoreApplication::organizationName` + * `QCoreApplication::organizationDomain` + * User name or home directory path if in User mode +* The primary instance is no longer notified when a secondary instance had + been started by default. A `Mode` flag for this feature exists. +* Added `instanceNumber()` which represents a unique identifier for each + secondary instance started. When called from the primary instance will + return `0`. + +__v2.4__ +-------- + +* Stability improvements +* Support for secondary instances. +* The library now recovers safely after the primary process has crashed +and the shared memory had not been deleted. + +__v2.3__ +-------- + +* Improved pimpl design and inheritance safety. + + _Vladislav Pyatnichenko_ + +__v2.2__ +-------- + +* The `QAPPLICATION_CLASS` macro can now be defined in the file including the +Single Application header or with a `DEFINES+=` statement in the project file. + +__v2.1__ +-------- + +* A race condition can no longer occur when starting two processes nearly + simultaneously. + + Fix issue [#3](https://github.com/itay-grudev/SingleApplication/issues/3) + +__v2.0__ +-------- + +* SingleApplication is now being passed a reference to `argc` instead of a + copy. + + Fix issue [#1](https://github.com/itay-grudev/SingleApplication/issues/1) + +* Improved documentation. diff --git a/src/singleapplication/CMakeLists.txt b/src/singleapplication/CMakeLists.txt new file mode 100644 index 00000000..ae1b1439 --- /dev/null +++ b/src/singleapplication/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.7.0) + +project(SingleApplication LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) + +add_library(${PROJECT_NAME} STATIC + singleapplication.cpp + singleapplication_p.cpp +) +add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) + +if(NOT QT_DEFAULT_MAJOR_VERSION) + set(QT_DEFAULT_MAJOR_VERSION 5 CACHE STRING "Qt version to use (5 or 6), defaults to 5") +endif() + +# Find dependencies +set(QT_COMPONENTS Core Network) +set(QT_LIBRARIES Qt${QT_DEFAULT_MAJOR_VERSION}::Core Qt${QT_DEFAULT_MAJOR_VERSION}::Network) + +if(QAPPLICATION_CLASS STREQUAL QApplication) + list(APPEND QT_COMPONENTS Widgets) + list(APPEND QT_LIBRARIES Qt${QT_DEFAULT_MAJOR_VERSION}::Widgets) +elseif(QAPPLICATION_CLASS STREQUAL QGuiApplication) + list(APPEND QT_COMPONENTS Gui) + list(APPEND QT_LIBRARIES Qt${QT_DEFAULT_MAJOR_VERSION}::Gui) +else() + set(QAPPLICATION_CLASS QCoreApplication) +endif() + +find_package(Qt${QT_DEFAULT_MAJOR_VERSION} COMPONENTS ${QT_COMPONENTS} REQUIRED) + +target_link_libraries(${PROJECT_NAME} PUBLIC ${QT_LIBRARIES}) + +if(WIN32) + target_link_libraries(${PROJECT_NAME} PRIVATE advapi32) +endif() + +target_compile_definitions(${PROJECT_NAME} PUBLIC QAPPLICATION_CLASS=${QAPPLICATION_CLASS}) +target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/src/singleapplication/LICENSE b/src/singleapplication/LICENSE new file mode 100644 index 00000000..a82e5a68 --- /dev/null +++ b/src/singleapplication/LICENSE @@ -0,0 +1,24 @@ +The MIT License (MIT) + +Copyright (c) Itay Grudev 2015 - 2020 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +Note: Some of the examples include code not distributed under the terms of the +MIT License. diff --git a/src/singleapplication/README.md b/src/singleapplication/README.md new file mode 100644 index 00000000..a76bada3 --- /dev/null +++ b/src/singleapplication/README.md @@ -0,0 +1,305 @@ +SingleApplication +================= +[![CI](https://github.com/itay-grudev/SingleApplication/workflows/CI:%20Build%20Test/badge.svg)](https://github.com/itay-grudev/SingleApplication/actions) + +This is a replacement of the QtSingleApplication for `Qt5` and `Qt6`. + +Keeps the Primary Instance of your Application and kills each subsequent +instances. It can (if enabled) spawn secondary (non-related to the primary) +instances and can send data to the primary instance from secondary instances. + +Usage +----- + +The `SingleApplication` class inherits from whatever `Q[Core|Gui]Application` +class you specify via the `QAPPLICATION_CLASS` macro (`QCoreApplication` is the +default). Further usage is similar to the use of the `Q[Core|Gui]Application` +classes. + +You can use the library as if you use any other `QCoreApplication` derived +class: + +```cpp +#include +#include + +int main( int argc, char* argv[] ) +{ + SingleApplication app( argc, argv ); + + return app.exec(); +} +``` + +To include the library files I would recommend that you add it as a git +submodule to your project. Here is how: + +```bash +git submodule add git@github.com:itay-grudev/SingleApplication.git singleapplication +``` + +**Qmake:** + +Then include the `singleapplication.pri` file in your `.pro` project file. + +```qmake +include(singleapplication/singleapplication.pri) +DEFINES += QAPPLICATION_CLASS=QApplication +``` + +**CMake:** + +Then include the subdirectory in your `CMakeLists.txt` project file. + +```cmake +set(QAPPLICATION_CLASS QApplication CACHE STRING "Inheritance class for SingleApplication") +add_subdirectory(src/third-party/singleapplication) +target_link_libraries(${PROJECT_NAME} SingleApplication::SingleApplication) +``` + + +The library sets up a `QLocalServer` and a `QSharedMemory` block. The first +instance of your Application is your Primary Instance. It would check if the +shared memory block exists and if not it will start a `QLocalServer` and listen +for connections. Each subsequent instance of your application would check if the +shared memory block exists and if it does, it will connect to the QLocalServer +to notify the primary instance that a new instance had been started, after which +it would terminate with status code `0`. In the Primary Instance +`SingleApplication` would emit the `instanceStarted()` signal upon detecting +that a new instance had been started. + +The library uses `stdlib` to terminate the program with the `exit()` function. + +Also don't forget to specify which `QCoreApplication` class your app is using if it +is not `QCoreApplication` as in examples above. + +The `Instance Started` signal +----------------------------- + +The SingleApplication class implements a `instanceStarted()` signal. You can +bind to that signal to raise your application's window when a new instance had +been started, for example. + +```cpp +// window is a QWindow instance +QObject::connect( + &app, + &SingleApplication::instanceStarted, + &window, + &QWindow::raise +); +``` + +Using `SingleApplication::instance()` is a neat way to get the +`SingleApplication` instance for binding to it's signals anywhere in your +program. + +__Note:__ On Windows the ability to bring the application windows to the +foreground is restricted. See [Windows specific implementations](Windows.md) +for a workaround and an example implementation. + + +Secondary Instances +------------------- + +If you want to be able to launch additional Secondary Instances (not related to +your Primary Instance) you have to enable that with the third parameter of the +`SingleApplication` constructor. The default is `false` meaning no Secondary +Instances. Here is an example of how you would start a Secondary Instance send +a message with the command line arguments to the primary instance and then shut +down. + +```cpp +int main(int argc, char *argv[]) +{ + SingleApplication app( argc, argv, true ); + + if( app.isSecondary() ) { + app.sendMessage( app.arguments().join(' ')).toUtf8() ); + app.exit( 0 ); + } + + return app.exec(); +} +``` + +*__Note:__ A secondary instance won't cause the emission of the +`instanceStarted()` signal by default. See `SingleApplication::Mode` for more +details.* + +You can check whether your instance is a primary or secondary with the following +methods: + +```cpp +app.isPrimary(); +// or +app.isSecondary(); +``` + +*__Note:__ If your Primary Instance is terminated a newly launched instance +will replace the Primary one even if the Secondary flag has been set.* + +Examples +-------- + +There are three examples provided in this repository: + +* Basic example that prevents a secondary instance from starting [`examples/basic`](https://github.com/itay-grudev/SingleApplication/tree/master/examples/basic) +* An example of a graphical application raising it's parent window [`examples/calculator`](https://github.com/itay-grudev/SingleApplication/tree/master/examples/calculator) +* A console application sending the primary instance it's command line parameters [`examples/sending_arguments`](https://github.com/itay-grudev/SingleApplication/tree/master/examples/sending_arguments) + +API +--- + +### Members + +```cpp +SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 100, QString userData = QString() ) +``` + +Depending on whether `allowSecondary` is set, this constructor may terminate +your app if there is already a primary instance running. Additional `Options` +can be specified to set whether the SingleApplication block should work +user-wide or system-wide. Additionally the `Mode::SecondaryNotification` may be +used to notify the primary instance whenever a secondary instance had been +started (disabled by default). `timeout` specifies the maximum time in +milliseconds to wait for blocking operations. Setting `userData` provides additional data that will isolate this instance from other instances that do not have the same (or any) user data set. + +*__Note:__ `argc` and `argv` may be changed as Qt removes arguments that it +recognizes.* + +*__Note:__ `Mode::SecondaryNotification` only works if set on both the primary +and the secondary instance.* + +*__Note:__ Operating system can restrict the shared memory blocks to the same +user, in which case the User/System modes will have no effect and the block will +be user wide.* + +--- + +```cpp +bool SingleApplication::sendMessage( QByteArray message, int timeout = 100 ) +``` + +Sends `message` to the Primary Instance. Uses `timeout` as a the maximum timeout +in milliseconds for blocking functions. Returns `true` if the message has been sent +successfully. If the message can't be sent or the function timeouts - returns `false`. + +--- + +```cpp +bool SingleApplication::isPrimary() +``` + +Returns if the instance is the primary instance. + +--- + +```cpp +bool SingleApplication::isSecondary() +``` +Returns if the instance is a secondary instance. + +--- + +```cpp +quint32 SingleApplication::instanceId() +``` + +Returns a unique identifier for the current instance. + +--- + +```cpp +qint64 SingleApplication::primaryPid() +``` + +Returns the process ID (PID) of the primary instance. + +--- + +```cpp +QString SingleApplication::primaryUser() +``` + +Returns the username the primary instance is running as. + +--- + +```cpp +QString SingleApplication::currentUser() +``` + +Returns the username the current instance is running as. + +### Signals + +```cpp +void SingleApplication::instanceStarted() +``` + +Triggered whenever a new instance had been started, except for secondary +instances if the `Mode::SecondaryNotification` flag is not specified. + +--- + +```cpp +void SingleApplication::receivedMessage( quint32 instanceId, QByteArray message ) +``` + +Triggered whenever there is a message received from a secondary instance. + +--- + +### Flags + +```cpp +enum SingleApplication::Mode +``` + +* `Mode::User` - The SingleApplication block should apply user wide. This adds + user specific data to the key used for the shared memory and server name. + This is the default functionality. +* `Mode::System` – The SingleApplication block applies system-wide. +* `Mode::SecondaryNotification` – Whether to trigger `instanceStarted()` even + whenever secondary instances are started. +* `Mode::ExcludeAppPath` – Excludes the application path from the server name + (and memory block) hash. +* `Mode::ExcludeAppVersion` – Excludes the application version from the server + name (and memory block) hash. + +*__Note:__ `Mode::SecondaryNotification` only works if set on both the primary +and the secondary instance.* + +*__Note:__ Operating system can restrict the shared memory blocks to the same +user, in which case the User/System modes will have no effect and the block will +be user wide.* + +--- + +Versioning +---------- + +Each major version introduces either very significant changes or is not +backwards compatible with the previous version. Minor versions only add +additional features, bug fixes or performance improvements and are backwards +compatible with the previous release. See [`CHANGELOG.md`](CHANGELOG.md) for +more details. + +Implementation +-------------- + +The library is implemented with a QSharedMemory block which is thread safe and +guarantees a race condition will not occur. It also uses a QLocalSocket to +notify the main process that a new instance had been spawned and thus invoke the +`instanceStarted()` signal and for messaging the primary instance. + +Additionally the library can recover from being forcefully killed on *nix +systems and will reset the memory block given that there are no other +instances running. + +License +------- +This library and it's supporting documentation are released under +`The MIT License (MIT)` with the exception of the Qt calculator examples which +is distributed under the BSD license. diff --git a/src/singleapplication/SingleApplication b/src/singleapplication/SingleApplication new file mode 100644 index 00000000..8ead1a42 --- /dev/null +++ b/src/singleapplication/SingleApplication @@ -0,0 +1 @@ +#include "singleapplication.h" diff --git a/src/singleapplication/Windows.md b/src/singleapplication/Windows.md new file mode 100644 index 00000000..13c52da0 --- /dev/null +++ b/src/singleapplication/Windows.md @@ -0,0 +1,46 @@ +Windows Specific Implementations +================================ + +Setting the foreground window +----------------------------- + +In the `instanceStarted()` example in the `README` we demonstrated how an +application can bring it's primary instance window whenever a second copy +of the application is started. + +On Windows the ability to bring the application windows to the foreground is +restricted, see [`AllowSetForegroundWindow()`][AllowSetForegroundWindow] for more +details. + +The background process (the primary instance) can bring its windows to the +foreground if it is allowed by the current foreground process (the secondary +instance). To bypass this `SingleApplication` must be initialized with the +`allowSecondary` parameter set to `true` and the `options` parameter must +include `Mode::SecondaryNotification`, See `SingleApplication::Mode` for more +details. + +Here is an example: + +```cpp +if( app.isSecondary() ) { + // This API requires LIBS += User32.lib to be added to the project + AllowSetForegroundWindow( DWORD( app.primaryPid() ) ); +} + +if( app.isPrimary() ) { + QObject::connect( + &app, + &SingleApplication::instanceStarted, + this, + &App::instanceStarted + ); +} +``` + +```cpp +void App::instanceStarted() { + QApplication::setActiveWindow( [window/widget to set to the foreground] ); +} +``` + +[AllowSetForegroundWindow]: https://msdn.microsoft.com/en-us/library/windows/desktop/ms632668.aspx diff --git a/src/singleapplication/singleapplication.cpp b/src/singleapplication/singleapplication.cpp new file mode 100644 index 00000000..09e264ef --- /dev/null +++ b/src/singleapplication/singleapplication.cpp @@ -0,0 +1,271 @@ +// The MIT License (MIT) +// +// Copyright (c) Itay Grudev 2015 - 2020 +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#include +#include +#include + +#include "singleapplication.h" +#include "singleapplication_p.h" + +/** + * @brief Constructor. Checks and fires up LocalServer or closes the program + * if another instance already exists + * @param argc + * @param argv + * @param allowSecondary Whether to enable secondary instance support + * @param options Optional flags to toggle specific behaviour + * @param timeout Maximum time blocking functions are allowed during app load + */ +SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSecondary, Options options, int timeout, const QString &userData ) + : app_t( argc, argv ), d_ptr( new SingleApplicationPrivate( this ) ) +{ + Q_D( SingleApplication ); + +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) + // On Android and iOS since the library is not supported fallback to + // standard QApplication behaviour by simply returning at this point. + qWarning() << "SingleApplication is not supported on Android and iOS systems."; + return; +#endif + + // Store the current mode of the program + d->options = options; + + // Add any unique user data + if ( ! userData.isEmpty() ) + d->addAppData( userData ); + + // Generating an application ID used for identifying the shared memory + // block and QLocalServer + d->genBlockServerName(); + + // To mitigate QSharedMemory issues with large amount of processes + // attempting to attach at the same time + SingleApplicationPrivate::randomSleep(); + +#ifdef Q_OS_UNIX + // By explicitly attaching it and then deleting it we make sure that the + // memory is deleted even after the process has crashed on Unix. + d->memory = new QSharedMemory( d->blockServerName ); + d->memory->attach(); + delete d->memory; +#endif + // Guarantee thread safe behaviour with a shared memory block. + d->memory = new QSharedMemory( d->blockServerName ); + + // Create a shared memory block + if( d->memory->create( sizeof( InstancesInfo ) )){ + // Initialize the shared memory block + if( ! d->memory->lock() ){ + qCritical() << "SingleApplication: Unable to lock memory block after create."; + abortSafely(); + } + d->initializeMemoryBlock(); + } else { + if( d->memory->error() == QSharedMemory::AlreadyExists ){ + // Attempt to attach to the memory segment + if( ! d->memory->attach() ){ + qCritical() << "SingleApplication: Unable to attach to shared memory block."; + abortSafely(); + } + if( ! d->memory->lock() ){ + qCritical() << "SingleApplication: Unable to lock memory block after attach."; + abortSafely(); + } + } else { + qCritical() << "SingleApplication: Unable to create block."; + abortSafely(); + } + } + + auto *inst = static_cast( d->memory->data() ); + QElapsedTimer time; + time.start(); + + // Make sure the shared memory block is initialised and in consistent state + while( true ){ + // If the shared memory block's checksum is valid continue + if( d->blockChecksum() == inst->checksum ) break; + + // If more than 5s have elapsed, assume the primary instance crashed and + // assume it's position + if( time.elapsed() > 5000 ){ + qWarning() << "SingleApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure."; + d->initializeMemoryBlock(); + } + + // Otherwise wait for a random period and try again. The random sleep here + // limits the probability of a collision between two racing apps and + // allows the app to initialise faster + if( ! d->memory->unlock() ){ + qDebug() << "SingleApplication: Unable to unlock memory for random wait."; + qDebug() << d->memory->errorString(); + } + SingleApplicationPrivate::randomSleep(); + if( ! d->memory->lock() ){ + qCritical() << "SingleApplication: Unable to lock memory after random wait."; + abortSafely(); + } + } + + if( inst->primary == false ){ + d->startPrimary(); + if( ! d->memory->unlock() ){ + qDebug() << "SingleApplication: Unable to unlock memory after primary start."; + qDebug() << d->memory->errorString(); + } + return; + } + + // Check if another instance can be started + if( allowSecondary ){ + d->startSecondary(); + if( d->options & Mode::SecondaryNotification ){ + d->connectToPrimary( timeout, SingleApplicationPrivate::SecondaryInstance ); + } + if( ! d->memory->unlock() ){ + qDebug() << "SingleApplication: Unable to unlock memory after secondary start."; + qDebug() << d->memory->errorString(); + } + return; + } + + if( ! d->memory->unlock() ){ + qDebug() << "SingleApplication: Unable to unlock memory at end of execution."; + qDebug() << d->memory->errorString(); + } + + d->connectToPrimary( timeout, SingleApplicationPrivate::NewInstance ); + + delete d; + + ::exit( EXIT_SUCCESS ); +} + +SingleApplication::~SingleApplication() +{ + Q_D( SingleApplication ); + delete d; +} + +/** + * Checks if the current application instance is primary. + * @return Returns true if the instance is primary, false otherwise. + */ +bool SingleApplication::isPrimary() const +{ + Q_D( const SingleApplication ); + return d->server != nullptr; +} + +/** + * Checks if the current application instance is secondary. + * @return Returns true if the instance is secondary, false otherwise. + */ +bool SingleApplication::isSecondary() const +{ + Q_D( const SingleApplication ); + return d->server == nullptr; +} + +/** + * Allows you to identify an instance by returning unique consecutive instance + * ids. It is reset when the first (primary) instance of your app starts and + * only incremented afterwards. + * @return Returns a unique instance id. + */ +quint32 SingleApplication::instanceId() const +{ + Q_D( const SingleApplication ); + return d->instanceNumber; +} + +/** + * Returns the OS PID (Process Identifier) of the process running the primary + * instance. Especially useful when SingleApplication is coupled with OS. + * specific APIs. + * @return Returns the primary instance PID. + */ +qint64 SingleApplication::primaryPid() const +{ + Q_D( const SingleApplication ); + return d->primaryPid(); +} + +/** + * Returns the username the primary instance is running as. + * @return Returns the username the primary instance is running as. + */ +QString SingleApplication::primaryUser() const +{ + Q_D( const SingleApplication ); + return d->primaryUser(); +} + +/** + * Returns the username the current instance is running as. + * @return Returns the username the current instance is running as. + */ +QString SingleApplication::currentUser() const +{ + return SingleApplicationPrivate::getUsername(); +} + +/** + * Sends message to the Primary Instance. + * @param message The message to send. + * @param timeout the maximum timeout in milliseconds for blocking functions. + * @return true if the message was sent successfuly, false otherwise. + */ +bool SingleApplication::sendMessage( const QByteArray &message, int timeout ) +{ + Q_D( SingleApplication ); + + // Nobody to connect to + if( isPrimary() ) return false; + + // Make sure the socket is connected + if( ! d->connectToPrimary( timeout, SingleApplicationPrivate::Reconnect ) ) + return false; + + return d->writeConfirmedMessage( timeout, message ); +} + +/** + * Cleans up the shared memory block and exits with a failure. + * This function halts program execution. + */ +void SingleApplication::abortSafely() +{ + Q_D( SingleApplication ); + + qCritical() << "SingleApplication: " << d->memory->error() << d->memory->errorString(); + delete d; + ::exit( EXIT_FAILURE ); +} + +QStringList SingleApplication::userData() const +{ + Q_D( const SingleApplication ); + return d->appData(); +} diff --git a/src/singleapplication/singleapplication.h b/src/singleapplication/singleapplication.h new file mode 100644 index 00000000..91cabf93 --- /dev/null +++ b/src/singleapplication/singleapplication.h @@ -0,0 +1,154 @@ +// The MIT License (MIT) +// +// Copyright (c) Itay Grudev 2015 - 2018 +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef SINGLE_APPLICATION_H +#define SINGLE_APPLICATION_H + +#include +#include + +#ifndef QAPPLICATION_CLASS + #define QAPPLICATION_CLASS QCoreApplication +#endif + +#include QT_STRINGIFY(QAPPLICATION_CLASS) + +class SingleApplicationPrivate; + +/** + * @brief The SingleApplication class handles multiple instances of the same + * Application + * @see QCoreApplication + */ +class SingleApplication : public QAPPLICATION_CLASS +{ + Q_OBJECT + + using app_t = QAPPLICATION_CLASS; + +public: + /** + * @brief Mode of operation of SingleApplication. + * Whether the block should be user-wide or system-wide and whether the + * primary instance should be notified when a secondary instance had been + * started. + * @note Operating system can restrict the shared memory blocks to the same + * user, in which case the User/System modes will have no effect and the + * block will be user wide. + * @enum + */ + enum Mode { + User = 1 << 0, + System = 1 << 1, + SecondaryNotification = 1 << 2, + ExcludeAppVersion = 1 << 3, + ExcludeAppPath = 1 << 4 + }; + Q_DECLARE_FLAGS(Options, Mode) + + /** + * @brief Intitializes a SingleApplication instance with argc command line + * arguments in argv + * @arg {int &} argc - Number of arguments in argv + * @arg {const char *[]} argv - Supplied command line arguments + * @arg {bool} allowSecondary - Whether to start the instance as secondary + * if there is already a primary instance. + * @arg {Mode} mode - Whether for the SingleApplication block to be applied + * User wide or System wide. + * @arg {int} timeout - Timeout to wait in milliseconds. + * @note argc and argv may be changed as Qt removes arguments that it + * recognizes + * @note Mode::SecondaryNotification only works if set on both the primary + * instance and the secondary instance. + * @note The timeout is just a hint for the maximum time of blocking + * operations. It does not guarantee that the SingleApplication + * initialisation will be completed in given time, though is a good hint. + * Usually 4*timeout would be the worst case (fail) scenario. + * @see See the corresponding QAPPLICATION_CLASS constructor for reference + */ + explicit SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 1000, const QString &userData = {} ); + ~SingleApplication() override; + + /** + * @brief Returns if the instance is the primary instance + * @returns {bool} + */ + bool isPrimary() const; + + /** + * @brief Returns if the instance is a secondary instance + * @returns {bool} + */ + bool isSecondary() const; + + /** + * @brief Returns a unique identifier for the current instance + * @returns {qint32} + */ + quint32 instanceId() const; + + /** + * @brief Returns the process ID (PID) of the primary instance + * @returns {qint64} + */ + qint64 primaryPid() const; + + /** + * @brief Returns the username of the user running the primary instance + * @returns {QString} + */ + QString primaryUser() const; + + /** + * @brief Returns the username of the current user + * @returns {QString} + */ + QString currentUser() const; + + /** + * @brief Sends a message to the primary instance. Returns true on success. + * @param {int} timeout - Timeout for connecting + * @returns {bool} + * @note sendMessage() will return false if invoked from the primary + * instance. + */ + bool sendMessage( const QByteArray &message, int timeout = 100 ); + + /** + * @brief Get the set user data. + * @returns {QStringList} + */ + QStringList userData() const; + +Q_SIGNALS: + void instanceStarted(); + void receivedMessage( quint32 instanceId, QByteArray message ); + +private: + SingleApplicationPrivate *d_ptr; + Q_DECLARE_PRIVATE(SingleApplication) + void abortSafely(); +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplication::Options) + +#endif // SINGLE_APPLICATION_H diff --git a/src/singleapplication/singleapplication.pri b/src/singleapplication/singleapplication.pri new file mode 100644 index 00000000..ae81f599 --- /dev/null +++ b/src/singleapplication/singleapplication.pri @@ -0,0 +1,20 @@ +QT += core network +CONFIG += c++11 + +HEADERS += $$PWD/SingleApplication \ + $$PWD/singleapplication.h \ + $$PWD/singleapplication_p.h +SOURCES += $$PWD/singleapplication.cpp \ + $$PWD/singleapplication_p.cpp + +INCLUDEPATH += $$PWD + +win32 { + msvc:LIBS += Advapi32.lib + gcc:LIBS += -ladvapi32 +} + +DISTFILES += \ + $$PWD/README.md \ + $$PWD/CHANGELOG.md \ + $$PWD/Windows.md diff --git a/src/singleapplication/singleapplication_p.cpp b/src/singleapplication/singleapplication_p.cpp new file mode 100644 index 00000000..1a061f23 --- /dev/null +++ b/src/singleapplication/singleapplication_p.cpp @@ -0,0 +1,538 @@ +// The MIT License (MIT) +// +// Copyright (c) Itay Grudev 2015 - 2020 +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// +// W A R N I N G !!! +// ----------------- +// +// This file is not part of the SingleApplication API. It is used purely as an +// implementation detail. This header file may change from version to +// version without notice, or may even be removed. +// + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) +#include +#else +#include +#endif + +#include "singleapplication.h" +#include "singleapplication_p.h" + +#ifdef Q_OS_UNIX + #include + #include + #include +#endif + +#ifdef Q_OS_WIN + #ifndef NOMINMAX + #define NOMINMAX 1 + #endif + #include + #include +#endif + +SingleApplicationPrivate::SingleApplicationPrivate( SingleApplication *q_ptr ) + : q_ptr( q_ptr ) +{ + server = nullptr; + socket = nullptr; + memory = nullptr; + instanceNumber = 0; +} + +SingleApplicationPrivate::~SingleApplicationPrivate() +{ + if( socket != nullptr ){ + socket->close(); + delete socket; + } + + if( memory != nullptr ){ + memory->lock(); + auto *inst = static_cast(memory->data()); + if( server != nullptr ){ + server->close(); + delete server; + inst->primary = false; + inst->primaryPid = -1; + inst->primaryUser[0] = '\0'; + inst->checksum = blockChecksum(); + } + memory->unlock(); + + delete memory; + } +} + +QString SingleApplicationPrivate::getUsername() +{ +#ifdef Q_OS_WIN + wchar_t username[UNLEN + 1]; + // Specifies size of the buffer on input + DWORD usernameLength = UNLEN + 1; + if( GetUserNameW( username, &usernameLength ) ) + return QString::fromWCharArray( username ); +#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) + return QString::fromLocal8Bit( qgetenv( "USERNAME" ) ); +#else + return qEnvironmentVariable( "USERNAME" ); +#endif +#endif +#ifdef Q_OS_UNIX + QString username; + uid_t uid = geteuid(); + struct passwd *pw = getpwuid( uid ); + if( pw ) + username = QString::fromLocal8Bit( pw->pw_name ); + if ( username.isEmpty() ){ +#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) + username = QString::fromLocal8Bit( qgetenv( "USER" ) ); +#else + username = qEnvironmentVariable( "USER" ); +#endif + } + return username; +#endif +} + +void SingleApplicationPrivate::genBlockServerName() +{ + QCryptographicHash appData( QCryptographicHash::Sha256 ); + appData.addData( "SingleApplication", 17 ); + appData.addData( SingleApplication::app_t::applicationName().toUtf8() ); + appData.addData( SingleApplication::app_t::organizationName().toUtf8() ); + appData.addData( SingleApplication::app_t::organizationDomain().toUtf8() ); + + if ( ! appDataList.isEmpty() ) + appData.addData( appDataList.join( "" ).toUtf8() ); + + if( ! (options & SingleApplication::Mode::ExcludeAppVersion) ){ + appData.addData( SingleApplication::app_t::applicationVersion().toUtf8() ); + } + + if( ! (options & SingleApplication::Mode::ExcludeAppPath) ){ +#if defined(Q_OS_WIN) + appData.addData( SingleApplication::app_t::applicationFilePath().toLower().toUtf8() ); +#elif defined(Q_OS_LINUX) + // If the application is running as an AppImage then the APPIMAGE env var should be used + // instead of applicationPath() as each instance is launched with its own executable path + const QByteArray appImagePath = qgetenv( "APPIMAGE" ); + if( appImagePath.isEmpty() ){ // Not running as AppImage: use path to executable file + appData.addData( SingleApplication::app_t::applicationFilePath().toUtf8() ); + } else { // Running as AppImage: Use absolute path to AppImage file + appData.addData( appImagePath ); + }; +#else + appData.addData( SingleApplication::app_t::applicationFilePath().toUtf8() ); +#endif + } + + // User level block requires a user specific data in the hash + if( options & SingleApplication::Mode::User ){ + appData.addData( getUsername().toUtf8() ); + } + + // Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with + // server naming requirements. + blockServerName = appData.result().toBase64().replace("/", "_"); +} + +void SingleApplicationPrivate::initializeMemoryBlock() const +{ + auto *inst = static_cast( memory->data() ); + inst->primary = false; + inst->secondary = 0; + inst->primaryPid = -1; + inst->primaryUser[0] = '\0'; + inst->checksum = blockChecksum(); +} + +void SingleApplicationPrivate::startPrimary() +{ + // Reset the number of connections + auto *inst = static_cast ( memory->data() ); + + inst->primary = true; + inst->primaryPid = QCoreApplication::applicationPid(); + qstrncpy( inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser) ); + inst->checksum = blockChecksum(); + instanceNumber = 0; + // Successful creation means that no main process exists + // So we start a QLocalServer to listen for connections + QLocalServer::removeServer( blockServerName ); + server = new QLocalServer(); + + // Restrict access to the socket according to the + // SingleApplication::Mode::User flag on User level or no restrictions + if( options & SingleApplication::Mode::User ){ + server->setSocketOptions( QLocalServer::UserAccessOption ); + } else { + server->setSocketOptions( QLocalServer::WorldAccessOption ); + } + + server->listen( blockServerName ); + QObject::connect( + server, + &QLocalServer::newConnection, + this, + &SingleApplicationPrivate::slotConnectionEstablished + ); +} + +void SingleApplicationPrivate::startSecondary() +{ + auto *inst = static_cast ( memory->data() ); + + inst->secondary += 1; + inst->checksum = blockChecksum(); + instanceNumber = inst->secondary; +} + +bool SingleApplicationPrivate::connectToPrimary( int msecs, ConnectionType connectionType ) +{ + QElapsedTimer time; + time.start(); + + // Connect to the Local Server of the Primary Instance if not already + // connected. + if( socket == nullptr ){ + socket = new QLocalSocket(); + } + + if( socket->state() == QLocalSocket::ConnectedState ) return true; + + if( socket->state() != QLocalSocket::ConnectedState ){ + + while( true ){ + randomSleep(); + + if( socket->state() != QLocalSocket::ConnectingState ) + socket->connectToServer( blockServerName ); + + if( socket->state() == QLocalSocket::ConnectingState ){ + socket->waitForConnected( static_cast(msecs - time.elapsed()) ); + } + + // If connected break out of the loop + if( socket->state() == QLocalSocket::ConnectedState ) break; + + // If elapsed time since start is longer than the method timeout return + if( time.elapsed() >= msecs ) return false; + } + } + + // Initialisation message according to the SingleApplication protocol + QByteArray initMsg; + QDataStream writeStream(&initMsg, QIODevice::WriteOnly); + +#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) + writeStream.setVersion(QDataStream::Qt_5_6); +#endif + + writeStream << blockServerName.toLatin1(); + writeStream << static_cast(connectionType); + writeStream << instanceNumber; +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + quint16 checksum = qChecksum(QByteArray(initMsg, static_cast(initMsg.length()))); +#else + quint16 checksum = qChecksum(initMsg.constData(), static_cast(initMsg.length())); +#endif + writeStream << checksum; + + return writeConfirmedMessage( static_cast(msecs - time.elapsed()), initMsg ); +} + +void SingleApplicationPrivate::writeAck( QLocalSocket *sock ) { + sock->putChar('\n'); +} + +bool SingleApplicationPrivate::writeConfirmedMessage (int msecs, const QByteArray &msg) +{ + QElapsedTimer time; + time.start(); + + // Frame 1: The header indicates the message length that follows + QByteArray header; + QDataStream headerStream(&header, QIODevice::WriteOnly); + +#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) + headerStream.setVersion(QDataStream::Qt_5_6); +#endif + headerStream << static_cast ( msg.length() ); + + if( ! writeConfirmedFrame( static_cast(msecs - time.elapsed()), header )) + return false; + + // Frame 2: The message + return writeConfirmedFrame( static_cast(msecs - time.elapsed()), msg ); +} + +bool SingleApplicationPrivate::writeConfirmedFrame( int msecs, const QByteArray &msg ) +{ + socket->write( msg ); + socket->flush(); + + bool result = socket->waitForReadyRead( msecs ); // await ack byte + if (result) { + socket->read( 1 ); + return true; + } + + return false; +} + +quint16 SingleApplicationPrivate::blockChecksum() const +{ +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + quint16 checksum = qChecksum(QByteArray(static_cast(memory->constData()), offsetof(InstancesInfo, checksum))); +#else + quint16 checksum = qChecksum(static_cast(memory->constData()), offsetof(InstancesInfo, checksum)); +#endif + return checksum; +} + +qint64 SingleApplicationPrivate::primaryPid() const +{ + qint64 pid; + + memory->lock(); + auto *inst = static_cast( memory->data() ); + pid = inst->primaryPid; + memory->unlock(); + + return pid; +} + +QString SingleApplicationPrivate::primaryUser() const +{ + QByteArray username; + + memory->lock(); + auto *inst = static_cast( memory->data() ); + username = inst->primaryUser; + memory->unlock(); + + return QString::fromUtf8( username ); +} + +/** + * @brief Executed when a connection has been made to the LocalServer + */ +void SingleApplicationPrivate::slotConnectionEstablished() +{ + QLocalSocket *nextConnSocket = server->nextPendingConnection(); + connectionMap.insert(nextConnSocket, ConnectionInfo()); + + QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, this, + [nextConnSocket, this](){ + auto &info = connectionMap[nextConnSocket]; + this->slotClientConnectionClosed( nextConnSocket, info.instanceId ); + } + ); + + QObject::connect(nextConnSocket, &QLocalSocket::disconnected, nextConnSocket, &QLocalSocket::deleteLater); + + QObject::connect(nextConnSocket, &QLocalSocket::destroyed, this, + [nextConnSocket, this](){ + connectionMap.remove(nextConnSocket); + } + ); + + QObject::connect(nextConnSocket, &QLocalSocket::readyRead, this, + [nextConnSocket, this](){ + auto &info = connectionMap[nextConnSocket]; + switch(info.stage){ + case StageInitHeader: + readMessageHeader( nextConnSocket, StageInitBody ); + break; + case StageInitBody: + readInitMessageBody(nextConnSocket); + break; + case StageConnectedHeader: + readMessageHeader( nextConnSocket, StageConnectedBody ); + break; + case StageConnectedBody: + this->slotDataAvailable( nextConnSocket, info.instanceId ); + break; + default: + break; + }; + } + ); +} + +void SingleApplicationPrivate::readMessageHeader( QLocalSocket *sock, SingleApplicationPrivate::ConnectionStage nextStage ) +{ + if (!connectionMap.contains( sock )){ + return; + } + + if( sock->bytesAvailable() < ( qint64 )sizeof( quint64 ) ){ + return; + } + + QDataStream headerStream( sock ); + +#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) + headerStream.setVersion( QDataStream::Qt_5_6 ); +#endif + + // Read the header to know the message length + quint64 msgLen = 0; + headerStream >> msgLen; + ConnectionInfo &info = connectionMap[sock]; + info.stage = nextStage; + info.msgLen = msgLen; + + writeAck( sock ); +} + +bool SingleApplicationPrivate::isFrameComplete( QLocalSocket *sock ) +{ + if (!connectionMap.contains( sock )){ + return false; + } + + ConnectionInfo &info = connectionMap[sock]; + if( sock->bytesAvailable() < ( qint64 )info.msgLen ){ + return false; + } + + return true; +} + +void SingleApplicationPrivate::readInitMessageBody( QLocalSocket *sock ) +{ + Q_Q(SingleApplication); + + if( !isFrameComplete( sock ) ) + return; + + // Read the message body + QByteArray msgBytes = sock->readAll(); + QDataStream readStream(msgBytes); + +#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) + readStream.setVersion( QDataStream::Qt_5_6 ); +#endif + + // server name + QByteArray latin1Name; + readStream >> latin1Name; + + // connection type + ConnectionType connectionType = InvalidConnection; + quint8 connTypeVal = InvalidConnection; + readStream >> connTypeVal; + connectionType = static_cast ( connTypeVal ); + + // instance id + quint32 instanceId = 0; + readStream >> instanceId; + + // checksum + quint16 msgChecksum = 0; + readStream >> msgChecksum; + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + const quint16 actualChecksum = qChecksum(QByteArray(msgBytes, static_cast(msgBytes.length() - sizeof(quint16)))); +#else + const quint16 actualChecksum = qChecksum(msgBytes.constData(), static_cast(msgBytes.length() - sizeof(quint16))); +#endif + + bool isValid = readStream.status() == QDataStream::Ok && + QLatin1String(latin1Name) == blockServerName && + msgChecksum == actualChecksum; + + if( !isValid ){ + sock->close(); + return; + } + + ConnectionInfo &info = connectionMap[sock]; + info.instanceId = instanceId; + info.stage = StageConnectedHeader; + + if( connectionType == NewInstance || + ( connectionType == SecondaryInstance && + options & SingleApplication::Mode::SecondaryNotification ) ) + { + Q_EMIT q->instanceStarted(); + } + + writeAck( sock ); +} + +void SingleApplicationPrivate::slotDataAvailable( QLocalSocket *dataSocket, quint32 instanceId ) +{ + Q_Q(SingleApplication); + + if ( !isFrameComplete( dataSocket ) ) + return; + + Q_EMIT q->receivedMessage( instanceId, dataSocket->readAll() ); + + writeAck( dataSocket ); + + ConnectionInfo &info = connectionMap[dataSocket]; + info.stage = StageConnectedHeader; +} + +void SingleApplicationPrivate::slotClientConnectionClosed( QLocalSocket *closedSocket, quint32 instanceId ) +{ + if( closedSocket->bytesAvailable() > 0 ) + slotDataAvailable( closedSocket, instanceId ); +} + +void SingleApplicationPrivate::randomSleep() +{ +#if QT_VERSION >= QT_VERSION_CHECK( 5, 10, 0 ) + QThread::msleep( QRandomGenerator::global()->bounded( 8u, 18u )); +#else + qsrand( QDateTime::currentMSecsSinceEpoch() % std::numeric_limits::max() ); + QThread::msleep( qrand() % 11 + 8); +#endif +} + +void SingleApplicationPrivate::addAppData(const QString &data) +{ + appDataList.push_back(data); +} + +QStringList SingleApplicationPrivate::appData() const +{ + return appDataList; +} diff --git a/src/singleapplication/singleapplication_p.h b/src/singleapplication/singleapplication_p.h new file mode 100644 index 00000000..58507cf3 --- /dev/null +++ b/src/singleapplication/singleapplication_p.h @@ -0,0 +1,109 @@ +// The MIT License (MIT) +// +// Copyright (c) Itay Grudev 2015 - 2020 +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// +// W A R N I N G !!! +// ----------------- +// +// This file is not part of the SingleApplication API. It is used purely as an +// implementation detail. This header file may change from version to +// version without notice, or may even be removed. +// + +#ifndef SINGLEAPPLICATION_P_H +#define SINGLEAPPLICATION_P_H + +#include +#include +#include +#include "singleapplication.h" + +struct InstancesInfo { + bool primary; + quint32 secondary; + qint64 primaryPid; + char primaryUser[128]; + quint16 checksum; // Must be the last field +}; + +struct ConnectionInfo { + qint64 msgLen = 0; + quint32 instanceId = 0; + quint8 stage = 0; +}; + +class SingleApplicationPrivate : public QObject { +Q_OBJECT +public: + enum ConnectionType : quint8 { + InvalidConnection = 0, + NewInstance = 1, + SecondaryInstance = 2, + Reconnect = 3 + }; + enum ConnectionStage : quint8 { + StageInitHeader = 0, + StageInitBody = 1, + StageConnectedHeader = 2, + StageConnectedBody = 3, + }; + Q_DECLARE_PUBLIC(SingleApplication) + + SingleApplicationPrivate( SingleApplication *q_ptr ); + ~SingleApplicationPrivate() override; + + static QString getUsername(); + void genBlockServerName(); + void initializeMemoryBlock() const; + void startPrimary(); + void startSecondary(); + bool connectToPrimary( int msecs, ConnectionType connectionType ); + quint16 blockChecksum() const; + qint64 primaryPid() const; + QString primaryUser() const; + bool isFrameComplete(QLocalSocket *sock); + void readMessageHeader(QLocalSocket *socket, ConnectionStage nextStage); + void readInitMessageBody(QLocalSocket *socket); + void writeAck(QLocalSocket *sock); + bool writeConfirmedFrame(int msecs, const QByteArray &msg); + bool writeConfirmedMessage(int msecs, const QByteArray &msg); + static void randomSleep(); + void addAppData(const QString &data); + QStringList appData() const; + + SingleApplication *q_ptr; + QSharedMemory *memory; + QLocalSocket *socket; + QLocalServer *server; + quint32 instanceNumber; + QString blockServerName; + SingleApplication::Options options; + QMap connectionMap; + QStringList appDataList; + +public Q_SLOTS: + void slotConnectionEstablished(); + void slotDataAvailable( QLocalSocket*, quint32 ); + void slotClientConnectionClosed( QLocalSocket*, quint32 ); +}; + +#endif // SINGLEAPPLICATION_P_H From 5a8b0a111760248ef32b6a058038c1a85a311267 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Thu, 14 Oct 2021 10:18:49 +0200 Subject: [PATCH 032/130] fixed deselect all on autosave --- src/board/UBBoardController.cpp | 2 +- src/core/UBPersistenceManager.cpp | 5 +++-- src/core/UBPersistenceManager.h | 3 +-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/board/UBBoardController.cpp b/src/board/UBBoardController.cpp index 62dcd073..8ba62e58 100644 --- a/src/board/UBBoardController.cpp +++ b/src/board/UBBoardController.cpp @@ -2020,7 +2020,7 @@ void UBBoardController::persistCurrentScene(bool isAnAutomaticBackup, bool force && (mActiveSceneIndex >= 0) && mActiveSceneIndex != mMovingSceneIndex && (mActiveScene->isModified())) { - UBPersistenceManager::persistenceManager()->persistDocumentScene(selectedDocument(), mActiveScene, mActiveSceneIndex); + UBPersistenceManager::persistenceManager()->persistDocumentScene(selectedDocument(), mActiveScene, mActiveSceneIndex, isAnAutomaticBackup); updatePage(mActiveSceneIndex); } } diff --git a/src/core/UBPersistenceManager.cpp b/src/core/UBPersistenceManager.cpp index 70a6ceb8..a53342a4 100644 --- a/src/core/UBPersistenceManager.cpp +++ b/src/core/UBPersistenceManager.cpp @@ -931,11 +931,12 @@ void UBPersistenceManager::reassignDocProxy(UBDocumentProxy *newDocument, UBDocu return mSceneCache.reassignDocProxy(newDocument, oldDocument); } -void UBPersistenceManager::persistDocumentScene(UBDocumentProxy* pDocumentProxy, UBGraphicsScene* pScene, const int pSceneIndex) +void UBPersistenceManager::persistDocumentScene(UBDocumentProxy* pDocumentProxy, UBGraphicsScene* pScene, const int pSceneIndex, bool isAnAutomaticBackup) { checkIfDocumentRepositoryExists(); - pScene->deselectAllItems(); + if (!isAnAutomaticBackup) + pScene->deselectAllItems(); generatePathIfNeeded(pDocumentProxy); diff --git a/src/core/UBPersistenceManager.h b/src/core/UBPersistenceManager.h index 93492def..8a1462ce 100644 --- a/src/core/UBPersistenceManager.h +++ b/src/core/UBPersistenceManager.h @@ -103,8 +103,7 @@ class UBPersistenceManager : public QObject virtual void copyDocumentScene(UBDocumentProxy *from, int fromIndex, UBDocumentProxy *to, int toIndex); - virtual void persistDocumentScene(UBDocumentProxy* pDocumentProxy, - UBGraphicsScene* pScene, const int pSceneIndex); + virtual void persistDocumentScene(UBDocumentProxy* pDocumentProxy, UBGraphicsScene* pScene, const int pSceneIndex, bool isAnAutomaticBackup = false); virtual UBGraphicsScene* createDocumentSceneAt(UBDocumentProxy* pDocumentProxy, int index, bool useUndoRedoStack = true); From b43ffa46090a20994a89a2c14c90bf3ad58647ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Thu, 14 Oct 2021 12:43:57 +0200 Subject: [PATCH 033/130] don't call showFullScreen if numscreens == 1 (not even sure why showFullScreen would have to be called here...) --- src/core/UBDisplayManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/UBDisplayManager.cpp b/src/core/UBDisplayManager.cpp index cd9db4ec..f24e9211 100644 --- a/src/core/UBDisplayManager.cpp +++ b/src/core/UBDisplayManager.cpp @@ -180,7 +180,7 @@ void UBDisplayManager::setDisplayWidget(QWidget* pDisplayWidget) } mDisplayWidget = pDisplayWidget; mDisplayWidget->setGeometry(mDesktop->screenGeometry(mDisplayScreenIndex)); - if (UBSettings::settings()->appUseMultiscreen->get().toBool()) + if (numScreens() > 1 && UBSettings::settings()->appUseMultiscreen->get().toBool()) UBPlatformUtils::showFullScreen(mDisplayWidget); } } From ebae31772373bd7264a32deffc2075ccb2fe2244 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Thu, 14 Oct 2021 15:50:32 +0200 Subject: [PATCH 034/130] change mode of magnifier on display view too --- src/domain/UBGraphicsScene.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/domain/UBGraphicsScene.cpp b/src/domain/UBGraphicsScene.cpp index 5b5c59e8..0336a63c 100644 --- a/src/domain/UBGraphicsScene.cpp +++ b/src/domain/UBGraphicsScene.cpp @@ -2380,6 +2380,9 @@ void UBGraphicsScene::changeMagnifierMode(int mode) { if(magniferControlViewWidget) magniferControlViewWidget->setDrawingMode(mode); + + if(magniferDisplayViewWidget) + magniferDisplayViewWidget->setDrawingMode(mode); } void UBGraphicsScene::resizedMagnifier(qreal newPercent) From fb6dffd7b6112d3f0c38e96a1c5478a764a5d141 Mon Sep 17 00:00:00 2001 From: ovari <17465872+ovari@users.noreply.github.com> Date: Sat, 16 Oct 2021 14:09:24 +1100 Subject: [PATCH 035/130] Update OpenBoard_hu.ts --- resources/i18n/OpenBoard_hu.ts | 276 ++++++++++++++++----------------- 1 file changed, 135 insertions(+), 141 deletions(-) diff --git a/resources/i18n/OpenBoard_hu.ts b/resources/i18n/OpenBoard_hu.ts index a66081cf..3e0bcd0b 100644 --- a/resources/i18n/OpenBoard_hu.ts +++ b/resources/i18n/OpenBoard_hu.ts @@ -50,7 +50,7 @@ IntranetPodcastPublishingDialog Publish Podcast to YouTube - Podcast publikálása a YouTubera + Podcast közzététele a YouTube-on Title @@ -441,7 +441,7 @@ Reload Current Page - Aktuális oldal újratöltése + Jelenlegi oldal újratöltése Load Home Page @@ -641,11 +641,11 @@ Add To New Page - Hozzáadás új oldalhoz + Hozzáadás az új oldalhoz Add Item To New Page - Tartalom hozzáadása új oldalhoz + Elem hozzáadása az új oldalhoz Add To Library @@ -653,7 +653,7 @@ Add Item To Library - Tartalom hozzáadása a könyvtárhoz + Elem hozzáadása a könyvtárhoz Pages @@ -813,31 +813,31 @@ Small Eraser - + Kis radír Color 1 - + 1. szín Color 2 - + 2. szín Color 3 - + 3. szín Color 4 - + 4. szín Color 5 - + 5. szín Draw intermediate grid lines - + Köztes rácsvonalak rajzolása @@ -886,15 +886,15 @@ Are you sure you want to remove 1 page from the selected document '%0'? - Biztosan el akarsz távolítani 1 oldalt a kiválasztott dokumentumból '%0'? + Biztosan szeretné eltávolítani 1 oldalt a kijelölt „%0”-dokumentumból? Element ID = - + Elem azonosítója = Content is not supported in destination format. - + A tartalom nem támogatott célformátumban. @@ -932,7 +932,7 @@ Board drawing... - Tábla rajzolása... + Tábla rajzolása… @@ -943,7 +943,7 @@ Draw intermediate grid lines - + Köztes rácsvonalak rajzolása @@ -990,11 +990,11 @@ Saving document... - Dokumentum mentése... + Dokumentum mentése… Document has just been saved... - Dokumentum éppen most mentve... + Dokumentum éppen most mentve… Deleting page %1 @@ -1002,7 +1002,7 @@ Color - Szín + Szín @@ -1020,7 +1020,7 @@ UBBoardThumbnailsView Loading page (%1/%2) - Oldalak betöltése (%1/%2) + Oldalak betöltése: %1/%2 @@ -1113,23 +1113,23 @@ Are you sure you want to remove the document '%1'? - Biztosan szeretné eltávolítani a '%1' dokumentumot? + Biztosan szeretné eltávolítani a(z) „%1” dokumentumot? Empty Trash - Kuka kiürítése + Törölt elemek kiürítése Are you sure you want to empty trash? - Biztosan szeretné a kukát üríteni? + Biztosan szeretné kiüríteni a törölt elemeket? Emptying trash - Kuka kiürítése folyamatban + Törölt elemek kiürítése folyamatban van… Emptied trash - Kuka kiürítve + Törölt elemek kiürítve Remove Folder @@ -1137,7 +1137,7 @@ Are you sure you want to remove the folder '%1' and all its content? - Biztosan eltávolítja a(z) '%1' mappát és minden tartalmát? + Biztosan szeretné eltávolítani a(z) „%1” mappát és az összes tartalmát? No document selected! @@ -1149,7 +1149,7 @@ Importing file %1... - %1 fájl importálása... + %1 fájl importálása… Failed to import file ... @@ -1169,7 +1169,7 @@ Trash - Kuka + Törölt elemek Open Document @@ -1189,9 +1189,7 @@ Are you sure you want to remove %n page(s) from the selected document '%1'? - - Biztosan szeretne eltávolítani %n oldalt a kiválasztott '%1' dokumentumból? - + Biztosan szeretné eltávolítani %n oldalt a kiválasztott „%1” dokumentumból? Folder does not contain any image files @@ -1203,11 +1201,11 @@ The document '%1' has been generated with a newer version of OpenBoard (%2). By opening it, you may lose some information. Do you want to proceed? - A '%1' dokumentum az OpenBoard újabb verziójával (%2) készült. Megnyitásával néhány információ elveszhet. Szeretné folytatni? + A(z) „%1” dokumentum az OpenBoard újabb verziójával (%2) készült. Megnyitásával néhány információ elveszhet. Szeretné folytatni? Are you sure you want to remove all selected documents? - Biztosan szeretne eltávolítani minden kiválasztott dokumentumot? + Biztosan szeretné eltávolítani minden kijelölt dokumentumot? Remove multiple documents @@ -1216,21 +1214,19 @@ duplicated %1 page duplicated %1 pages - - - + %1 oldal másolata Remove Item - + Elem eltávolítása Are you sure you want to remove the selected item(s) ? - + Biztosan szeretné eltávolítani a kijelölt elem(ek)et? Title page - + Címlap @@ -1261,7 +1257,7 @@ Inserting page %1 of %2 - %2 / %1 oldal beszúrása + Oldal beszúrása: %1/%2 Import successful. @@ -1280,66 +1276,64 @@ UBDocumentNavigator Page %0 - + %0. oldal UBDocumentReplaceDialog Accept - Elfogad + Elfogadás Replace - + Csere Cancel - Mégse + Mégse The name %1 is allready used. Keeping this name will replace the document. Providing a new name will create a new document. - + A(z) %1 név már használatban van. +Ennek a névnek a megtartása helyettesíti a dokumentumot. +Új név megadása új dokumentumot hoz létre. UBDocumentTreeModel My documents - + Saját dokumentumok Trash - Kuka + Törölt elemek %1 pages copied - - %1 oldal másolva - + %1 oldal másolva UBDocumentTreeView Copying page %1/%2 - %1/%2 oldal másolása + Oldal másolása: %1/%2 %1 pages copied - - %1 oldal másolva - + %1 oldal másolva Remove Item - + Elem eltávolítása Are you sure you want to remove the selected item(s) ? - + Biztosan szeretné eltávolítani a kijelölt elem(ek)et? @@ -1350,7 +1344,7 @@ Providing a new name will create a new document. Copying page %1/%2 - %1/%2 oldal másolása + Oldal másolása: %1/%2 %1 pages copied @@ -1374,7 +1368,7 @@ Providing a new name will create a new document. UBDraggableThumbnail Page %0 - + %0. oldal @@ -1392,7 +1386,7 @@ Providing a new name will create a new document. Exporting document... - Dokumentum exportálása... + Dokumentum exportálása… Export failed @@ -1415,23 +1409,23 @@ Providing a new name will create a new document. UBExportCFF Export to IWB - + Exportálás IWB-formátumként Export as IWB File - + Exportálás IWB-fájlformátumként Exporting document... - Dokumentum exportálása... + Dokumentum exportálása… Export successful. - Exportálás sikeres. + Az exportálás sikeres. Export failed. - Exportálás sikertelen. + Az exportálás sikertelen. @@ -1446,7 +1440,7 @@ Providing a new name will create a new document. Exporting %1 %2 of %3 - %1 exportálása %2/%3 + %1 exportálása: %2/%3 Export to OpenBoard Format @@ -1457,27 +1451,27 @@ Providing a new name will create a new document. UBExportDocumentSetAdaptor Failed to export... - + Exportálás sikertelen… Export as UBX File - + Exportálás UBX-fájlként Exporting document... - Dokumentum exportálása... + Dokumentum exportálása… Export successful. - Exportálás sikeres. + Exportálás sikeres. Export failed. - Exportálás sikertelen. + Exportálás sikertelen. Export to OpenBoard UBX Format - + Exportálás OpenBoard UBX-formátumként @@ -1499,7 +1493,7 @@ Providing a new name will create a new document. Exporting page %1 of %2 - %2 / %1 oldal exportálása + Oldal exportálása: %1/%2 Export to PDF @@ -1518,7 +1512,7 @@ Providing a new name will create a new document. Exporting document... - Dokumentum exportálása... + Dokumentum exportálása… Export successful. @@ -1603,7 +1597,7 @@ Providing a new name will create a new document. Animations - Animációk + Animációk Interactivities @@ -1627,14 +1621,14 @@ Providing a new name will create a new document. Trash - Kuka + Törölt elemek UBFeaturesNewFolderDialog Accept - Elfogad + Elfogadás Cancel @@ -1705,7 +1699,7 @@ Providing a new name will create a new document. UBGraphicsTextItem <Type Text Here> - <Szöveg helye> + <Adja meg a szöveget ide> @@ -1730,7 +1724,7 @@ Providing a new name will create a new document. UBGraphicsWidgetItem Loading ... - Betöltés ... + Betöltés folyamatban van… @@ -1748,30 +1742,30 @@ Providing a new name will create a new document. UBImportCFF Common File Format ( - + Közös fájlformátum ( Importing file %1... - %1 fájl importálása... + %1 fájl importálása… Import of file %1 failed. - %1 fájl importálása sikertelen. + %1 fájl importálása sikertelen. Import successful. - Importálás sikeres. + Importálás sikeres. Import failed. - + Importálás sikertelen. UBImportDocument Importing file %1... - %1 fájl importálása... + %1 fájl importálása… Import successful. @@ -1790,14 +1784,14 @@ Providing a new name will create a new document. UBImportDocumentSetAdaptor Openboard (set of documents) (*.ubx) - + OpenBoard (dokumentumkészlet) (*.ubx) UBImportImage Image Format ( - Kép formátum ( + Kép-formátum ( @@ -1812,25 +1806,25 @@ Providing a new name will create a new document. Importing page %1 of %2 - %2 / %1 oldal importálása + Oldal importálása: %1/%2 UBIntranetPodcastPublisher Error while publishing video to intranet (%1) - Hiba a videó intranetre való publikálása közben (%1) + Hiba történt videó közzétételekor az intraneten (%1) Publishing to Intranet in progress %1 % - Intranetre publikálás folyamatban %1 % + Az intraneten való közzététel folyamatban van %1% UBIntranetPodcastPublishingDialog Publish - Publikálás + Közzététel @@ -1936,15 +1930,15 @@ Figyelmen kívül hagyja ezeket a hibákat ennél a hosztnál? has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. - elveszítette a '%1' dokumentum repository hozzáférését. Sajnálatosan az alkalmazásnak le kellett állnia, hogy elkerülje az adatsérülést. A legutóbbi változtatások elveszhettek. + elveszítette a(z) „%1” dokumentum repository hozzáférését. Sajnálatosan az alkalmazásnak le kellett állnia, hogy elkerülje az adatsérülést. A legutóbbi változtatások elveszhettek. Moving page to trash folder... - Oldal áthelyezése a kukába... + Oldal áthelyezése a törölt elemek mappába… OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. - + Az OpenBoard elvesztette a hozzáférést a(z) „%1” dokumentumtárához. Sajnos az alkalmazást le kell állítani az adatvesztés elkerülése érdekében. A legújabb változások is elveszhetnek. @@ -1974,11 +1968,11 @@ Figyelmen kívül hagyja ezeket a hibákat ennél a hosztnál? UBPodcastController Failed to start encoder ... - Sikertelen a kódoló indítása... + Sikertelen a kódoló indítása… No Podcast encoder available ... - Nincs elérhető podcast kódoló ... + Nincs elérhető podcast kódoló… Part %1 @@ -1986,7 +1980,7 @@ Figyelmen kívül hagyja ezeket a hibákat ennél a hosztnál? on your desktop ... - az asztalán ... + az asztalán… in folder %1 @@ -2022,11 +2016,11 @@ Figyelmen kívül hagyja ezeket a hibákat ennél a hosztnál? Publish to Intranet - Publikálás az Intranetre + Közzététel az Intraneten Publish to Youtube - Publikálás a YouTubera + Közzététel a Youtube-on OpenBoard Cast @@ -2048,34 +2042,34 @@ Figyelmen kívül hagyja ezeket a hibákat ennél a hosztnál? UBProxyLoginDlg Proxy Login - Proxy belépés + Proxy bejelentkezés Username: - Felhasználónév: + Felhasználónév: Password: - Jelszó: + Jelszó: UBPublicationDlg Publish document on the web - Dokumentum publikálása a WEBre + Dokumentum közzététele az interneten Title: - Cím: + Cím: Description: - Leírás: + Leírás: Publish - Publikálás + Közzététel @@ -2103,11 +2097,11 @@ Figyelmen kívül hagyja ezeket a hibákat ennél a hosztnál? UBThumbnailAdaptor Generating preview thumbnails ... - Előképek létrehozása... + Előképek létrehozása… %1 thumbnails generated ... - %1 előkép létrehozva... + %1 előkép létrehozva… loading thumbnail of page %1 @@ -2153,7 +2147,7 @@ Figyelmen kívül hagyja ezeket a hibákat ennél a hosztnál? Axes - + Tengelyek @@ -2212,7 +2206,7 @@ Please reboot the application to access the updated documents. Please wait the import process will start soon... - Kérem várjon. Az importálási folyamat hamarosan elindul... + Kérem várjon. Az importálási folyamat hamarosan elindul… Remind me later @@ -2223,7 +2217,7 @@ Please reboot the application to access the updated documents. UBWebPluginWidget Loading... - Betöltés ... + Betöltés folyamatban van… @@ -2256,7 +2250,7 @@ Please reboot the application to access the updated documents. Autos & Vehicles - Autók & Járművek + Autók és Járművek Music @@ -2264,7 +2258,7 @@ Please reboot the application to access the updated documents. Pets & Animals - Házi kedvencek @ Állatok + Háziállatok és Állatok Sports @@ -2272,7 +2266,7 @@ Please reboot the application to access the updated documents. Travel & Events - Utazás & Események + Utazás és Események Gaming @@ -2284,11 +2278,11 @@ Please reboot the application to access the updated documents. People & Blogs - Emberek & Blogok + Emberek és Webes naplók News & Politics - Hírek & Politika + Hírek és Politika Entertainment @@ -2300,15 +2294,15 @@ Please reboot the application to access the updated documents. Howto & Style - Hogyan Csináld & Stílus + Hogyan kell és Stílus Nonprofits & Activism - NonProfit & Aktivisták + Nonprofit szervezetek és Aktivizmus Science & Technology - Tudomány & Technológia + Tudomány és Technológia @@ -2361,7 +2355,7 @@ Please reboot the application to access the updated documents. %1 of %2 (%3/sec) %4 - %1 / %2 (%3/mp) %4 + %1/%2 (%3/mp) %4 ? @@ -2370,7 +2364,7 @@ Please reboot the application to access the updated documents. %1 of %2 - Stopped - %1 / %2 - Megállítva + %1/%2 - Megállítva bytes @@ -2430,11 +2424,11 @@ Please reboot the application to access the updated documents. WBTabBar New &Tab - Új fül &T + Új &Lap Clone Tab - Fül másolása + Lap másolása &Close Tab @@ -2442,15 +2436,15 @@ Please reboot the application to access the updated documents. Close &Other Tabs - Többi fül bezárása &O + &Többi lap bezárása Reload Tab - Fül frissítése + Lap újratöltése Reload All Tabs - Minden fül frissítése + Minden lap újratöltése @@ -2491,7 +2485,7 @@ Please reboot the application to access the updated documents. Add to Current Document - Hozzaadás az aktuális dokumentumhoz + Hozzaadás a jelenlegi dokumentumhoz PDF @@ -2517,14 +2511,14 @@ Please reboot the application to access the updated documents. XPDFRenderer Processing... - + Feldolgozás folyamatban van… YouTubePublishingDialog Publish Podcast to YouTube - Podcast publikálása YouTubera + Podcast közzététele a YouTube-on Title @@ -2645,19 +2639,19 @@ p, li { white-space: pre-wrap; } Creation date - + Létrehozás dátuma Update date - + Frissítés dátuma Alphabetical order - + Rendezés betűrendben Sort Order - + Rendezési sorrend @@ -2744,7 +2738,7 @@ p, li { white-space: pre-wrap; } version : … - verziószám: ... + verziószám: Licences @@ -2828,31 +2822,31 @@ p, li { white-space: pre-wrap; } Swap first and second view displays - + Első és második kijelző megcserélése Documents Mode - + Dokumentum üzemmód Display date column on alphabetical sort - + Dátum oszlop megjelenítése betűrendben Empty trash for documents older than - + A törölt elemek ürítése a következőknél régebbi dokumentumokhoz: days - + nap PDF Rendering - + PDF-megjelenítő Improve zoom execution time (can slightly affect rendering quality) - + Nagyitás végrehajtási idő csökkentése (némileg befolyásolhatja a renderelés minőségét) From 8803239d84a2d0c839351ebb429836959c2e6fab Mon Sep 17 00:00:00 2001 From: letsfindaway Date: Mon, 18 Oct 2021 19:44:34 +0200 Subject: [PATCH 036/130] fix: subtract margin from boundingRect in UBSvgSubsetAdaptor --- src/adaptors/UBSvgSubsetAdaptor.cpp | 56 ++++++++++++++++------------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/src/adaptors/UBSvgSubsetAdaptor.cpp b/src/adaptors/UBSvgSubsetAdaptor.cpp index 62a4e524..cdf6900b 100644 --- a/src/adaptors/UBSvgSubsetAdaptor.cpp +++ b/src/adaptors/UBSvgSubsetAdaptor.cpp @@ -355,7 +355,7 @@ UBGraphicsScene* UBSvgSubsetAdaptor::UBSvgSubsetReader::loadScene(UBDocumentProx time.start(); mScene = 0; UBGraphicsWidgetItem *currentWidget = 0; - bool pageDpiSpecified = true; + //bool pageDpiSpecified = true; saveSceneAfterLoading = false; mFileVersion = 40100; // default to 4.1.0 @@ -443,7 +443,7 @@ UBGraphicsScene* UBSvgSubsetAdaptor::UBSvgSubsetReader::loadScene(UBDocumentProx else if (proxy->pageDpi() == 0) { proxy->setPageDpi((UBApplication::desktop()->physicalDpiX() + UBApplication::desktop()->physicalDpiY())/2); - pageDpiSpecified = false; + //pageDpiSpecified = false; } bool darkBackground = false; @@ -909,9 +909,9 @@ UBGraphicsScene* UBSvgSubsetAdaptor::UBSvgSubsetReader::loadScene(UBDocumentProx if (textDelegate) { - QDesktopWidget* desktop = UBApplication::desktop(); - qreal currentDpi = (desktop->physicalDpiX() + desktop->physicalDpiY()) / 2; - qreal textSizeMultiplier = qreal(proxy->pageDpi())/currentDpi; + //QDesktopWidget* desktop = UBApplication::desktop(); + //qreal currentDpi = (desktop->physicalDpiX() + desktop->physicalDpiY()) / 2; + //qreal textSizeMultiplier = qreal(proxy->pageDpi())/currentDpi; //textDelegate->scaleTextSize(textSizeMultiplier); } @@ -2390,8 +2390,9 @@ void UBSvgSubsetAdaptor::UBSvgSubsetWriter::graphicsItemToSvg(QGraphicsItem* ite mXmlWriter.writeAttribute("x", "0"); mXmlWriter.writeAttribute("y", "0"); - mXmlWriter.writeAttribute("width", QString("%1").arg(item->boundingRect().width())); - mXmlWriter.writeAttribute("height", QString("%1").arg(item->boundingRect().height())); + QRectF rect = item->boundingRect() - QMarginsF(0.5, 0.5, 0.5, 0.5); + mXmlWriter.writeAttribute("width", QString("%1").arg(rect.width())); + mXmlWriter.writeAttribute("height", QString("%1").arg(rect.height())); mXmlWriter.writeAttribute("transform", toSvgTransform(item->sceneMatrix())); @@ -2487,8 +2488,9 @@ void UBSvgSubsetAdaptor::UBSvgSubsetWriter::graphicsWidgetToSvg(UBGraphicsWidget mXmlWriter.writeStartElement(nsXHtml, "iframe"); mXmlWriter.writeAttribute("style", "border: none"); - mXmlWriter.writeAttribute("width", QString("%1").arg(item->boundingRect().width())); - mXmlWriter.writeAttribute("height", QString("%1").arg(item->boundingRect().height())); + QRectF rect = item->boundingRect() - QMarginsF(0.5, 0.5, 0.5, 0.5); + mXmlWriter.writeAttribute("width", QString("%1").arg(rect.width())); + mXmlWriter.writeAttribute("height", QString("%1").arg(rect.height())); QString startFileUrl; if (item->mainHtmlFileName().startsWith("http://")) @@ -2817,10 +2819,11 @@ void UBSvgSubsetAdaptor::UBSvgSubsetWriter::curtainItemToSvg(UBGraphicsCurtainIt */ mXmlWriter.writeStartElement(UBSettings::uniboardDocumentNamespaceUri, "curtain"); - mXmlWriter.writeAttribute("x", QString("%1").arg(curtainItem->boundingRect().center().x())); - mXmlWriter.writeAttribute("y", QString("%1").arg(curtainItem->boundingRect().center().y())); - mXmlWriter.writeAttribute("width", QString("%1").arg(curtainItem->boundingRect().width())); - mXmlWriter.writeAttribute("height", QString("%1").arg(curtainItem->boundingRect().height())); + QRectF rect = curtainItem->boundingRect() - QMarginsF(0.5, 0.5, 0.5, 0.5); + mXmlWriter.writeAttribute("x", QString("%1").arg(rect.center().x())); + mXmlWriter.writeAttribute("y", QString("%1").arg(rect.center().y())); + mXmlWriter.writeAttribute("width", QString("%1").arg(rect.width())); + mXmlWriter.writeAttribute("height", QString("%1").arg(rect.height())); mXmlWriter.writeAttribute("transform", toSvgTransform(curtainItem->sceneMatrix())); //graphicsItemToSvg(curtainItem); @@ -2877,10 +2880,11 @@ void UBSvgSubsetAdaptor::UBSvgSubsetWriter::rulerToSvg(UBGraphicsRuler* item) */ mXmlWriter.writeStartElement(UBSettings::uniboardDocumentNamespaceUri, "ruler"); - mXmlWriter.writeAttribute("x", QString("%1").arg(item->boundingRect().x())); - mXmlWriter.writeAttribute("y", QString("%1").arg(item->boundingRect().y())); - mXmlWriter.writeAttribute("width", QString("%1").arg(item->boundingRect().width())); - mXmlWriter.writeAttribute("height", QString("%1").arg(item->boundingRect().height())); + QRectF rect = item->boundingRect() - QMarginsF(0.5, 0.5, 0.5, 0.5); + mXmlWriter.writeAttribute("x", QString("%1").arg(rect.x())); + mXmlWriter.writeAttribute("y", QString("%1").arg(rect.y())); + mXmlWriter.writeAttribute("width", QString("%1").arg(rect.width())); + mXmlWriter.writeAttribute("height", QString("%1").arg(rect.height())); mXmlWriter.writeAttribute("transform", toSvgTransform(item->sceneMatrix())); QString zs; @@ -3005,10 +3009,11 @@ void UBSvgSubsetAdaptor::UBSvgSubsetWriter::compassToSvg(UBGraphicsCompass* item */ mXmlWriter.writeStartElement(UBSettings::uniboardDocumentNamespaceUri, "compass"); - mXmlWriter.writeAttribute("x", QString("%1").arg(item->boundingRect().x())); - mXmlWriter.writeAttribute("y", QString("%1").arg(item->boundingRect().y())); - mXmlWriter.writeAttribute("width", QString("%1").arg(item->boundingRect().width())); - mXmlWriter.writeAttribute("height", QString("%1").arg(item->boundingRect().height())); + QRectF rect = item->boundingRect() - QMarginsF(0.5, 0.5, 0.5, 0.5); + mXmlWriter.writeAttribute("x", QString("%1").arg(rect.x())); + mXmlWriter.writeAttribute("y", QString("%1").arg(rect.y())); + mXmlWriter.writeAttribute("width", QString("%1").arg(rect.width())); + mXmlWriter.writeAttribute("height", QString("%1").arg(rect.height())); mXmlWriter.writeAttribute("transform", toSvgTransform(item->sceneMatrix())); QString zs; @@ -3139,10 +3144,11 @@ void UBSvgSubsetAdaptor::UBSvgSubsetWriter::triangleToSvg(UBGraphicsTriangle *it */ mXmlWriter.writeStartElement(UBSettings::uniboardDocumentNamespaceUri, "triangle"); - mXmlWriter.writeAttribute("x", QString("%1").arg(item->boundingRect().x())); - mXmlWriter.writeAttribute("y", QString("%1").arg(item->boundingRect().y())); - mXmlWriter.writeAttribute("width", QString("%1").arg(item->boundingRect().width())); - mXmlWriter.writeAttribute("height", QString("%1").arg(item->boundingRect().height())); + QRectF rect = item->boundingRect() - QMarginsF(0.5, 0.5, 0.5, 0.5); + mXmlWriter.writeAttribute("x", QString("%1").arg(rect.x())); + mXmlWriter.writeAttribute("y", QString("%1").arg(rect.y())); + mXmlWriter.writeAttribute("width", QString("%1").arg(rect.width())); + mXmlWriter.writeAttribute("height", QString("%1").arg(rect.height())); mXmlWriter.writeAttribute("transform", toSvgTransform(item->sceneMatrix())); mXmlWriter.writeAttribute("orientation", UBGraphicsTriangle::orientationToStr(item->getOrientation())); From f7974f0b339024767f5186f60fdfd400f4befeb8 Mon Sep 17 00:00:00 2001 From: letsfindaway Date: Mon, 18 Oct 2021 20:23:38 +0200 Subject: [PATCH 037/130] fix: resizing of palettes in UBBoardPaletteManager --- src/board/UBBoardPaletteManager.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/board/UBBoardPaletteManager.cpp b/src/board/UBBoardPaletteManager.cpp index 720fbe9f..31605dbd 100644 --- a/src/board/UBBoardPaletteManager.cpp +++ b/src/board/UBBoardPaletteManager.cpp @@ -512,16 +512,18 @@ void UBBoardPaletteManager::containerResized() mKeyboardPalette->adjustSizeAndPosition(); } - if(mLeftPalette) +// NOTE @letsfindaway Fixed, but don't see any reason for this. +// Probably remove. + if(mLeftPalette && mLeftPalette->width() > 0) { mLeftPalette->resize(mLeftPalette->width()-1, mContainer->height()); - mLeftPalette->resize(mLeftPalette->width(), mContainer->height()); + mLeftPalette->resize(mLeftPalette->width()+1, mContainer->height()); } - if(mRightPalette) + if(mRightPalette && mRightPalette->width() > 0) { mRightPalette->resize(mRightPalette->width()-1, mContainer->height()); - mRightPalette->resize(mRightPalette->width(), mContainer->height()); + mRightPalette->resize(mRightPalette->width()+1, mContainer->height()); } } From ec13aa56dca82d335dbff3291414596ca7bbc1c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Tue, 2 Nov 2021 16:40:40 +0100 Subject: [PATCH 038/130] ignore .qmake.stash file --- .qmake.stash | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 .qmake.stash diff --git a/.qmake.stash b/.qmake.stash deleted file mode 100644 index d6e97efd..00000000 --- a/.qmake.stash +++ /dev/null @@ -1,23 +0,0 @@ -QMAKE_CXX.QT_COMPILER_STDCXX = 201402L -QMAKE_CXX.QMAKE_GCC_MAJOR_VERSION = 9 -QMAKE_CXX.QMAKE_GCC_MINOR_VERSION = 3 -QMAKE_CXX.QMAKE_GCC_PATCH_VERSION = 0 -QMAKE_CXX.COMPILER_MACROS = \ - QT_COMPILER_STDCXX \ - QMAKE_GCC_MAJOR_VERSION \ - QMAKE_GCC_MINOR_VERSION \ - QMAKE_GCC_PATCH_VERSION -QMAKE_CXX.INCDIRS = \ - /usr/include/c++/9 \ - /usr/include/x86_64-linux-gnu/c++/9 \ - /usr/include/c++/9/backward \ - /usr/lib/gcc/x86_64-linux-gnu/9/include \ - /usr/local/include \ - /usr/include/x86_64-linux-gnu \ - /usr/include -QMAKE_CXX.LIBDIRS = \ - /usr/lib/gcc/x86_64-linux-gnu/9 \ - /usr/lib/x86_64-linux-gnu \ - /usr/lib \ - /lib/x86_64-linux-gnu \ - /lib From 50b1b3af8a4e7a56de51664a7f297ff6bea31890 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Wed, 3 Nov 2021 10:29:37 +0100 Subject: [PATCH 039/130] don't reload thumbnails every time a thumbnail is added --- src/document/UBDocumentContainer.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/document/UBDocumentContainer.cpp b/src/document/UBDocumentContainer.cpp index 456c37a7..481a415a 100644 --- a/src/document/UBDocumentContainer.cpp +++ b/src/document/UBDocumentContainer.cpp @@ -106,7 +106,6 @@ void UBDocumentContainer::addPage(int index) void UBDocumentContainer::addPixmapAt(const QPixmap *pix, int index) { mDocumentThumbs.insert(index, pix); - emit documentThumbnailsUpdated(this); } From d8c9ebf46f6c685139ef74b854c01a33f26897a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Wed, 3 Nov 2021 10:30:48 +0100 Subject: [PATCH 040/130] call treeViewSelectionChanged just once --- src/document/UBDocumentController.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/document/UBDocumentController.cpp b/src/document/UBDocumentController.cpp index dffa1be2..fbd1e03c 100644 --- a/src/document/UBDocumentController.cpp +++ b/src/document/UBDocumentController.cpp @@ -1548,7 +1548,6 @@ void UBDocumentTreeView::dropEvent(QDropEvent *event) const QPixmap *pix = new QPixmap(thumbTmp); UBDocumentController *ctrl = UBApplication::documentController; ctrl->addPixmapAt(pix, toIndex); - ctrl->TreeViewSelectionChanged(ctrl->firstSelectedTreeIndex(), QModelIndex()); } QApplication::restoreOverrideCursor(); @@ -1557,6 +1556,8 @@ void UBDocumentTreeView::dropEvent(QDropEvent *event) docModel->setHighLighted(QModelIndex()); } + UBApplication::documentController->TreeViewSelectionChanged(UBApplication::documentController->firstSelectedTreeIndex(), QModelIndex()); + } else { From 398e940c3baa086e7839defc6d1e162221043cbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Wed, 3 Nov 2021 12:54:01 +0100 Subject: [PATCH 041/130] use new assistive control app when using system virtual keyboard on osx >= 10.15 --- src/document/UBDocumentController.cpp | 2 +- src/frameworks/UBPlatformUtils_mac.mm | 101 ++++++++++++++++++++------ 2 files changed, 81 insertions(+), 22 deletions(-) diff --git a/src/document/UBDocumentController.cpp b/src/document/UBDocumentController.cpp index dffa1be2..b0f27721 100644 --- a/src/document/UBDocumentController.cpp +++ b/src/document/UBDocumentController.cpp @@ -664,7 +664,7 @@ QMimeData *UBDocumentTreeModel::mimeData (const QModelIndexList &indexes) const #if defined(Q_OS_OSX) #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) - if (QOperatingSystemVersion::current().minorVersion() < 15) /* < Mojave */ + if (QOperatingSystemVersion::current().majorVersion() == 10 && QOperatingSystemVersion::current().minorVersion() < 15) /* <= Mojave */ mimeData->setUrls(urlList); #endif #else diff --git a/src/frameworks/UBPlatformUtils_mac.mm b/src/frameworks/UBPlatformUtils_mac.mm index ae5c5956..fa43bcb1 100644 --- a/src/frameworks/UBPlatformUtils_mac.mm +++ b/src/frameworks/UBPlatformUtils_mac.mm @@ -31,6 +31,7 @@ #include "core/UBApplication.h" #include "core/UBSettings.h" #include "frameworks/UBFileSystemUtils.h" +#include "gui/UBMainWindow.h" #include @@ -622,35 +623,93 @@ void UBPlatformUtils::showFullScreen(QWidget *pWidget) void UBPlatformUtils::showOSK(bool show) { - @autoreleasepool { - CFDictionaryRef properties = (CFDictionaryRef)[NSDictionary - dictionaryWithObject: @"com.apple.KeyboardViewer" - forKey: (NSString *)kTISPropertyInputSourceID]; + if (QOperatingSystemVersion::current().majorVersion() == 10 && QOperatingSystemVersion::current().minorVersion() < 15) /* < Catalina */ + { + @autoreleasepool { + CFDictionaryRef properties = (CFDictionaryRef)[NSDictionary + dictionaryWithObject: @"com.apple.keyboardViewer" + forKey: (NSString *)kTISPropertyInputSourceID]; - NSArray *sources = (NSArray *)TISCreateInputSourceList(properties, true); + NSArray *sources = (NSArray *)TISCreateInputSourceList(properties, true); - if ([sources count] > 0) { - TISInputSourceRef osk = (TISInputSourceRef)[sources objectAtIndex: 0]; + if ([sources count] > 0) { + TISInputSourceRef osk = (TISInputSourceRef)[sources objectAtIndex: 0]; - OSStatus result; - if (show) { - TISEnableInputSource(osk); - result = TISSelectInputSource(osk); - } - else { - TISDisableInputSource(osk); - result = TISDeselectInputSource(osk); + OSStatus result; + if (show) { + TISEnableInputSource(osk); + result = TISSelectInputSource(osk); + } + else { + TISDisableInputSource(osk); + result = TISDeselectInputSource(osk); + } + + if (result == paramErr) { + qWarning() << "Unable to select input source"; + UBApplication::showMessage(tr("Unable to activate system on-screen keyboard")); + } } - if (result == paramErr) { - qWarning() << "Unable to select input source"; - UBApplication::showMessage(tr("Unable to activate system on-screen keyboard")); + else { + qWarning() << "System OSK not found"; + UBApplication::showMessage(tr("System on-screen keyboard not found")); } } + } + else + { + NSString *source = + @"tell application \"System Events\"\n\ + if application process \"TextInputMenuAgent\" exists then\n\ + tell application process \"TextInputMenuAgent\"\n\ + tell menu bar item 1 of menu bar 2\n\ + ignoring application responses\n\ + click\n\ + end ignoring\n\ + end tell\n\ + end tell\n\ + end if\n\ + end tell\n\ + do shell script \"killall 'System Events'\"\n"; + + source = [source stringByAppendingString:@"if application \"Assistive Control\" is"]; + + if (show) + { + source = [source stringByAppendingString:@" not"]; + } - else { - qWarning() << "System OSK not found"; - UBApplication::showMessage(tr("System on-screen keyboard not found")); + source = [source stringByAppendingString:@" running then\n\ + tell application \"System Events\"\n\ + tell application process \"TextInputMenuAgent\"\n\ + tell menu 1 of menu bar item 1 of menu bar 2\n\ + click menu item 2\n\ + end tell\n\ + end tell\n\ + end tell\n\ + end if"]; + + NSAppleScript *script = [[[NSAppleScript alloc] initWithSource:source] autorelease]; + NSDictionary *errorInfo = nil; + [script executeAndReturnError:&errorInfo]; + + if(errorInfo!=nil) + { + NSAlert *alert = [[NSAlert alloc] init]; + + if (alert != nil) + { + alert.messageText = errorInfo.allValues[0]; + [alert runModal]; + [alert release]; + + //restore action state to previous one as it failed + if (show) + UBApplication::mainWindow->actionVirtualKeyboard->setChecked(false); + else + UBApplication::mainWindow->actionVirtualKeyboard->setChecked(true); + } } } } From 7466b607786a2cc61bea2dcc4a066948a719d7ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Thu, 4 Nov 2021 14:06:38 +0100 Subject: [PATCH 042/130] at the same level, a document and a folder can share the same name --- src/document/UBDocumentController.cpp | 71 ++++++++++++++++++++------- src/document/UBDocumentController.h | 20 +++++--- 2 files changed, 67 insertions(+), 24 deletions(-) diff --git a/src/document/UBDocumentController.cpp b/src/document/UBDocumentController.cpp index 22eda173..cc9c1412 100644 --- a/src/document/UBDocumentController.cpp +++ b/src/document/UBDocumentController.cpp @@ -1034,6 +1034,16 @@ QString UBDocumentTreeModel::virtualPathForIndex(const QModelIndex &pIndex) cons return virtualDirForIndex(pIndex) + "/" + curNode->nodeName(); } +QList UBDocumentTreeModel::nodeChildrenFromIndex(const QModelIndex &pIndex) const +{ + UBDocumentTreeNode *node = nodeFromIndex(pIndex); + + if (node) + return node->children(); + else + return QList(); +} + QStringList UBDocumentTreeModel::nodeNameList(const QModelIndex &pIndex, bool distinctNodeType) const { QStringList result; @@ -1668,14 +1678,20 @@ void UBDocumentTreeItemDelegate::commitAndCloseEditor() void UBDocumentTreeItemDelegate::processChangedText(const QString &str) const { QLineEdit *editor = qobject_cast(sender()); - if (!editor) { - return; - } - - if (!validateString(str)) { - editor->setStyleSheet("background-color: #FFB3C8;"); - } else { - editor->setStyleSheet("background-color: #FFFFFF;"); + if (editor) + { + if (editor->validator()) + { + int pos = 0; + if (editor->validator()->validate(const_cast(str), pos) != QValidator::Acceptable) + { + editor->setStyleSheet("background-color: #FFB3C8;"); + } + else + { + editor->setStyleSheet("background-color: #FFFFFF;"); + } + } } } @@ -1702,17 +1718,29 @@ QWidget *UBDocumentTreeItemDelegate::createEditor(QWidget *parent, const QStyleO QModelIndex sourceIndex = proxy->mapToSource(index); - if (docModel) { + if (docModel) + { mExistingFileNames = docModel->nodeNameList(sourceIndex.parent()); mExistingFileNames.removeOne(sourceIndex.data().toString()); + + UBDocumentTreeNode* sourceNode = docModel->nodeFromIndex(sourceIndex); + + if (sourceNode) + { + QLineEdit *nameEditor = new QLineEdit(parent); + QList nodeChildren = docModel->nodeChildrenFromIndex(sourceIndex.parent()); + nodeChildren.removeOne(sourceNode); + + UBValidator* validator = new UBValidator(nodeChildren, sourceNode->nodeType()); + nameEditor->setValidator(validator); + connect(nameEditor, SIGNAL(editingFinished()), this, SLOT(commitAndCloseEditor())); + connect(nameEditor, SIGNAL(textChanged(QString)), this, SLOT(processChangedText(QString))); + + return nameEditor; + } } - QLineEdit *nameEditor = new QLineEdit(parent); - UBValidator* validator = new UBValidator(mExistingFileNames); - nameEditor->setValidator(validator); - connect(nameEditor, SIGNAL(editingFinished()), this, SLOT(commitAndCloseEditor())); - connect(nameEditor, SIGNAL(textChanged(QString)), this, SLOT(processChangedText(QString))); - return nameEditor; + return nullptr; } //N/C - NNe - 20140407 : the other column are not editable. @@ -1731,8 +1759,17 @@ void UBDocumentTreeItemDelegate::setEditorData(QWidget *editor, const QModelInde void UBDocumentTreeItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { QLineEdit *lineEditor = qobject_cast(editor); - if (validateString(lineEditor->text())) { - model->setData(index, lineEditor->text()); + if (lineEditor) + { + int pos; + QString input = lineEditor->text(); + if (lineEditor->validator()) + { + if (lineEditor->validator()->validate(input, pos) == QValidator::Acceptable) + { + model->setData(index, input); + } + } } } diff --git a/src/document/UBDocumentController.h b/src/document/UBDocumentController.h index fa613d04..6c673097 100644 --- a/src/document/UBDocumentController.h +++ b/src/document/UBDocumentController.h @@ -210,6 +210,7 @@ public: QString virtualDirForIndex(const QModelIndex &pIndex) const; QString virtualPathForIndex(const QModelIndex &pIndex) const; QStringList nodeNameList(const QModelIndex &pIndex, bool distinctNodeType = false) const; + QList nodeChildrenFromIndex(const QModelIndex &pIndex) const; bool newNodeAllowed(const QModelIndex &pSelectedIndex) const; QModelIndex goTo(const QString &dir); bool inTrash(const QModelIndex &index) const; @@ -324,22 +325,27 @@ private: class UBValidator : public QValidator { - const QStringList mExistingFileNames; + const QList mExistingNodes; + UBDocumentTreeNode::Type mEditedNodeType; public: - UBValidator(const QStringList existingFileNames, QObject *parent = nullptr) + UBValidator(const QList existingNodes, UBDocumentTreeNode::Type editedNodeType, QObject *parent = nullptr) : QValidator(parent) - , mExistingFileNames(existingFileNames) + , mExistingNodes(existingNodes) + , mEditedNodeType(editedNodeType) { } QValidator::State validate(QString &input, int &pos) const { - if (mExistingFileNames.contains(input)) - return QValidator::Intermediate; - else - return QValidator::Acceptable; + for (auto node : mExistingNodes) + { + if (node->nodeName() == input && node->nodeType() == mEditedNodeType) + return QValidator::Intermediate; + } + + return QValidator::Acceptable; } }; From d56c61cb5c12f5cd1c410210564b76b67f81f71f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Thu, 4 Nov 2021 18:12:43 +0100 Subject: [PATCH 043/130] fixed a typo preventing old keyboard from launching --- src/frameworks/UBPlatformUtils_mac.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frameworks/UBPlatformUtils_mac.mm b/src/frameworks/UBPlatformUtils_mac.mm index fa43bcb1..34fe2d72 100644 --- a/src/frameworks/UBPlatformUtils_mac.mm +++ b/src/frameworks/UBPlatformUtils_mac.mm @@ -627,7 +627,7 @@ void UBPlatformUtils::showOSK(bool show) { @autoreleasepool { CFDictionaryRef properties = (CFDictionaryRef)[NSDictionary - dictionaryWithObject: @"com.apple.keyboardViewer" + dictionaryWithObject: @"com.apple.KeyboardViewer" forKey: (NSString *)kTISPropertyInputSourceID]; NSArray *sources = (NSArray *)TISCreateInputSourceList(properties, true); From 937b031d6e688400afb4b6d10bbd8c280ebe682f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Fri, 12 Nov 2021 09:31:35 +0100 Subject: [PATCH 044/130] improved thumbnails handling as they could be re-generated 2-3 times with no reason. Huge performance issue in a network-storage context, especially with large files --- src/adaptors/UBSvgSubsetAdaptor.cpp | 2 ++ src/adaptors/UBThumbnailAdaptor.cpp | 25 ++++++--------- src/adaptors/UBThumbnailAdaptor.h | 4 +-- src/core/UBApplicationController.cpp | 2 +- src/core/UBPersistenceManager.cpp | 7 +++-- src/core/UBSceneCache.cpp | 1 + src/document/UBDocumentContainer.cpp | 40 ++++++++++++++---------- src/document/UBDocumentContainer.h | 10 +++--- src/document/UBDocumentController.cpp | 31 +++++++++--------- src/document/UBDocumentController.h | 2 ++ src/gui/UBDocumentNavigator.cpp | 45 ++++++++++++++++++++++----- src/gui/UBDocumentNavigator.h | 2 +- src/gui/UBDocumentThumbnailWidget.cpp | 12 +++++++ src/gui/UBDocumentThumbnailWidget.h | 5 ++- 14 files changed, 120 insertions(+), 68 deletions(-) diff --git a/src/adaptors/UBSvgSubsetAdaptor.cpp b/src/adaptors/UBSvgSubsetAdaptor.cpp index 62a4e524..72dcc042 100644 --- a/src/adaptors/UBSvgSubsetAdaptor.cpp +++ b/src/adaptors/UBSvgSubsetAdaptor.cpp @@ -29,6 +29,7 @@ #include "UBSvgSubsetAdaptor.h" +#include #include #include #include @@ -239,6 +240,7 @@ QString UBSvgSubsetAdaptor::uniboardDocumentNamespaceUriFromVersion(int mFileVer UBGraphicsScene* UBSvgSubsetAdaptor::loadScene(UBDocumentProxy* proxy, const int pageIndex) { + UBApplication::showMessage(QObject::tr("Loading scene (%1/%2)").arg(pageIndex+1).arg(proxy->pageCount())); QString fileName = proxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.svg", pageIndex); qDebug() << fileName; QFile file(fileName); diff --git a/src/adaptors/UBThumbnailAdaptor.cpp b/src/adaptors/UBThumbnailAdaptor.cpp index b73f9e57..0dedef4f 100644 --- a/src/adaptors/UBThumbnailAdaptor.cpp +++ b/src/adaptors/UBThumbnailAdaptor.cpp @@ -54,6 +54,7 @@ void UBThumbnailAdaptor::generateMissingThumbnails(UBDocumentProxy* proxy) for (int iPageNo = 0; iPageNo < existingPageCount; ++iPageNo) { + UBApplication::showMessage(tr("check generateMissingThumbnails (%1/%2)").arg(iPageNo).arg(existingPageCount)); QString thumbFileName = proxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.thumbnail.jpg", iPageNo); QFile thumbFile(thumbFileName); @@ -83,8 +84,9 @@ void UBThumbnailAdaptor::generateMissingThumbnails(UBDocumentProxy* proxy) } } -const QPixmap* UBThumbnailAdaptor::get(UBDocumentProxy* proxy, int pageIndex) +QPixmap UBThumbnailAdaptor::get(UBDocumentProxy* proxy, int pageIndex) { + UBApplication::showMessage(tr("Loading thumbnail (%1/%2)").arg(pageIndex+1).arg(proxy->pageCount())); QString fileName = proxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.thumbnail.jpg", pageIndex); QFile file(fileName); @@ -93,30 +95,21 @@ const QPixmap* UBThumbnailAdaptor::get(UBDocumentProxy* proxy, int pageIndex) generateMissingThumbnails(proxy); } - QPixmap* pix = new QPixmap(); + QPixmap pix; if (file.exists()) { - //Warning. Works only with modified Qt -#ifdef Q_OS_LINUX - pix->load(fileName, 0, Qt::AutoColor); -#else - pix->load(fileName, 0, Qt::AutoColor); -#endif + pix.load(fileName, 0, Qt::AutoColor); } return pix; } -void UBThumbnailAdaptor::load(UBDocumentProxy* proxy, QList& list) +void UBThumbnailAdaptor::load(UBDocumentProxy* proxy, QList>& list) { - generateMissingThumbnails(proxy); - - foreach(const QPixmap* pm, list){ - delete pm; - pm = NULL; - } list.clear(); for(int i=0; ipageCount(); i++) - list.append(get(proxy, i)); + { + list.append(std::make_shared(get(proxy, i))); + } } void UBThumbnailAdaptor::persistScene(UBDocumentProxy* proxy, UBGraphicsScene* pScene, int pageIndex, bool overrideModified) diff --git a/src/adaptors/UBThumbnailAdaptor.h b/src/adaptors/UBThumbnailAdaptor.h index 8c36b724..47f8be17 100644 --- a/src/adaptors/UBThumbnailAdaptor.h +++ b/src/adaptors/UBThumbnailAdaptor.h @@ -45,8 +45,8 @@ public: static void persistScene(UBDocumentProxy* proxy, UBGraphicsScene* pScene, int pageIndex, bool overrideModified = false); - static const QPixmap* get(UBDocumentProxy* proxy, int index); - static void load(UBDocumentProxy* proxy, QList& list); + static QPixmap get(UBDocumentProxy* proxy, int index); + static void load(UBDocumentProxy* proxy, QList>& list); private: static void generateMissingThumbnails(UBDocumentProxy* proxy); diff --git a/src/core/UBApplicationController.cpp b/src/core/UBApplicationController.cpp index ffe0f639..480afb65 100644 --- a/src/core/UBApplicationController.cpp +++ b/src/core/UBApplicationController.cpp @@ -354,7 +354,7 @@ void UBApplicationController::showBoard() int selectedSceneIndex = UBApplication::documentController->getSelectedItemIndex(); if (selectedSceneIndex != -1) { - UBApplication::boardController->setActiveDocumentScene(UBApplication::documentController->selectedDocument(), selectedSceneIndex, true); + UBApplication::boardController->setActiveDocumentScene(UBApplication::documentController->selectedDocument(), selectedSceneIndex); } } diff --git a/src/core/UBPersistenceManager.cpp b/src/core/UBPersistenceManager.cpp index a53342a4..0642f63d 100644 --- a/src/core/UBPersistenceManager.cpp +++ b/src/core/UBPersistenceManager.cpp @@ -809,7 +809,7 @@ void UBPersistenceManager::copyDocumentScene(UBDocumentProxy *from, int fromInde Q_ASSERT(QFileInfo(thumbTmp).exists()); Q_ASSERT(QFileInfo(thumbTo).exists()); - const QPixmap *pix = new QPixmap(thumbTmp); + auto pix = std::make_shared(thumbTmp); UBDocumentController *ctrl = UBApplication::documentController; ctrl->addPixmapAt(pix, toIndex); ctrl->TreeViewSelectionChanged(ctrl->firstSelectedTreeIndex(), QModelIndex()); @@ -820,10 +820,13 @@ void UBPersistenceManager::copyDocumentScene(UBDocumentProxy *from, int fromInde UBGraphicsScene* UBPersistenceManager::createDocumentSceneAt(UBDocumentProxy* proxy, int index, bool useUndoRedoStack) { - int count = sceneCount(proxy); + int count = proxy->pageCount(); for(int i = count - 1; i >= index; i--) + { + UBApplication::showMessage(tr("renaming pages (%1/%2)").arg(i).arg(count)); renamePage(proxy, i , i + 1); + } mSceneCache.shiftUpScenes(proxy, index, count -1); diff --git a/src/core/UBSceneCache.cpp b/src/core/UBSceneCache.cpp index c5a110d1..a3c92bc6 100644 --- a/src/core/UBSceneCache.cpp +++ b/src/core/UBSceneCache.cpp @@ -222,6 +222,7 @@ void UBSceneCache::shiftUpScenes(UBDocumentProxy* proxy, int startIncIndex, int { 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); } } diff --git a/src/document/UBDocumentContainer.cpp b/src/document/UBDocumentContainer.cpp index 481a415a..2609158b 100644 --- a/src/document/UBDocumentContainer.cpp +++ b/src/document/UBDocumentContainer.cpp @@ -40,10 +40,7 @@ UBDocumentContainer::UBDocumentContainer(QObject * parent) UBDocumentContainer::~UBDocumentContainer() { - foreach(const QPixmap* pm, mDocumentThumbs){ - delete pm; - pm = NULL; - } + } void UBDocumentContainer::setDocument(UBDocumentProxy* document, bool forceReload) @@ -52,6 +49,8 @@ void UBDocumentContainer::setDocument(UBDocumentProxy* document, bool forceReloa { mCurrentDocument = document; + //qDebug() << documentThumbs(); + clearThumbPage(); reloadThumbnails(); emit documentSet(mCurrentDocument); } @@ -103,15 +102,14 @@ void UBDocumentContainer::addPage(int index) } -void UBDocumentContainer::addPixmapAt(const QPixmap *pix, int index) +void UBDocumentContainer::addPixmapAt(std::shared_ptr pix, int index) { - mDocumentThumbs.insert(index, pix); + documentThumbs().insert(index, pix); } void UBDocumentContainer::clearThumbPage() { - qDeleteAll(mDocumentThumbs); mDocumentThumbs.clear(); } @@ -126,7 +124,6 @@ void UBDocumentContainer::initThumbPage() void UBDocumentContainer::updatePage(int index) { updateThumbPage(index); - emit documentThumbnailsUpdated(this); } void UBDocumentContainer::deleteThumbPage(int index) @@ -138,8 +135,9 @@ void UBDocumentContainer::updateThumbPage(int index) { if (mDocumentThumbs.size() > index) { - mDocumentThumbs[index] = UBThumbnailAdaptor::get(mCurrentDocument, index); - emit documentPageUpdated(index); + QPixmap pixmap = UBThumbnailAdaptor::get(mCurrentDocument, index); + mDocumentThumbs[index] = std::make_shared(pixmap); + emit documentPageUpdated(index); //refresh specific thumbnail in board } else { @@ -149,15 +147,24 @@ void UBDocumentContainer::updateThumbPage(int index) void UBDocumentContainer::insertThumbPage(int index) { - mDocumentThumbs.insert(index, UBThumbnailAdaptor::get(mCurrentDocument, index)); + QPixmap newPixmap = UBThumbnailAdaptor::get(mCurrentDocument, index); + if (index < documentThumbs().size()) + { + *documentThumbs().at(index) = newPixmap; + } + else + { + documentThumbs().insert(index, std::make_shared(newPixmap)); + } +} + +void UBDocumentContainer::insertExistingThumbPage(int index, std::shared_ptr thumbnailPixmap) +{ + documentThumbs().insert(index, thumbnailPixmap); } void UBDocumentContainer::reloadThumbnails() { - if (mCurrentDocument) - { - UBThumbnailAdaptor::load(mCurrentDocument, mDocumentThumbs); - } emit documentThumbnailsUpdated(this); } @@ -173,6 +180,5 @@ int UBDocumentContainer::sceneIndexFromPage(int page) void UBDocumentContainer::addEmptyThumbPage() { - const QPixmap* pThumb = new QPixmap(); - mDocumentThumbs.append(pThumb); + mDocumentThumbs.append(std::shared_ptr()); } diff --git a/src/document/UBDocumentContainer.h b/src/document/UBDocumentContainer.h index 7be279e0..abf7ab1f 100644 --- a/src/document/UBDocumentContainer.h +++ b/src/document/UBDocumentContainer.h @@ -45,8 +45,9 @@ class UBDocumentContainer : public QObject void pureSetDocument(UBDocumentProxy *document) {mCurrentDocument = document;} UBDocumentProxy* selectedDocument(){return mCurrentDocument;} + QList>& documentThumbs() { return mDocumentThumbs; } int pageCount() const{return mCurrentDocument->pageCount();} - const QPixmap* pageAt(int index) + std::shared_ptr pageAt(int index) { if (index < mDocumentThumbs.size()) return mDocumentThumbs[index]; @@ -65,16 +66,17 @@ class UBDocumentContainer : public QObject void clearThumbPage(); void initThumbPage(); void addPage(int index); - void addPixmapAt(const QPixmap *pix, int index); + void addPixmapAt(std::shared_ptr pix, int index); void updatePage(int index); void addEmptyThumbPage(); - void reloadThumbnails(); + virtual void reloadThumbnails(); void insertThumbPage(int index); + void insertExistingThumbPage(int index, std::shared_ptr thumbnailPixmap); private: UBDocumentProxy* mCurrentDocument; - QList mDocumentThumbs; + QList> mDocumentThumbs; protected: diff --git a/src/document/UBDocumentController.cpp b/src/document/UBDocumentController.cpp index cc9c1412..3f5791d0 100644 --- a/src/document/UBDocumentController.cpp +++ b/src/document/UBDocumentController.cpp @@ -1555,7 +1555,7 @@ void UBDocumentTreeView::dropEvent(QDropEvent *event) Q_ASSERT(QFileInfo(thumbTmp).exists()); Q_ASSERT(QFileInfo(thumbTo).exists()); - const QPixmap *pix = new QPixmap(thumbTmp); + auto pix = std::make_shared(thumbTmp); UBDocumentController *ctrl = UBApplication::documentController; ctrl->addPixmapAt(pix, toIndex); } @@ -2238,7 +2238,6 @@ void UBDocumentController::sortDocuments(int kind, int order) } } - void UBDocumentController::onSortOrderChanged(bool order) { int kindIndex = mDocumentUI->sortKind->currentIndex(); @@ -2297,9 +2296,6 @@ void UBDocumentController::show() { selectDocument(mBoardController->selectedDocument()); - //to be sure thumbnails will be up-to-date - reloadThumbnails(); - updateActions(); if(!mToolsPalette) @@ -3061,6 +3057,11 @@ void UBDocumentController::moveSceneToIndex(UBDocumentProxy* proxy, int source, } } +void UBDocumentController::updateThumbnailPixmap(int index, const QPixmap& newThumbnail) +{ + mDocumentUI->thumbnailWidget->updateThumbnailPixmap(index, newThumbnail); +} + void UBDocumentController::thumbnailViewResized() { @@ -3182,7 +3183,7 @@ void UBDocumentController::addToDocument() UBMetadataDcSubsetAdaptor::persist(mBoardController->selectedDocument()); mBoardController->reloadThumbnails(); - emit UBApplication::boardController->documentThumbnailsUpdated(this); + emit mBoardController->documentThumbnailsUpdated(this); UBApplication::applicationController->showBoard(); mBoardController->setActiveDocumentScene(newActiveSceneIndex); @@ -3583,7 +3584,7 @@ void UBDocumentController::deletePages(QList itemsToDelete) } } UBDocumentContainer::deletePages(sceneIndexes); - emit UBApplication::boardController->documentThumbnailsUpdated(this); + emit mBoardController->documentThumbnailsUpdated(this); proxy->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(QDateTime::currentDateTime())); UBMetadataDcSubsetAdaptor::persist(proxy); @@ -3724,7 +3725,7 @@ bool UBDocumentController::firstAndOnlySceneSelected() const return false; } -void UBDocumentController:: refreshDocumentThumbnailsView(UBDocumentContainer*) +void UBDocumentController:: refreshDocumentThumbnailsView(UBDocumentContainer* source) { UBDocumentTreeModel *docModel = UBPersistenceManager::persistenceManager()->mDocumentTreeStructureModel; UBDocumentProxy *currentDocumentProxy = selectedDocument(); @@ -3740,11 +3741,9 @@ void UBDocumentController:: refreshDocumentThumbnailsView(UBDocumentContainer*) return; } - QList thumbs; - if (currentDocumentProxy) { - UBThumbnailAdaptor::load(currentDocumentProxy, thumbs); + UBThumbnailAdaptor::load(currentDocumentProxy, documentThumbs()); } QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); @@ -3760,7 +3759,9 @@ void UBDocumentController:: refreshDocumentThumbnailsView(UBDocumentContainer*) { for (int i = 0; i < currentDocumentProxy->pageCount(); i++) { - const QPixmap* pix = thumbs.at(i); + UBApplication::showMessage(tr("Refreshing Document Thumbnails View (%1/%2)").arg(i+1).arg(source->selectedDocument()->pageCount())); + + auto pix = documentThumbs().at(i); QGraphicsPixmapItem *pixmapItem = new UBSceneThumbnailPixmap(*pix, currentDocumentProxy, i); // deleted by the tree widget if (currentDocumentProxy == mBoardController->selectedDocument() && mBoardController->activeSceneIndex() == i) @@ -3792,9 +3793,9 @@ void UBDocumentController:: refreshDocumentThumbnailsView(UBDocumentContainer*) if (selection) { disconnect(mDocumentUI->thumbnailWidget->scene(), SIGNAL(selectionChanged()), this, SLOT(pageSelectionChanged())); - UBSceneThumbnailPixmap *currentScene = dynamic_cast(selection); - if (currentScene) - mDocumentUI->thumbnailWidget->hightlightItem(currentScene->sceneIndex()); + UBSceneThumbnailPixmap *currentSceneThumbnailPixmap = dynamic_cast(selection); + if (currentSceneThumbnailPixmap) + mDocumentUI->thumbnailWidget->hightlightItem(currentSceneThumbnailPixmap->sceneIndex()); connect(mDocumentUI->thumbnailWidget->scene(), SIGNAL(selectionChanged()), this, SLOT(pageSelectionChanged())); } diff --git a/src/document/UBDocumentController.h b/src/document/UBDocumentController.h index 6c673097..7fbd555a 100644 --- a/src/document/UBDocumentController.h +++ b/src/document/UBDocumentController.h @@ -501,6 +501,8 @@ class UBDocumentController : public UBDocumentContainer void collapseAll(); void expandAll(); + void updateThumbnailPixmap(int index, const QPixmap& newThumbnail); + protected: virtual void setupViews(); virtual void setupToolbar(); diff --git a/src/gui/UBDocumentNavigator.cpp b/src/gui/UBDocumentNavigator.cpp index 1fa0a6db..7a6ccf51 100644 --- a/src/gui/UBDocumentNavigator.cpp +++ b/src/gui/UBDocumentNavigator.cpp @@ -133,12 +133,31 @@ void UBDocumentNavigator::generateThumbnails(UBDocumentContainer* source) for(int i = 0; i < source->selectedDocument()->pageCount(); i++) { - //claudio This is a very bad hack and shows a architectural problem - // source->selectedDocument()->pageCount() != source->pageCount() - if(i>=source->pageCount() || !source->pageAt(i)) - source->insertThumbPage(i); + UBApplication::showMessage(tr("generating thumbnails for board (%1/%2)").arg(i+1).arg(source->selectedDocument()->pageCount())); - const QPixmap* pix = source->pageAt(i); + bool found = false; + if (UBApplication::documentController) + { + if (UBApplication::documentController->selectedDocument() == source->selectedDocument()) + { + if (UBApplication::documentController->pageAt(i)) + { + found = true; + //thumbnail has already been loaded on the documentController so we don't need to do it again + source->insertExistingThumbPage(i, UBApplication::documentController->pageAt(i)); + } + } + } + + if (!found) + { + //claudio This is a very bad hack and shows a architectural problem + // source->selectedDocument()->pageCount() != source->pageCount() + if(i>=source->pageCount() || !source->pageAt(i)) + source->insertThumbPage(i); + } + + auto pix = source->pageAt(i); Q_ASSERT(!pix->isNull()); int pageIndex = UBDocumentContainer::pageFromSceneIndex(i); @@ -193,12 +212,12 @@ void UBDocumentNavigator::onScrollToSelectedPage(int index) */ void UBDocumentNavigator::updateSpecificThumbnail(int iPage) { - const QPixmap* pix = UBApplication::boardController->pageAt(iPage); + auto pix = UBApplication::boardController->pageAt(iPage); UBSceneThumbnailNavigPixmap* newItem = new UBSceneThumbnailNavigPixmap(*pix, UBApplication::boardController->selectedDocument(), iPage); // Get the old thumbnail UBSceneThumbnailNavigPixmap* oldItem = mThumbsWithLabels.at(iPage).getThumbnail(); - if(NULL != oldItem) + if(oldItem) { mScene->removeItem(oldItem); mScene->addItem(newItem); @@ -209,6 +228,18 @@ void UBDocumentNavigator::updateSpecificThumbnail(int iPage) oldItem = NULL; } + ensureVisible(0, 0, 10, 10); + + refreshScene(); + + if (UBApplication::documentController) + { + if (UBApplication::documentController->selectedDocument() == UBApplication::boardController->selectedDocument()) + { + //update the pixmap in document mode + UBApplication::documentController->updateThumbnailPixmap(iPage, *pix); + } + } } /** diff --git a/src/gui/UBDocumentNavigator.h b/src/gui/UBDocumentNavigator.h index 9e29a2c9..ffe5a447 100644 --- a/src/gui/UBDocumentNavigator.h +++ b/src/gui/UBDocumentNavigator.h @@ -58,7 +58,7 @@ public: public slots: void onScrollToSelectedPage(int index);// { if (mCrntItem) centerOn(mCrntItem); } void generateThumbnails(UBDocumentContainer* source); - void updateSpecificThumbnail(int iPage); + void updateSpecificThumbnail(int iPage); void longPressTimeout(); void mousePressAndHoldEvent(); diff --git a/src/gui/UBDocumentThumbnailWidget.cpp b/src/gui/UBDocumentThumbnailWidget.cpp index 44cf9ebe..4f37beb5 100644 --- a/src/gui/UBDocumentThumbnailWidget.cpp +++ b/src/gui/UBDocumentThumbnailWidget.cpp @@ -297,6 +297,18 @@ bool UBDocumentThumbnailWidget::dragEnabled() const return mDragEnabled; } +void UBDocumentThumbnailWidget::updateThumbnailPixmap(int index, const QPixmap& newThumbnail) +{ + if (index >= 0 && index < mGraphicItems.length()) + { + UBSceneThumbnailPixmap *thumbnail = dynamic_cast(mGraphicItems.at(index)); + if (thumbnail) + { + thumbnail->setPixmap(newThumbnail); + } + } +} + void UBDocumentThumbnailWidget::hightlightItem(int index) { if (0 <= index && index < mLabelsItems.length()) diff --git a/src/gui/UBDocumentThumbnailWidget.h b/src/gui/UBDocumentThumbnailWidget.h index e08b464d..a7e36da3 100644 --- a/src/gui/UBDocumentThumbnailWidget.h +++ b/src/gui/UBDocumentThumbnailWidget.h @@ -48,9 +48,8 @@ class UBDocumentThumbnailWidget: public UBThumbnailWidget void hightlightItem(int index); public slots: - virtual void setGraphicsItems(const QList& pGraphicsItems, - const QList& pItemPaths, const QStringList pLabels = QStringList(), - const QString& pMimeType = QString("")); + void updateThumbnailPixmap(int index, const QPixmap& newThumbnail); + virtual void setGraphicsItems(const QList& pGraphicsItems, const QList& pItemPaths, const QStringList pLabels = QStringList(), const QString& pMimeType = QString("")); signals: void sceneDropped(UBDocumentProxy* proxy, int source, int target); From bd41d79731928e20ad85936366a05651a573d59d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Fri, 12 Nov 2021 09:33:36 +0100 Subject: [PATCH 045/130] fixed an issue where long time press could be triggered if the 'persist document' operations would take longer than the timeout defined for longPressTimer --- src/gui/UBDocumentNavigator.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gui/UBDocumentNavigator.cpp b/src/gui/UBDocumentNavigator.cpp index 7a6ccf51..7d045b3a 100644 --- a/src/gui/UBDocumentNavigator.cpp +++ b/src/gui/UBDocumentNavigator.cpp @@ -333,7 +333,6 @@ void UBDocumentNavigator::mousePressEvent(QMouseEvent *event) if (!event->isAccepted()) { - mLongPressTimer.start(); mLastPressedMousePos = event->pos(); mLastClickedThumbnail = clickedThumbnail(mLastPressedMousePos); @@ -345,6 +344,8 @@ void UBDocumentNavigator::mousePressEvent(QMouseEvent *event) UBApplication::boardController->setActiveDocumentScene(mLastClickedThumbnail->sceneIndex()); UBApplication::boardController->centerOn(UBApplication::boardController->activeScene()->lastCenter()); } + + mLongPressTimer.start(); } } From 4707745ca044459053c7d78258bd50022a8c16c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Fri, 12 Nov 2021 11:43:57 +0100 Subject: [PATCH 046/130] using macports for poppler (so 10.13 lib is available) + updated quazip homebrew's lib (to improve) --- OpenBoard.pro | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/OpenBoard.pro b/OpenBoard.pro index 5ab0de3c..e924b83d 100644 --- a/OpenBoard.pro +++ b/OpenBoard.pro @@ -178,26 +178,25 @@ macx { equals(QT_MAJOR_VERSION, 5):lessThan(QT_MINOR_VERSION, 14) { LIBS += "-L../OpenBoard-ThirdParty/quazip/lib/macx" "-lquazip" } else { - LIBS += -L/usr/local/opt/quazip/lib -lquazip + LIBS += -L/usr/local/opt/quazip/lib -lquazip1-qt5 } - LIBS += -L/usr/local/opt/ffmpeg/lib + LIBS += -L/opt/local/lib INCLUDEPATH += /usr/local/opt/openssl/include - INCLUDEPATH += /usr/local/opt/ffmpeg/include + INCLUDEPATH += /opt/local/include equals(QT_MAJOR_VERSION, 5):lessThan(QT_MINOR_VERSION, 14) { INCLUDEPATH += ../OpenBoard-ThirdParty/quazip/quazip-0.7.1 } else { INCLUDEPATH += /usr/local/opt/quazip/include/quazip } - LIBS += -L/usr/local/opt/poppler/lib -lpoppler - INCLUDEPATH += /usr/local/opt/poppler/include - INCLUDEPATH += /usr/local/opt/poppler/include/poppler + LIBS += -L/opt/local/lib -lpoppler + INCLUDEPATH += /opt/local/include/poppler CONFIG(release, debug|release):CONFIG += x86_64 CONFIG(debug, debug|release):CONFIG += x86_64 QMAKE_MAC_SDK = macosx - QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.10 + QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.13 QMAKE_CXXFLAGS += -Wno-overloaded-virtual #VERSION_RC_PATH = "$$BUILD_DIR/version_rc" From 0b0ef0296256d89507385d4d9406dc052e04519f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Fri, 12 Nov 2021 11:45:45 +0100 Subject: [PATCH 047/130] use macports for ffmpeg lib (no more issues on rpaths) --- release_scripts/osx/release.macx.sh | 34 ++++------------------------- 1 file changed, 4 insertions(+), 30 deletions(-) diff --git a/release_scripts/osx/release.macx.sh b/release_scripts/osx/release.macx.sh index 376fdb68..7bea5a01 100755 --- a/release_scripts/osx/release.macx.sh +++ b/release_scripts/osx/release.macx.sh @@ -19,7 +19,7 @@ PROJECT_ROOT="$SCRIPT_PATH/../.." APPLICATION_NAME="OpenBoard" -BASE_QT_DIR=~/Qt/5.15.0/clang_64 +BASE_QT_DIR=~/Qt/5.15.2/clang_64 # Executables QMAKE=$BASE_QT_DIR/bin/qmake MACDEPLOYQT=$BASE_QT_DIR/bin/macdeployqt @@ -99,7 +99,7 @@ function addImporter { rm MakeFile* rm -rf release rm -rf debug - $QMAKE ${importerName}.pro + $QMAKE ${importerName}.pro -spec macx-clang make -j4 release $MACDEPLOYQT ${importerName}.app cd - @@ -143,9 +143,9 @@ notify "Generating Makefile ..." if [ "$1" == "1010" ]; then - QMAKE_CMD="$QMAKE \"DEFINES+=OS_NEWER_THAN_OR_EQUAL_TO_1010\" $APPLICATION_NAME.pro -spec macx-g++" + QMAKE_CMD="$QMAKE \"DEFINES+=OS_NEWER_THAN_OR_EQUAL_TO_1010\" $APPLICATION_NAME.pro -spec macx-clang" else - QMAKE_CMD="$QMAKE $APPLICATION_NAME.pro -spec macx-g++" + QMAKE_CMD="$QMAKE $APPLICATION_NAME.pro -spec macx-clang" fi $QMAKE_CMD @@ -200,32 +200,6 @@ cd "`pwd`/build/macx/release/product/" $MACDEPLOYQT "`pwd`/$APPLICATION_NAME.app" cd - -# make sure libs installed via homebrew 2.0 refer to in-app libs - notify "relinking libs ..." -# libavformat -install_name_tool "$APP/Contents/Frameworks/libavformat.58.dylib" -change /usr/local/Cellar/ffmpeg/4.3.1_1/lib/libavcodec.58.dylib @executable_path/../Frameworks/libavcodec.58.dylib -install_name_tool "$APP/Contents/Frameworks/libavformat.58.dylib" -change /usr/local/Cellar/ffmpeg/4.3.1_1/lib/libswresample.3.dylib @executable_path/../Frameworks/libswresample.3.dylib -install_name_tool "$APP/Contents/Frameworks/libavformat.58.dylib" -change /usr/local/Cellar/ffmpeg/4.3.1_1/lib/libavutil.56.dylib @executable_path/../Frameworks/libavutil.56.dylib - -# libavcodec -install_name_tool "$APP/Contents/Frameworks/libavcodec.58.dylib" -change /usr/local/Cellar/ffmpeg/4.3.1_1/lib/libswresample.3.dylib @executable_path/../Frameworks/libswresample.3.dylib -install_name_tool "$APP/Contents/Frameworks/libavcodec.58.dylib" -change /usr/local/Cellar/ffmpeg/4.3.1_1/lib/libavutil.56.dylib @executable_path/../Frameworks/libavutil.56.dylib - -#libswresample -install_name_tool "$APP/Contents/Frameworks/libswresample.3.dylib" -change /usr/local/Cellar/ffmpeg/4.3.1_1/lib/libavutil.56.dylib @executable_path/../Frameworks/libavutil.56.dylib - -#libswscale -install_name_tool "$APP/Contents/Frameworks/libswscale.5.dylib" -change /usr/local/Cellar/ffmpeg/4.3.1_1/lib/libavutil.56.dylib @executable_path/../Frameworks/libavutil.56.dylib - -# libhogweed -install_name_tool "$APP/Contents/Frameworks/libhogweed.6.dylib" -change /usr/local/Cellar/nettle/3.6/lib/libnettle.8.dylib @executable_path/../Frameworks/libnettle.8.dylib - -# libssl -install_name_tool "$APP/Contents/Frameworks/libssl.1.1.dylib" -change /usr/local/Cellar/openssl@1.1/1.1.1h/lib/libcrypto.1.1.dylib @executable_path/../Frameworks/libcrypto.1.1.dylib - -# libvorbis -install_name_tool "$APP/Contents/Frameworks/libvorbisenc.2.dylib" -change /usr/local/Cellar/libvorbis/1.3.6/lib/libvorbis.0.dylib @executable_path/../Frameworks/libvorbis.0.dylib - notify "Extracting debug information ..." $DSYMUTIL "$APP/Contents/MacOS/$APPLICATION_NAME" -o "$DSYM" $STRIP -S "$APP/Contents/MacOS/$APPLICATION_NAME" From e45255efc3e660624d2ed5aae3c671fbbcffb73a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Fri, 12 Nov 2021 12:00:48 +0100 Subject: [PATCH 048/130] add .qmake.stash to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6de0441e..46f6fb9f 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,7 @@ Makefile.Sankore* # Build files # ############### +.qmake.stash build install Makefile From 6504c42e755c908f92cad6dd05aef53344a0b9d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Fri, 12 Nov 2021 15:56:41 +0100 Subject: [PATCH 049/130] fixed an issue where mSelectedThumbnail could become inconsistent + fixed an issue where a blank page would be added while importing the same document again and again --- src/core/UBPersistenceManager.cpp | 5 ++++- src/document/UBDocumentContainer.cpp | 1 - src/document/UBDocumentController.cpp | 13 ++++++++----- src/gui/UBDocumentNavigator.cpp | 4 +++- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/core/UBPersistenceManager.cpp b/src/core/UBPersistenceManager.cpp index 0642f63d..ae4c2180 100644 --- a/src/core/UBPersistenceManager.cpp +++ b/src/core/UBPersistenceManager.cpp @@ -238,7 +238,10 @@ QDialog::DialogCode UBPersistenceManager::processInteractiveReplacementDialog(UB if (mDocumentTreeStructureModel->currentIndex() == replaceIndex) { - UBApplication::documentController->selectDocument(pProxy, true, true); + if (pProxy->pageCount() > 0) + { + UBApplication::documentController->selectDocument(pProxy, true, true); + } } if (replaceProxy) { diff --git a/src/document/UBDocumentContainer.cpp b/src/document/UBDocumentContainer.cpp index 2609158b..445b1304 100644 --- a/src/document/UBDocumentContainer.cpp +++ b/src/document/UBDocumentContainer.cpp @@ -49,7 +49,6 @@ void UBDocumentContainer::setDocument(UBDocumentProxy* document, bool forceReloa { mCurrentDocument = document; - //qDebug() << documentThumbs(); clearThumbPage(); reloadThumbnails(); emit documentSet(mCurrentDocument); diff --git a/src/document/UBDocumentController.cpp b/src/document/UBDocumentController.cpp index 3f5791d0..6166f806 100644 --- a/src/document/UBDocumentController.cpp +++ b/src/document/UBDocumentController.cpp @@ -1854,12 +1854,15 @@ void UBDocumentController::selectDocument(UBDocumentProxy* proxy, bool setAsCurr if (setAsCurrentDocument) { UBPersistenceManager::persistenceManager()->mDocumentTreeStructureModel->setCurrentDocument(proxy); QModelIndex indexCurrentDoc = UBPersistenceManager::persistenceManager()->mDocumentTreeStructureModel->indexForProxy(proxy); - mDocumentUI->documentTreeView->setSelectedAndExpanded(indexCurrentDoc, true, editMode); - - if (proxy != mBoardController->selectedDocument()) // only if wanted Document is different from document actually on Board, // ALTI/AOU - 20140217 + if (indexCurrentDoc.isValid()) { - //issue 1629 - NNE - 20131105 : When set a current document, change in the board controller - mBoardController->setActiveDocumentScene(proxy, 0, true, onImport); + mDocumentUI->documentTreeView->setSelectedAndExpanded(indexCurrentDoc, true, editMode); + + if (proxy != mBoardController->selectedDocument()) // only if wanted Document is different from document actually on Board, // ALTI/AOU - 20140217 + { + //issue 1629 - NNE - 20131105 : When set a current document, change in the board controller + mBoardController->setActiveDocumentScene(proxy, 0, true, onImport); + } } } diff --git a/src/gui/UBDocumentNavigator.cpp b/src/gui/UBDocumentNavigator.cpp index 7d045b3a..4f0adba3 100644 --- a/src/gui/UBDocumentNavigator.cpp +++ b/src/gui/UBDocumentNavigator.cpp @@ -224,6 +224,8 @@ void UBDocumentNavigator::updateSpecificThumbnail(int iPage) mThumbsWithLabels[iPage].setThumbnail(newItem); if (mLastClickedThumbnail == oldItem) mLastClickedThumbnail = newItem; + if (mSelectedThumbnail == oldItem) + mSelectedThumbnail = newItem; delete oldItem; oldItem = NULL; } @@ -317,7 +319,7 @@ void UBDocumentNavigator::resizeEvent(QResizeEvent *event) mThumbnailWidth = (width() > mThumbnailMinWidth) ? width() - 2*border() : mThumbnailMinWidth; if(mSelectedThumbnail) - ensureVisible(mSelectedThumbnail); + ensureVisible(mSelectedThumbnail); // Refresh the scene refreshScene(); From 3d6608a29a5e6e96d884677b34057ffb99a880a4 Mon Sep 17 00:00:00 2001 From: letsfindaway Date: Sun, 14 Nov 2021 18:17:34 +0100 Subject: [PATCH 050/130] fix: remove incorrect sRGB profile from iCCP data in png files When starting OpenBoard and eventually during execution the following error message appeared in the log: libpng warning: iCCP: known incorrect sRGB profile This is due to wrong data in some of the png files used for icons in OpenBoard. Removing this wrong information resolves this issue. For further information see https://stackoverflow.com/questions/ 22745076/libpng-warning-iccp-known-incorrect-srgb-profile --- resources/images/favorites.png | Bin 3402 -> 916 bytes resources/images/folder.png | Bin 4008 -> 1958 bytes resources/images/libpalette/social.png | Bin 4547 -> 2076 bytes .../images/stylusPalette/eraserArrow.png | Bin 4255 -> 1797 bytes .../images/stylusPalette/eraserOnArrow.png | Bin 5122 -> 2676 bytes .../images/stylusPalette/markerArrow.png | Bin 3857 -> 1423 bytes .../images/stylusPalette/markerOnArrow.png | Bin 5038 -> 2586 bytes resources/images/stylusPalette/penOnArrow.png | Bin 5209 -> 2759 bytes resources/images/toolPalette/axesTool.png | Bin 8134 -> 10266 bytes resources/images/tools.png | Bin 3508 -> 1067 bytes resources/style/treeview-branch-closed.png | Bin 2944 -> 404 bytes resources/style/treeview-branch-open.png | Bin 2962 -> 405 bytes 12 files changed, 0 insertions(+), 0 deletions(-) diff --git a/resources/images/favorites.png b/resources/images/favorites.png index a3275659c12a87d18ccbb255364caea73d4133e8..30c8dd04095716e20c35f0eb4cf5ebf3f896f61a 100644 GIT binary patch literal 916 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyEa{HEjtmSN`?>!lvVtU&J%W50 z7^+km7#f-x7=Hc-(k~epN(~qoUL`OvSj}Ky5YL|!f7A`A_IZF$i0l9V|6?<1W7BJ6 zGwNc}Yk>&Lg)@M%8FgR;Rt8f8RSZ!EQUKKmHUZ29n+Y))q#eybh%C%%m=zFnK-PiG zftvu)3sDRehqwx+2H{AM326&4v-ipDnZ3%5`6 z+t@inL1V#A%U?j#7?Zr+T^Kr8Wj%l#&H|6fVg?3oVGw3ym^DWND9B#o>Fdh=l#7eY zLNBxL%{HJOTTd6q5R21ygRX};Ir5me8gB9m$r0bMNXcwX!u!Af{~zb{fAg35ZuPl$ zJ7?cK=`(5L#{Zi;T0L7bY8D-8;qP{1Jk;c^G;fRMLxzO&k{zL*D`qVd4E~dSj-f8> z+00qz&rNh;yVk>4zR2fum_PSK21`}ufD2suQJYj9I;R*jBrVdK8g;jJW{8Qw;)bb< zGEAb^hI+q?)1B2Db}>epbxppCe_MiA-{CVg#oFE%?|oyba^W=+xPS9$40Gu>8;Mt% z+uqrQq%tU3S#0rWxL#M%v+Idjpv23$Gmif*sbX_aovrb1XDesG67Fk3`YJ^+Z6_8^ ztrIcw7u1!psEKKvoV{?8$Ca7+0Ufuk_pLwp+}T8*+2Qfxo0pA`?Ax=eTtPG6yzSxI z-*U>%K`-lTjYto%s9pD|hDvk?p3JIplz0t6Jh3QIe8al4_M) zlnSI6j0}tnbqx)GD8#_R%GAut#7x`3$jZQA(i%Py6b-rgDVb@NxHUZfxNQYc1B0il KpUXO@geCwcwq48s delta 3368 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 RR8jx{002ovPDHLkV1icxn+5;? diff --git a/resources/images/libpalette/social.png b/resources/images/libpalette/social.png index 9f1c6dcb781658cd5dbfe0935d0560d857ad2d53..1322f27c6710d8e12636c1837a8ebb23cf8b4f58 100644 GIT binary patch delta 1901 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 diff --git a/resources/images/stylusPalette/eraserArrow.png b/resources/images/stylusPalette/eraserArrow.png index 24f4bf99bb22489fdb13ad451e77a743e696ae74..e1c810980095e52dd01ac6ce1de8bfca79fbc25c 100644 GIT binary patch delta 1725 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 diff --git a/resources/images/stylusPalette/eraserOnArrow.png b/resources/images/stylusPalette/eraserOnArrow.png index ee4953cbd3ab6ffb4c3dd657f1ea002255ab9f65..3d1ec0ea1ceda14ae4e7a6c32ecb0daf403db2f1 100644 GIT binary patch delta 2611 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 VFfilswx|FA002ovPDHLkV1gHm`c?n{ delta 5075 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 diff --git a/resources/images/stylusPalette/markerArrow.png b/resources/images/stylusPalette/markerArrow.png index c8b621bc2af5f2e2bad834009200bd3284bcc2f6..ef1a22814972e4764b2ea793dd7a533a37afd368 100644 GIT binary patch literal 1423 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 TL62@s00000NkvXXu0mjfJMUx4 diff --git a/resources/images/stylusPalette/markerOnArrow.png b/resources/images/stylusPalette/markerOnArrow.png index 36d1d2a80e0f3f0385dc94b07a8d13fe5fd5d16b..e200d0208fcc803f6aacf269773a26e138966259 100644 GIT binary patch delta 2522 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 KMNUMnLSTZGP?vWA diff --git a/resources/images/stylusPalette/penOnArrow.png b/resources/images/stylusPalette/penOnArrow.png index 23aa2f9b2551cdf0edb7e3c079b35be0ffc3551b..674ac0139f53b4d1f3a13c908d3cabf6fd0ce47b 100644 GIT binary patch delta 2695 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 Z_%{bX!3P^n2b2H+002ovPDHLkV1h-;?8yKC diff --git a/resources/images/toolPalette/axesTool.png b/resources/images/toolPalette/axesTool.png index 05008c8cba7f605baf16a85869ae30817d341e22..78be7b0634c13e538064b4e4036718425c7dc229 100644 GIT binary patch literal 10266 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 delta 7809 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 AG5`Po delta 3474 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 AlmGw# diff --git a/resources/style/treeview-branch-closed.png b/resources/style/treeview-branch-closed.png index be02211184e48b5a2139c6fc275800e09114de53..2c566533e5424e5724028032938f99870a3904e9 100644 GIT binary patch delta 327 zcmZn=pTayrQi6qnf#K_P6EPsglJ4m1$iT3%pZiZD>*R@C`rJ%O-tI08|J(b|?4PJ8 zUeCZ@;_2(k{*;T0%R*nS^Ns>gQ@f{&V+hCf$e~)AQd_f=i^h<>o`C9-jY_|E)i5)ABMlFgPWf`se6}*$>SR>$k=8#7jIg zG&FDwaa3gHR&M${W#jXI!D1ZG?UgU@^EbZtuRbNAf#Xloi{1OBlXSV6AOC0XG3A#( z5UX~-;Z+HP#xp%G|B0G`KsTwDxJHzuB$lLFB^RXvDF!10BST$710V`9u&^>UvobN) rHZZa>Ffh5zx&TE(ZhlH;S|xT3A|M^EZF^;aIv6}%{an^LB{Ts5HEnM} delta 2905 zcmV-f3#Rmx1ArHh8Gi-<003W}{G9**010qNS#tmY0V4na0V4r&L3iE&018iOLqkwd zXm50Hb7*gHAW1_*AaHVTW@&6?004N}ol|F2Q|T5x_ulkEONfA!OK(yY2q02Ii+~i7 zCMqEb5K4$4q1hEt!4XA81RKbphy#v}fQ%JUEDVYY*azexqJNHqqlk*i`{8?|Yu3E? z=FR@K*FNX0^PRKL2fzpnmPj*EHGmAMLLL#|gU7_i;p8qrfeIvW01ybXWFd3?BLM*T zemp!YBESc}00DT@3kU$fO`E_l9Ebl8>Oz@Z0f2-7z;ux~O9+4z06=< z09Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p00esgV8|mQcmRZ%02D^@ zS3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D}NL=VFF>AKrX_0nHe&HG!NkO z%m4tOkrff(gY*4(&JM25&Nhy=4qq+mzXtyzVq)X|<DpKGaQJ>aJVl|9x!Kv};eCNs@5@0DoRYBra6Svp>fO002awfhw>;8}z{# zEWidF!3EsG3;bXU&9EIRU@z1_9W=mEXoiz;4lcq~ zxDGvV5BgyUp1~-*fe8db$Osc*A=-!mVv1NJjtCc-h4>-CNCXm#Bp}I%6j35eku^v$ zQh$n6AXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>Xu_CMttHv6zR;&ZN ziS=X8v3CR#fknUxHUxJlp|(=5QHQ7#Gb=$GgN^mhymh82Uyh-WAnn-~WeXBl@Gub51x8Pkgy$5b#kG3%J;nGcz7Rah#v zDtr}@$_kZAl_r%NDlb&2s-~*ms(%Yr^Hs}KkEvc$eXd4TGgITK3DlOWRjQp(>r)$3 zXQ?}=hpK0&Z&W{|ep&sA23f;Q!%st`QJ}G3IcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya?2D1z#2HOnI z7(B%_ac?{wFUQ;QQA1tBKz~D}VU=N*;e?U7(LAHoMvX=fjA_PP<0Rv4#%;! zuC{HqePL%}7iYJ{uEXw=y_0>qeU1G+2MveW4yzqn9e#7PauhmNI^LSjobEq;#q^fx zFK1ZK5YN~%R|78Dq z|Iq-afF%KE1Brn_fm;Im_iKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$3*&ni zm@mj(aCxE5!hiIIrxvL$5-d8FKum~EIF#@~5Gtq^j3x3DcO{MrdBPpSXCg1rHqnUK zLtH8zPVz`9O?r~-k-Rl|B*inOEaka`C#jIUObtxkn>wBrnsy*W_HW0Wrec-#cqqYFCLW#$!oKatOZ#u3V*gjrsz~!DAy_nvS(#iX1~pe z$~l&+o-57m%(KedkT;y~pa1O=!V=+2Q(!ODWcwE=7E3snl`g?;PX*X>OX6feMEuLErma3QLmkw?X+1j)X z-&VBk_4Y;EFPF_I+q;9dL%E~BJh;4Nr^(LEJ3myURP#>OB6F(@)2{oV%K?xm;_x?s~noduI3P8=g1L-SoYA@fQEq)t)&$-M#aAZ}-Lb_1_lV zesU-M&da;mcPH+xyidGe^g!)F*+boj)qg)*{@mE_+<$7occAmp+(-8Yg@e!jk@b%c zLj{kSkIRM)hU=a(|cFn9-q^@|Tmp zZG5Hu>cHz6uiM7L#vZ=Ocr!6x^j7=r!FSwu9q*&x4^QNLAb%+TX!)`AQ_!dTlMNY@ zlm7$*nDhK&GcDVZF)x3sNklJ4!=g7=+=!)B3c+JYUw4PH;DZt)&YPTtTdC z6vP{v;FWm6+oFz5kRT@()4ee7Ov7$ft@nN3hSD%kG8v6VSp_`f9_QE`0$%WtWQNPC z+U~k;Z5epQBW6jiaEkq^+UWbfwIpwN!Y!_GhGR@HZb{xT#~nd#7A_|^#JJ&ONpy+} z9Q;<`b6K>icK%9!EkvhDwuWYYCpki`I%@B~tA7Rn=G>#)^xJt100000NkvXXu0mjf DUG;n= diff --git a/resources/style/treeview-branch-open.png b/resources/style/treeview-branch-open.png index 320b81e546cfafebc6948c6167761c235b7a2005..58fa306254c1a6c497dbb2837f705f4c4cc93f4f 100644 GIT binary patch delta 328 zcmbOvK9zZbqy!5C14H`TYgIssCEd~2k%3`jKlh(R*2xpO^tqXmyxmdRY=o+A-R7+eVN>UO_QmvAUQh^kMk%5t+uAu=Cg&0^^nVMOd sm}?stSs56Z+-6;Xq9HdwB{QuOs|Hc94%fE5GC&;+p00i_>zopr09iM4k^lez delta 2923 zcmV-x3zYPg1Ckez8Gi-<007P89`OJG010qNS#tmY0V4na0V4r&L3iE&018iOLqkwd zXm50Hb7*gHAW1_*AaHVTW@&6?004N}ol|F2Q|T5x_ulkEONfA!OK(yY2q02Ii+~i7 zCMqEb5K4$4q1hEt!4XA81RKbphy#v}fQ%JUEDVYY*azexqJNHqqlk*i`{8?|Yu3E? z=FR@K*FNX0^PRKL2fzpnmPj*EHGmAMLLL#|gU7_i;p8qrfeIvW01ybXWFd3?BLM*T zemp!YBESc}00DT@3kU$fO`E_l9Ebl8>Oz@Z0f2-7z;ux~O9+4z06=< z09Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p00esgV8|mQcmRZ%02D^@ zS3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D}NL=VFF>AKrX_0nHe&HG!NkO z%m4tOkrff(gY*4(&JM25&Nhy=4qq+mzXtyzVq)X|<DpKGaQJ>aJVl|9x!Kv};eCNs@5@0DoRYBra6Svp>fO002awfhw>;8}z{# zEWidF!3EsG3;bXU&9EIRU@z1_9W=mEXoiz;4lcq~ zxDGvV5BgyUp1~-*fe8db$Osc*A=-!mVv1NJjtCc-h4>-CNCXm#Bp}I%6j35eku^v$ zQh$n6AXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>Xu_CMttHv6zR;&ZN ziS=X8v3CR#fknUxHUxJlp|(=5QHQ7#Gb=$GgN^mhymh82Uyh-WAnn-~WeXBl@Gub51x8Pkgy$5b#kG3%J;nGcz7Rah#v zDtr}@$_kZAl_r%NDlb&2s-~*ms(%Yr^Hs}KkEvc$eXd4TGgITK3DlOWRjQp(>r)$3 zXQ?}=hpK0&Z&W{|ep&sA23f;Q!%st`QJ}G3IcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya?2D1z#2HOnI z7(B%_ac?{wFUQ;QQA1tBKz~D}VU=N*;e?U7(LAHoMvX=fjA_PP<0Rv4#%;! zuC{HqePL%}7iYJ{uEXw=y_0>qeU1G+2MveW4yzqn9e#7PauhmNI^LSjobEq;#q^fx zFK1ZK5YN~%R|78Dq z|Iq-afF%KE1Brn_fm;Im_iKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$3*&ni zm@mj(aCxE5!hiIIrxvL$5-d8FKum~EIF#@~5Gtq^j3x3DcO{MrdBPpSXCg1rHqnUK zLtH8zPVz`9O?r~-k-Rl|B*inOEaka`C#jIUObtxkn>wBrnsy*W_HW0Wrec-#cqqYFCLW#$!oKatOZ#u3V*gjrsz~!DAy_nvS(#iX1~pe z$~l&+o-57m%(KedkT;y~pa1O=!V=+2Q(!ODWcwE=7E3snl`g?;PX*X>OX6feMEuLErma3QLmkw?X+1j)X z-&VBk_4Y;EFPF_I+q;9dL%E~BJh;4Nr^(LEJ3myURP#>OB6F(@)2{oV%K?xm;_x?s~noduI3P8=g1L-SoYA@fQEq)t)&$-M#aAZ}-Lb_1_lV zesU-M&da;mcPH+xyidGe^g!)F*+boj)qg)*{@mE_+<$7occAmp+(-8Yg@e!jk@b%c zLj{kSkIRM)hU=a(|cFn9-q^@|Tmp zZG5Hu>cHz6uiM7L#vZ=Ocr!6x^j7=r!FSwu9q*&x4^QNLAb%+TX!)`AQ_!dTlMNY@ zlm7$*nDhK&GcDVZF)x3;Nkl#q0c?}MjEDP7GqKh9^r0|57` VZJ1HJPt^bb002ovPDHLkV1j9)gpmLM From 002a5fefc63f830dc95eddd72ba5e59efeb5b1b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Mon, 15 Nov 2021 10:22:05 +0100 Subject: [PATCH 051/130] ruler and triangle were no longer receiving mouse move events --- src/board/UBBoardView.cpp | 29 +++++++++++++++++++++++++++++ src/tools/UBGraphicsAxes.cpp | 1 - src/tools/UBGraphicsRuler.cpp | 6 ++++-- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/board/UBBoardView.cpp b/src/board/UBBoardView.cpp index 147b78eb..99402ca9 100644 --- a/src/board/UBBoardView.cpp +++ b/src/board/UBBoardView.cpp @@ -1243,6 +1243,35 @@ void UBBoardView::mouseMoveEvent (QMouseEvent *event) if (!mTabletStylusIsPressed && scene()) { scene()->inputDeviceMove(mapToScene(UBGeometryUtils::pointConstrainedInRect(event->pos(), rect())) , mMouseButtonIsPressed); } + if (UBDrawingController::drawingController()->isDrawingTool()) + { + QGraphicsItem* item = scene()->itemAt(mapToScene(event->pos()), QTransform()); + if (item) + { + //if showMarkerPreviewCircle, showPenPreviewCircle or showEraserPreviewCircle is true, then the topmost visible item under the mouse is the preview circle + QGraphicsEllipseItem* circle = dynamic_cast(scene()->itemAt(mapToScene(event->pos()), QTransform())); + if (circle) + { + circle->setVisible(false); + item = scene()->itemAt(mapToScene(event->pos()), QTransform()); + if (item) + { + if (item->type() == UBGraphicsRuler::Type || item->type() == UBGraphicsTriangle::Type) + { + QGraphicsView::mouseMoveEvent(event); + } + } + circle->setVisible(true); + } + else + { + if (item->type() == UBGraphicsRuler::Type || item->type() == UBGraphicsTriangle::Type) + { + QGraphicsView::mouseMoveEvent(event); + } + } + } + } event->accept (); } diff --git a/src/tools/UBGraphicsAxes.cpp b/src/tools/UBGraphicsAxes.cpp index 9fb6be2f..279d0342 100644 --- a/src/tools/UBGraphicsAxes.cpp +++ b/src/tools/UBGraphicsAxes.cpp @@ -428,7 +428,6 @@ void UBGraphicsAxes::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) setCursor(Qt::ArrowCursor); mCloseSvgItem->setVisible(mShowButtons); mNumbersSvgItem->setVisible(mShowButtons); - UBDrawingController::drawingController()->mActiveRuler = NULL; event->accept(); update(); } diff --git a/src/tools/UBGraphicsRuler.cpp b/src/tools/UBGraphicsRuler.cpp index dd4a9e0c..d42ece6a 100644 --- a/src/tools/UBGraphicsRuler.cpp +++ b/src/tools/UBGraphicsRuler.cpp @@ -420,6 +420,9 @@ void UBGraphicsRuler::hoverEnterEvent(QGraphicsSceneHoverEvent *event) { UBStylusTool::Enum currentTool = (UBStylusTool::Enum)UBDrawingController::drawingController ()->stylusTool (); + if (UBDrawingController::drawingController()->mActiveRuler == nullptr) + UBDrawingController::drawingController()->mActiveRuler = this; + if (currentTool == UBStylusTool::Selector || currentTool == UBStylusTool::Play) { @@ -450,7 +453,6 @@ void UBGraphicsRuler::hoverEnterEvent(QGraphicsSceneHoverEvent *event) else if (UBDrawingController::drawingController()->isDrawingTool()) { setCursor(drawRulerLineCursor()); - UBDrawingController::drawingController()->mActiveRuler = this; event->accept(); } } @@ -462,7 +464,7 @@ void UBGraphicsRuler::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) mCloseSvgItem->setVisible(mShowButtons); mResizeSvgItem->setVisible(mShowButtons); mRotateSvgItem->setVisible(mShowButtons); - UBDrawingController::drawingController()->mActiveRuler = NULL; + UBDrawingController::drawingController()->mActiveRuler = nullptr; event->accept(); update(); } From 09fc069edd71d696c3233b98c9bffaa9b4d3870d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Mon, 15 Nov 2021 10:22:52 +0100 Subject: [PATCH 052/130] changed version to 1.6.2a-1115 --- OpenBoard.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenBoard.pro b/OpenBoard.pro index e924b83d..003c6fe5 100644 --- a/OpenBoard.pro +++ b/OpenBoard.pro @@ -11,7 +11,7 @@ VERSION_MAJ = 1 VERSION_MIN = 6 VERSION_PATCH = 2 VERSION_TYPE = a # a = alpha, b = beta, rc = release candidate, r = release, other => error -VERSION_BUILD = 0930 +VERSION_BUILD = 1115 VERSION = "$${VERSION_MAJ}.$${VERSION_MIN}.$${VERSION_PATCH}-$${VERSION_TYPE}.$${VERSION_BUILD}" From bfcaace42e3f65b1dd9cd14f1f84247650842b59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Wed, 17 Nov 2021 18:57:52 +0100 Subject: [PATCH 053/130] fixed a lot of issues regarding thumbnails --- src/board/UBBoardController.cpp | 29 +++++- src/core/UBApplicationController.cpp | 11 +- src/core/UBPersistenceManager.cpp | 2 +- src/document/UBDocumentContainer.cpp | 58 +++++------ src/document/UBDocumentContainer.h | 27 ++--- src/document/UBDocumentController.cpp | 49 +++++++-- src/document/UBDocumentController.h | 5 +- src/gui/UBDocumentNavigator.cpp | 143 ++++++++++++++++++++------ src/gui/UBDocumentNavigator.h | 6 +- src/gui/UBDocumentThumbnailWidget.cpp | 43 ++++++++ src/gui/UBDocumentThumbnailWidget.h | 3 + src/gui/UBThumbnailWidget.cpp | 12 ++- src/gui/UBThumbnailWidget.h | 20 +++- 13 files changed, 308 insertions(+), 100 deletions(-) diff --git a/src/board/UBBoardController.cpp b/src/board/UBBoardController.cpp index 8ba62e58..6ab9bd9a 100644 --- a/src/board/UBBoardController.cpp +++ b/src/board/UBBoardController.cpp @@ -521,6 +521,10 @@ void UBBoardController::addScene() persistCurrentScene(false,true); UBDocumentContainer::addPage(mActiveSceneIndex + 1); + if (UBApplication::documentController->selectedDocument() == selectedDocument()) + { + UBApplication::documentController->insertThumbPage(mActiveSceneIndex+1); + } selectedDocument()->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(QDateTime::currentDateTime())); @@ -584,7 +588,11 @@ void UBBoardController::duplicateScene(int nIndex) scIndexes << nIndex; duplicatePages(scIndexes); insertThumbPage(nIndex); - emit documentThumbnailsUpdated(this); + if (UBApplication::documentController->selectedDocument() == selectedDocument()) + { + UBApplication::documentController->insertThumbPage(nIndex); + } + //emit documentThumbnailsUpdated(this); emit addThumbnailRequired(this, nIndex + 1); selectedDocument()->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(QDateTime::currentDateTime())); @@ -779,6 +787,10 @@ void UBBoardController::deleteScene(int nIndex) QList scIndexes; scIndexes << nIndex; deletePages(scIndexes); + if (UBApplication::documentController->selectedDocument() == selectedDocument()) + { + UBApplication::documentController->deleteThumbPage(nIndex); + } selectedDocument()->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(QDateTime::currentDateTime())); UBMetadataDcSubsetAdaptor::persist(selectedDocument()); @@ -1618,7 +1630,12 @@ void UBBoardController::moveSceneToIndex(int source, int target) { persistCurrentScene(false,true); - UBDocumentContainer::movePageToIndex(source, target); + UBPersistenceManager::persistenceManager()->moveSceneToIndex(selectedDocument(), source, target); + UBDocumentContainer::moveThumbPage(source, target); + if (UBApplication::documentController->selectedDocument() == selectedDocument()) + { + UBApplication::documentController->moveThumbPage(source, target); + } selectedDocument()->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(QDateTime::currentDateTime())); UBPersistenceManager::persistenceManager()->persistDocumentMetadata(selectedDocument()); @@ -1877,7 +1894,7 @@ void UBBoardController::documentSceneChanged(UBDocumentProxy* pDocumentProxy, in if(selectedDocument() == pDocumentProxy) { setActiveDocumentScene(mActiveSceneIndex); - updatePage(pIndex); + updateThumbPage(pIndex); } } @@ -2021,7 +2038,11 @@ void UBBoardController::persistCurrentScene(bool isAnAutomaticBackup, bool force && (mActiveScene->isModified())) { UBPersistenceManager::persistenceManager()->persistDocumentScene(selectedDocument(), mActiveScene, mActiveSceneIndex, isAnAutomaticBackup); - updatePage(mActiveSceneIndex); + updateThumbPage(mActiveSceneIndex); + if (UBApplication::documentController->selectedDocument() == selectedDocument()) + { + UBApplication::documentController->updateThumbPage(mActiveSceneIndex); + } } } diff --git a/src/core/UBApplicationController.cpp b/src/core/UBApplicationController.cpp index 480afb65..27f558c5 100644 --- a/src/core/UBApplicationController.cpp +++ b/src/core/UBApplicationController.cpp @@ -351,11 +351,11 @@ void UBApplicationController::showBoard() if (mMainMode == Document) { - int selectedSceneIndex = UBApplication::documentController->getSelectedItemIndex(); - if (selectedSceneIndex != -1) - { - UBApplication::boardController->setActiveDocumentScene(UBApplication::documentController->selectedDocument(), selectedSceneIndex); - } +// int selectedSceneIndex = UBApplication::documentController->getSelectedItemIndex(); +// if (selectedSceneIndex != -1) +// { +// UBApplication::boardController->setActiveDocumentScene(UBApplication::documentController->selectedDocument(), selectedSceneIndex); +// } } mMainMode = Board; @@ -435,6 +435,7 @@ void UBApplicationController::showDocument() { if (UBApplication::boardController->activeScene()->isModified()) UBApplication::boardController->persistCurrentScene(); + UBApplication::boardController->hide(); } diff --git a/src/core/UBPersistenceManager.cpp b/src/core/UBPersistenceManager.cpp index ae4c2180..2aec77db 100644 --- a/src/core/UBPersistenceManager.cpp +++ b/src/core/UBPersistenceManager.cpp @@ -827,7 +827,6 @@ UBGraphicsScene* UBPersistenceManager::createDocumentSceneAt(UBDocumentProxy* pr for(int i = count - 1; i >= index; i--) { - UBApplication::showMessage(tr("renaming pages (%1/%2)").arg(i).arg(count)); renamePage(proxy, i , i + 1); } @@ -977,6 +976,7 @@ UBDocumentProxy* UBPersistenceManager::persistDocumentMetadata(UBDocumentProxy* void UBPersistenceManager::renamePage(UBDocumentProxy* 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)); diff --git a/src/document/UBDocumentContainer.cpp b/src/document/UBDocumentContainer.cpp index 445b1304..c1164ce3 100644 --- a/src/document/UBDocumentContainer.cpp +++ b/src/document/UBDocumentContainer.cpp @@ -65,16 +65,15 @@ void UBDocumentContainer::duplicatePages(QList& pageIndexes) } } -bool UBDocumentContainer::movePageToIndex(int source, int target) +void UBDocumentContainer::moveThumbPage(int source, int target) { - //on document view - UBPersistenceManager::persistenceManager()->moveSceneToIndex(mCurrentDocument, source, target); - deleteThumbPage(source); - insertThumbPage(target); - emit documentThumbnailsUpdated(this); - //on board thumbnails view + mDocumentThumbs.move(source, target); + + //on board thumbnails view (UBDocumentNavigator) + emit documentPageMoved(source, target); + + //on board thumbnails view (UBoardThumbnailsView) emit moveThumbnailRequired(source, target); - return true; } void UBDocumentContainer::deletePages(QList& pageIndexes) @@ -84,26 +83,20 @@ void UBDocumentContainer::deletePages(QList& pageIndexes) foreach(int index, pageIndexes) { deleteThumbPage(index - offset); - emit removeThumbnailRequired(index - offset); offset++; - } - emit documentThumbnailsUpdated(this); } void UBDocumentContainer::addPage(int index) { UBPersistenceManager::persistenceManager()->createDocumentSceneAt(mCurrentDocument, index); insertThumbPage(index); - - emit documentThumbnailsUpdated(this); - emit addThumbnailRequired(this, index); } void UBDocumentContainer::addPixmapAt(std::shared_ptr pix, int index) { - documentThumbs().insert(index, pix); + mDocumentThumbs.insert(index, pix); } @@ -120,14 +113,15 @@ void UBDocumentContainer::initThumbPage() insertThumbPage(i); } -void UBDocumentContainer::updatePage(int index) -{ - updateThumbPage(index); -} - void UBDocumentContainer::deleteThumbPage(int index) { mDocumentThumbs.removeAt(index); + + //on board thumbnails view (UBDocumentNavigator) + emit documentPageRemoved(index); + + //on board thumbnails view (UBoardThumbnailsView) + emit removeThumbnailRequired(index); } void UBDocumentContainer::updateThumbPage(int index) @@ -136,30 +130,26 @@ void UBDocumentContainer::updateThumbPage(int index) { QPixmap pixmap = UBThumbnailAdaptor::get(mCurrentDocument, index); mDocumentThumbs[index] = std::make_shared(pixmap); - emit documentPageUpdated(index); //refresh specific thumbnail in board - } - else - { - qDebug() << "error [updateThumbPage] : index > mDocumentThumbs' size."; + + emit documentPageUpdated(index); } } void UBDocumentContainer::insertThumbPage(int index) { QPixmap newPixmap = UBThumbnailAdaptor::get(mCurrentDocument, index); - if (index < documentThumbs().size()) - { - *documentThumbs().at(index) = newPixmap; - } - else - { - documentThumbs().insert(index, std::make_shared(newPixmap)); - } + mDocumentThumbs.insert(index, std::make_shared(newPixmap)); + + emit documentPageInserted(index); + emit addThumbnailRequired(this, index); } void UBDocumentContainer::insertExistingThumbPage(int index, std::shared_ptr thumbnailPixmap) { - documentThumbs().insert(index, thumbnailPixmap); + mDocumentThumbs.insert(index, thumbnailPixmap); + + emit documentPageInserted(index); + emit addThumbnailRequired(this, index); } void UBDocumentContainer::reloadThumbnails() diff --git a/src/document/UBDocumentContainer.h b/src/document/UBDocumentContainer.h index abf7ab1f..aa220895 100644 --- a/src/document/UBDocumentContainer.h +++ b/src/document/UBDocumentContainer.h @@ -61,39 +61,40 @@ class UBDocumentContainer : public QObject static int sceneIndexFromPage(int sceneIndex); void duplicatePages(QList& pageIndexes); - bool movePageToIndex(int source, int target); void deletePages(QList& pageIndexes); - void clearThumbPage(); - void initThumbPage(); + + void addPage(int index); void addPixmapAt(std::shared_ptr pix, int index); - void updatePage(int index); - void addEmptyThumbPage(); + virtual void reloadThumbnails(); - void insertThumbPage(int index); + void clearThumbPage(); + void initThumbPage(); void insertExistingThumbPage(int index, std::shared_ptr thumbnailPixmap); + void insertThumbPage(int index); + void addEmptyThumbPage(); + void deleteThumbPage(int index); + void updateThumbPage(int index); + void moveThumbPage(int source, int target); private: UBDocumentProxy* mCurrentDocument; QList> mDocumentThumbs; - - protected: - void deleteThumbPage(int index); - void updateThumbPage(int index); - signals: void documentSet(UBDocumentProxy* document); + void documentPageInserted(int index); void documentPageUpdated(int index); + void documentPageRemoved(int index); + void documentPageMoved(int from, int to); + void documentThumbnailsUpdated(UBDocumentContainer* source); void initThumbnailsRequired(UBDocumentContainer* source); void addThumbnailRequired(UBDocumentContainer* source, int index); void removeThumbnailRequired(int index); void moveThumbnailRequired(int from, int to); void updateThumbnailsRequired(); - - void documentThumbnailsUpdated(UBDocumentContainer* source); }; diff --git a/src/document/UBDocumentController.cpp b/src/document/UBDocumentController.cpp index 6166f806..4038656c 100644 --- a/src/document/UBDocumentController.cpp +++ b/src/document/UBDocumentController.cpp @@ -1555,9 +1555,14 @@ void UBDocumentTreeView::dropEvent(QDropEvent *event) Q_ASSERT(QFileInfo(thumbTmp).exists()); Q_ASSERT(QFileInfo(thumbTo).exists()); + auto pix = std::make_shared(thumbTmp); UBDocumentController *ctrl = UBApplication::documentController; ctrl->addPixmapAt(pix, toIndex); + if (UBApplication::boardController->selectedDocument() == targetDocProxy) + { + UBApplication::boardController->insertThumbPage(toIndex); + } } QApplication::restoreOverrideCursor(); @@ -1800,6 +1805,10 @@ UBDocumentController::UBDocumentController(UBMainWindow* mainWindow) setupToolbar(); connect(this, SIGNAL(exportDone()), mMainWindow, SLOT(onExportDone())); connect(this, SIGNAL(documentThumbnailsUpdated(UBDocumentContainer*)), this, SLOT(refreshDocumentThumbnailsView(UBDocumentContainer*))); + connect(this, SIGNAL(documentPageInserted(int)), this, SLOT(insertThumbnail(int))); + connect(this, SIGNAL(documentPageUpdated(int)), this, SLOT(updateThumbnail(int))); + connect(this, SIGNAL(documentPageRemoved(int)), this, SLOT(removeThumbnail(int))); + connect(this, SIGNAL(documentPageMoved(int, int)), this, SLOT(moveThumbnail(int, int))); connect(this, SIGNAL(reorderDocumentsRequested()), this, SLOT(reorderDocuments())); } @@ -3048,23 +3057,45 @@ bool UBDocumentController::addFileToDocument(UBDocumentProxy* document) void UBDocumentController::moveSceneToIndex(UBDocumentProxy* proxy, int source, int target) { - if (UBDocumentContainer::movePageToIndex(source, target)) - { - proxy->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(QDateTime::currentDateTime())); - UBMetadataDcSubsetAdaptor::persist(proxy); + UBPersistenceManager::persistenceManager()->moveSceneToIndex(proxy, source, target); - mDocumentUI->thumbnailWidget->hightlightItem(target); + proxy->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(QDateTime::currentDateTime())); + UBMetadataDcSubsetAdaptor::persist(proxy); - mBoardController->setActiveDocumentScene(target); - mBoardController->reloadThumbnails(); + UBDocumentContainer::moveThumbPage(source, target); + if (UBApplication::boardController->selectedDocument() == selectedDocument()) + { + UBApplication::boardController->moveThumbPage(source, target); } + mDocumentUI->thumbnailWidget->hightlightItem(target); + + //mBoardController->setActiveDocumentScene(target); +} + +void UBDocumentController::insertThumbnail(int index, const QPixmap& pix) +{ + QGraphicsPixmapItem *newThumbnail = new UBSceneThumbnailPixmap(pix, selectedDocument(), index); // deleted by the tree widget + + mDocumentUI->thumbnailWidget->insertThumbnail(index, newThumbnail); +} + +void UBDocumentController::updateThumbnail(int index) +{ + auto pix = UBApplication::boardController->pageAt(index); + + mDocumentUI->thumbnailWidget->updateThumbnailPixmap(index, *pix); } -void UBDocumentController::updateThumbnailPixmap(int index, const QPixmap& newThumbnail) + +void UBDocumentController::removeThumbnail(int index) { - mDocumentUI->thumbnailWidget->updateThumbnailPixmap(index, newThumbnail); + mDocumentUI->thumbnailWidget->removeThumbnail(index); } +void UBDocumentController::moveThumbnail(int from, int to) +{ + mDocumentUI->thumbnailWidget->moveThumbnail(from, to); +} void UBDocumentController::thumbnailViewResized() { diff --git a/src/document/UBDocumentController.h b/src/document/UBDocumentController.h index 7fbd555a..3b634175 100644 --- a/src/document/UBDocumentController.h +++ b/src/document/UBDocumentController.h @@ -501,7 +501,10 @@ class UBDocumentController : public UBDocumentContainer void collapseAll(); void expandAll(); - void updateThumbnailPixmap(int index, const QPixmap& newThumbnail); + void updateThumbnail(int index); + void removeThumbnail(int index); + void moveThumbnail(int from, int to); + void insertThumbnail(int index, const QPixmap& pix); protected: virtual void setupViews(); diff --git a/src/gui/UBDocumentNavigator.cpp b/src/gui/UBDocumentNavigator.cpp index 4f0adba3..60ecc1f4 100644 --- a/src/gui/UBDocumentNavigator.cpp +++ b/src/gui/UBDocumentNavigator.cpp @@ -83,7 +83,10 @@ UBDocumentNavigator::UBDocumentNavigator(QWidget *parent, const char *name):QGra setFrameShadow(QFrame::Plain); connect(UBApplication::boardController, SIGNAL(documentThumbnailsUpdated(UBDocumentContainer*)), this, SLOT(generateThumbnails(UBDocumentContainer*))); - connect(UBApplication::boardController, SIGNAL(documentPageUpdated(int)), this, SLOT(updateSpecificThumbnail(int))); + connect(UBApplication::boardController, SIGNAL(documentPageInserted(int)), this, SLOT(insertThumbnail(int))); + connect(UBApplication::boardController, SIGNAL(documentPageUpdated(int)), this, SLOT(updateThumbnail(int))); + connect(UBApplication::boardController, SIGNAL(documentPageRemoved(int)), this, SLOT(removeThumbnail(int))); + connect(UBApplication::boardController, SIGNAL(documentPageMoved(int, int)), this, SLOT(moveThumbnail(int, int))); connect(UBApplication::boardController, SIGNAL(pageSelectionChanged(int)), this, SLOT(onScrollToSelectedPage(int))); connect(&mLongPressTimer, SIGNAL(timeout()), this, SLOT(longPressTimeout()), Qt::UniqueConnection); @@ -140,6 +143,7 @@ void UBDocumentNavigator::generateThumbnails(UBDocumentContainer* source) { if (UBApplication::documentController->selectedDocument() == source->selectedDocument()) { + if (UBApplication::documentController->pageAt(i)) { found = true; @@ -154,27 +158,31 @@ void UBDocumentNavigator::generateThumbnails(UBDocumentContainer* source) //claudio This is a very bad hack and shows a architectural problem // source->selectedDocument()->pageCount() != source->pageCount() if(i>=source->pageCount() || !source->pageAt(i)) + { source->insertThumbPage(i); - } - - auto pix = source->pageAt(i); - Q_ASSERT(!pix->isNull()); - int pageIndex = UBDocumentContainer::pageFromSceneIndex(i); + } + else + { + auto pix = source->pageAt(i); + Q_ASSERT(!pix->isNull()); + int pageIndex = UBDocumentContainer::pageFromSceneIndex(i); - UBSceneThumbnailNavigPixmap* pixmapItem = new UBSceneThumbnailNavigPixmap(*pix, source->selectedDocument(), i); + UBSceneThumbnailNavigPixmap* pixmapItem = new UBSceneThumbnailNavigPixmap(*pix, source->selectedDocument(), i); - QString label = tr("Page %0").arg(pageIndex); - UBThumbnailTextItem *labelItem = new UBThumbnailTextItem(label); + QString label = tr("Page %0").arg(pageIndex); + UBThumbnailTextItem *labelItem = new UBThumbnailTextItem(label); - UBImgTextThumbnailElement thumbWithText(pixmapItem, labelItem); - thumbWithText.setBorder(border()); - mThumbsWithLabels.append(thumbWithText); + UBImgTextThumbnailElement thumbWithText(pixmapItem, labelItem); + thumbWithText.setBorder(border()); + mThumbsWithLabels.append(thumbWithText); - if (lastClickedIndex == i) - mLastClickedThumbnail = pixmapItem; + if (lastClickedIndex == i) + mLastClickedThumbnail = pixmapItem; - mScene->addItem(pixmapItem); - mScene->addItem(labelItem); + mScene->addItem(pixmapItem); + mScene->addItem(labelItem); + } + } } if (selectedIndex >= 0 && selectedIndex < mThumbsWithLabels.count()) @@ -210,18 +218,20 @@ void UBDocumentNavigator::onScrollToSelectedPage(int index) * \brief Refresh the given thumbnail * @param iPage as the given page related thumbnail */ -void UBDocumentNavigator::updateSpecificThumbnail(int iPage) +void UBDocumentNavigator::updateThumbnail(int index) { - auto pix = UBApplication::boardController->pageAt(iPage); - UBSceneThumbnailNavigPixmap* newItem = new UBSceneThumbnailNavigPixmap(*pix, UBApplication::boardController->selectedDocument(), iPage); + auto pix = UBApplication::boardController->pageAt(index); + UBSceneThumbnailNavigPixmap* newItem = new UBSceneThumbnailNavigPixmap(*pix, UBApplication::boardController->selectedDocument(), index); // Get the old thumbnail - UBSceneThumbnailNavigPixmap* oldItem = mThumbsWithLabels.at(iPage).getThumbnail(); + UBSceneThumbnailNavigPixmap* oldItem = mThumbsWithLabels.at(index).getThumbnail(); if(oldItem) { + if (oldItem->isSelected()) + mScene->removeItem(oldItem->selectionItem()); mScene->removeItem(oldItem); mScene->addItem(newItem); - mThumbsWithLabels[iPage].setThumbnail(newItem); + mThumbsWithLabels[index].setThumbnail(newItem); if (mLastClickedThumbnail == oldItem) mLastClickedThumbnail = newItem; if (mSelectedThumbnail == oldItem) @@ -233,15 +243,59 @@ void UBDocumentNavigator::updateSpecificThumbnail(int iPage) ensureVisible(0, 0, 10, 10); refreshScene(); +} + +void UBDocumentNavigator::removeThumbnail(int index) +{ + //remove selectionItem + if (mThumbsWithLabels.at(index).getThumbnail()->isSelected()) + mScene->removeItem(mThumbsWithLabels.at(index).getThumbnail()->selectionItem()); + + mScene->removeItem(mThumbsWithLabels.at(index).getCaption()); + mScene->removeItem(mThumbsWithLabels.at(index).getThumbnail()); - if (UBApplication::documentController) + mThumbsWithLabels.removeAt(index); + + //update thumbs page number accordingly + for (int i=0; i < mThumbsWithLabels.length(); i++) { - if (UBApplication::documentController->selectedDocument() == UBApplication::boardController->selectedDocument()) - { - //update the pixmap in document mode - UBApplication::documentController->updateThumbnailPixmap(iPage, *pix); - } + mThumbsWithLabels.at(i).getCaption()->setText(tr("Page %0").arg(i+1)); + } + + refreshScene(); +} + +void UBDocumentNavigator::insertThumbnail(int index) +{ + auto pix = UBApplication::boardController->pageAt(index); + UBSceneThumbnailNavigPixmap* pixmapItem = new UBSceneThumbnailNavigPixmap(*pix, UBApplication::boardController->selectedDocument(), index); + + QString label = tr("Page %0").arg(index+1); + UBThumbnailTextItem *labelItem = new UBThumbnailTextItem(label); + labelItem->setWidth(mThumbnailWidth); + + UBImgTextThumbnailElement thumbWithText(pixmapItem, labelItem); + thumbWithText.setBorder(border()); + + mThumbsWithLabels.insert(index, thumbWithText); + + if (mLastClickedThumbnail) + { + if (mLastClickedThumbnail->sceneIndex() == index) + mLastClickedThumbnail = pixmapItem; } + + //sceneIndex is 0-based while pageNumber is 1-based + for (int i=0; i < mThumbsWithLabels.size(); i++) + { + mThumbsWithLabels.at(i).getThumbnail()->setSceneIndex(i); + mThumbsWithLabels.at(i).getCaption()->setPageNumber(i+1); + } + + mScene->addItem(pixmapItem); + mScene->addItem(labelItem); + + refreshScene(); } /** @@ -319,7 +373,7 @@ void UBDocumentNavigator::resizeEvent(QResizeEvent *event) mThumbnailWidth = (width() > mThumbnailMinWidth) ? width() - 2*border() : mThumbnailMinWidth; if(mSelectedThumbnail) - ensureVisible(mSelectedThumbnail); + ensureVisible(mSelectedThumbnail); // Refresh the scene refreshScene(); @@ -351,6 +405,17 @@ void UBDocumentNavigator::mousePressEvent(QMouseEvent *event) } } +void UBDocumentNavigator::clearSelection() +{ + for (auto item : mScene->items()) + { + if (item->type() == QGraphicsRectItem::Type && item != mDropBar) + { + mScene->removeItem(item); + } + } +} + UBSceneThumbnailNavigPixmap* UBDocumentNavigator::clickedThumbnail(const QPoint pos) const { UBSceneThumbnailNavigPixmap* clickedThumbnail = NULL; @@ -600,8 +665,12 @@ void UBDocumentNavigator::dropEvent(QDropEvent *event) if (mDropSource && mDropTarget) { - if (mDropSource->sceneIndex() != mDropTarget->sceneIndex()) - UBApplication::boardController->moveSceneToIndex(mDropSource->sceneIndex(), mDropTarget->sceneIndex()); + int sourceIndex = mDropSource->sceneIndex(); + int targetIndex = mDropTarget->sceneIndex(); + if (sourceIndex != targetIndex) + { + UBApplication::boardController->moveSceneToIndex(sourceIndex, targetIndex); + } } mDropSource = NULL; @@ -611,3 +680,17 @@ void UBDocumentNavigator::dropEvent(QDropEvent *event) mDropBar->setRect(QRectF()); mDropBar->hide(); } + +void UBDocumentNavigator::moveThumbnail(int from, int to) +{ + mThumbsWithLabels.move(from, to); + + //sceneIndex is 0-based while pageNumber is 1-based + for (int i=0; i < mThumbsWithLabels.size(); i++) + { + mThumbsWithLabels.at(i).getThumbnail()->setSceneIndex(i); + mThumbsWithLabels.at(i).getCaption()->setPageNumber(i+1); + } + + refreshScene(); +} diff --git a/src/gui/UBDocumentNavigator.h b/src/gui/UBDocumentNavigator.h index ffe5a447..1a252a6e 100644 --- a/src/gui/UBDocumentNavigator.h +++ b/src/gui/UBDocumentNavigator.h @@ -53,12 +53,16 @@ public: int nbColumns(); void setThumbnailMinWidth(int width); int thumbnailMinWidth(); + void clearSelection(); UBSceneThumbnailNavigPixmap* clickedThumbnail(const QPoint pos) const; public slots: void onScrollToSelectedPage(int index);// { if (mCrntItem) centerOn(mCrntItem); } void generateThumbnails(UBDocumentContainer* source); - void updateSpecificThumbnail(int iPage); + void insertThumbnail(int index); + void updateThumbnail(int index); + void removeThumbnail(int index); + void moveThumbnail(int from, int to); void longPressTimeout(); void mousePressAndHoldEvent(); diff --git a/src/gui/UBDocumentThumbnailWidget.cpp b/src/gui/UBDocumentThumbnailWidget.cpp index 4f37beb5..45dbc52d 100644 --- a/src/gui/UBDocumentThumbnailWidget.cpp +++ b/src/gui/UBDocumentThumbnailWidget.cpp @@ -309,6 +309,49 @@ void UBDocumentThumbnailWidget::updateThumbnailPixmap(int index, const QPixmap& } } +void UBDocumentThumbnailWidget::removeThumbnail(int sceneIndex) +{ + if (sceneIndex >= 0 && sceneIndex < mGraphicItems.length()) + { + QGraphicsItem* thumbnailItem = mGraphicItems.at(sceneIndex); + QGraphicsItem* textItem = mLabelsItems.at(sceneIndex); + UBSceneThumbnailPixmap *thumbnail = dynamic_cast(thumbnailItem); + if (thumbnail) + { + mGraphicItems.removeAt(sceneIndex); + if (thumbnailItem) + scene()->removeItem(thumbnailItem); + + mLabelsItems.removeAt(sceneIndex); + if (textItem) + scene()->removeItem(textItem); + } + refreshScene(); + } +} + +void UBDocumentThumbnailWidget::insertThumbnail(int index, QGraphicsPixmapItem* newThumbnail) +{ + if (!mGraphicItems.contains(newThumbnail)) //sometimes, refreshDocumentThumbnailsView is called before + { + auto thumbnailTextItem = new UBThumbnailTextItem(index); + mGraphicItems.insert(index, newThumbnail); + mLabelsItems.insert(index, thumbnailTextItem); + } + + refreshScene(); +} + +void UBDocumentThumbnailWidget::moveThumbnail(int from, int to) +{ + UBSceneThumbnailPixmap *thumbnail = dynamic_cast(mGraphicItems.at(from)); + if (thumbnail) + { + mGraphicItems.move(from, to); + } + refreshScene(); +} + void UBDocumentThumbnailWidget::hightlightItem(int index) { if (0 <= index && index < mLabelsItems.length()) diff --git a/src/gui/UBDocumentThumbnailWidget.h b/src/gui/UBDocumentThumbnailWidget.h index a7e36da3..5ac9e620 100644 --- a/src/gui/UBDocumentThumbnailWidget.h +++ b/src/gui/UBDocumentThumbnailWidget.h @@ -49,6 +49,9 @@ class UBDocumentThumbnailWidget: public UBThumbnailWidget public slots: void updateThumbnailPixmap(int index, const QPixmap& newThumbnail); + void removeThumbnail(int index); + void moveThumbnail(int from, int to); + void insertThumbnail(int index, QGraphicsPixmapItem *newThumbnail); virtual void setGraphicsItems(const QList& pGraphicsItems, const QList& pItemPaths, const QStringList pLabels = QStringList(), const QString& pMimeType = QString("")); signals: diff --git a/src/gui/UBThumbnailWidget.cpp b/src/gui/UBThumbnailWidget.cpp index b31bdd1e..ba3694d2 100644 --- a/src/gui/UBThumbnailWidget.cpp +++ b/src/gui/UBThumbnailWidget.cpp @@ -136,6 +136,12 @@ void UBThumbnailWidget::setGraphicsItems(const QList& pGraphicsI mLastSelectedThumbnail = 0; } +void UBThumbnailWidget::insertThumbnailToScene(QGraphicsPixmapItem* newThumbnail, UBThumbnailTextItem* thumbnailTextItem) +{ + mThumbnailsScene.addItem(newThumbnail); + mThumbnailsScene.addItem(thumbnailTextItem); +} + void UBThumbnailWidget::refreshScene() { @@ -156,6 +162,10 @@ void UBThumbnailWidget::refreshScene() { QGraphicsItem* item = mGraphicItems.at(i); + UBSceneThumbnailPixmap *thumbnail = dynamic_cast(item); + if (thumbnail) + thumbnail->setSceneIndex(i); + qreal scaleWidth = mThumbnailWidth / item->boundingRect().width(); qreal scaleHeight = thumbnailHeight / item->boundingRect().height(); @@ -193,7 +203,7 @@ void UBThumbnailWidget::refreshScene() if (mLabelsItems.size() > i) { QFontMetrics fm(mLabelsItems.at(i)->font(), this); - QString elidedText = fm.elidedText(mLabels.at(i), Qt::ElideRight, mThumbnailWidth); + QString elidedText = fm.elidedText(mLabelsItems.at(i)->toPlainText(), Qt::ElideRight, mThumbnailWidth); mLabelsItems.at(i)->setPlainText(elidedText); mLabelsItems.at(i)->setWidth(fm.width(elidedText) + 2 * mLabelsItems.at(i)->document()->documentMargin()); diff --git a/src/gui/UBThumbnailWidget.h b/src/gui/UBThumbnailWidget.h index c286f00d..527b9cc2 100644 --- a/src/gui/UBThumbnailWidget.h +++ b/src/gui/UBThumbnailWidget.h @@ -81,6 +81,7 @@ class UBThumbnailWidget : public QGraphicsView void setThumbnailWidth(qreal pThumbnailWidth); void setSpacing(qreal pSpacing); virtual void setGraphicsItems(const QList& pGraphicsItems, const QList& pItemPaths, const QStringList pLabels = QStringList(), const QString& pMimeType = QString("")); + void insertThumbnailToScene(QGraphicsPixmapItem* newThumbnail, UBThumbnailTextItem* thumbnailTextItem); void refreshScene(); void sceneSelectionChanged(); @@ -184,7 +185,6 @@ class UBThumbnail item->sceneBoundingRect().height() + 10); mSelectionItem->show(); - } else { @@ -199,6 +199,7 @@ class UBThumbnail void setRow(int row) { mRow = row; } UBThumbnailTextItem *label(){return mLabel;} void setLabel(UBThumbnailTextItem *label){mLabel = label;} + QGraphicsRectItem* selectionItem(){ return mSelectionItem; } protected: QGraphicsRectItem *mSelectionItem; @@ -255,6 +256,18 @@ class UBThumbnailTextItem : public QGraphicsTextItem } } + void setPageNumber(int i) + { + mUnelidedText = tr("Page %0").arg(i); + computeText(); + } + + void setText(const QString& text) + { + mUnelidedText = text; + computeText(); + } + void computeText() { QFontMetricsF fm(font()); @@ -339,6 +352,11 @@ class UBSceneThumbnailPixmap : public UBThumbnailPixmap return mSceneIndex; } + void setSceneIndex(int i) + { + mSceneIndex = i; + } + void highlight() { //NOOP From ac43610fb6c6e386b6ab281b867ea6934f1bd260 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Thu, 18 Nov 2021 10:29:27 +0100 Subject: [PATCH 054/130] fixed other issues regarding thumbnails. A lot of issues were hidden by the fact that we were regenerating everything, almost everytime an action would imply a change in thumbnails --- src/board/UBBoardController.cpp | 4 +--- src/document/UBDocumentContainer.cpp | 5 +++++ src/document/UBDocumentContainer.h | 1 + src/document/UBDocumentController.cpp | 7 +++++-- src/gui/UBDocumentNavigator.cpp | 18 +++++++++--------- src/gui/UBDocumentThumbnailWidget.cpp | 3 +++ src/gui/UBThumbnailWidget.cpp | 2 ++ 7 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/board/UBBoardController.cpp b/src/board/UBBoardController.cpp index 6ab9bd9a..2401d30a 100644 --- a/src/board/UBBoardController.cpp +++ b/src/board/UBBoardController.cpp @@ -584,9 +584,7 @@ void UBBoardController::duplicateScene(int nIndex) QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); persistCurrentScene(false,true); - QList scIndexes; - scIndexes << nIndex; - duplicatePages(scIndexes); + duplicatePage(nIndex); insertThumbPage(nIndex); if (UBApplication::documentController->selectedDocument() == selectedDocument()) { diff --git a/src/document/UBDocumentContainer.cpp b/src/document/UBDocumentContainer.cpp index c1164ce3..86e857a7 100644 --- a/src/document/UBDocumentContainer.cpp +++ b/src/document/UBDocumentContainer.cpp @@ -65,6 +65,11 @@ void UBDocumentContainer::duplicatePages(QList& pageIndexes) } } +void UBDocumentContainer::duplicatePage(int index) +{ + UBPersistenceManager::persistenceManager()->duplicateDocumentScene(mCurrentDocument, index); +} + void UBDocumentContainer::moveThumbPage(int source, int target) { mDocumentThumbs.move(source, target); diff --git a/src/document/UBDocumentContainer.h b/src/document/UBDocumentContainer.h index aa220895..e02556b5 100644 --- a/src/document/UBDocumentContainer.h +++ b/src/document/UBDocumentContainer.h @@ -61,6 +61,7 @@ class UBDocumentContainer : public QObject static int sceneIndexFromPage(int sceneIndex); void duplicatePages(QList& pageIndexes); + void duplicatePage(int index); void deletePages(QList& pageIndexes); diff --git a/src/document/UBDocumentController.cpp b/src/document/UBDocumentController.cpp index 4038656c..1d2b08d3 100644 --- a/src/document/UBDocumentController.cpp +++ b/src/document/UBDocumentController.cpp @@ -3618,7 +3618,11 @@ void UBDocumentController::deletePages(QList itemsToDelete) } } UBDocumentContainer::deletePages(sceneIndexes); - emit mBoardController->documentThumbnailsUpdated(this); + if (mBoardController->selectedDocument() == selectedDocument()) + { + for (auto index : sceneIndexes) + mBoardController->deleteThumbPage(index); + } proxy->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(QDateTime::currentDateTime())); UBMetadataDcSubsetAdaptor::persist(proxy); @@ -3635,7 +3639,6 @@ void UBDocumentController::deletePages(QList itemsToDelete) mDocumentUI->thumbnailWidget->selectItemAt(minIndex); mBoardController->setActiveDocumentScene(minIndex); - mBoardController->reloadThumbnails(); } } diff --git a/src/gui/UBDocumentNavigator.cpp b/src/gui/UBDocumentNavigator.cpp index 60ecc1f4..345afcdd 100644 --- a/src/gui/UBDocumentNavigator.cpp +++ b/src/gui/UBDocumentNavigator.cpp @@ -240,8 +240,6 @@ void UBDocumentNavigator::updateThumbnail(int index) oldItem = NULL; } - ensureVisible(0, 0, 10, 10); - refreshScene(); } @@ -259,6 +257,7 @@ void UBDocumentNavigator::removeThumbnail(int index) //update thumbs page number accordingly for (int i=0; i < mThumbsWithLabels.length(); i++) { + mThumbsWithLabels.at(i).getThumbnail()->setSceneIndex(i); mThumbsWithLabels.at(i).getCaption()->setText(tr("Page %0").arg(i+1)); } @@ -372,11 +371,11 @@ void UBDocumentNavigator::resizeEvent(QResizeEvent *event) // Update the thumbnails width mThumbnailWidth = (width() > mThumbnailMinWidth) ? width() - 2*border() : mThumbnailMinWidth; - if(mSelectedThumbnail) - ensureVisible(mSelectedThumbnail); - // Refresh the scene refreshScene(); + + if(mSelectedThumbnail) + ensureVisible(mSelectedThumbnail); } /** @@ -617,8 +616,6 @@ void UBDocumentNavigator::dragMoveEvent(QDragMoveEvent *event) int thumbnailHeight = mThumbnailWidth / UBSettings::minScreenRatio; QRectF thumbnailArea(0, scenePos.y() - thumbnailHeight/2, mThumbnailWidth, thumbnailHeight); - ensureVisible(thumbnailArea); - UBSceneThumbnailNavigPixmap* item = dynamic_cast(itemAt(position.toPoint())); if (item) { @@ -642,7 +639,7 @@ void UBDocumentNavigator::dragMoveEvent(QDragMoveEvent *event) { y = item->pos().y() - UBSettings::thumbnailSpacing / 2; if (mDropBar->y() != y) - mDropBar->setRect(QRectF(item->pos().x(), y, mThumbnailWidth-verticalScrollBar()->width(), 3)); + mDropBar->setRect(QRectF(item->pos().x(), y, (item->boundingRect().width()-verticalScrollBar()->width())*scale, 3)); } } else @@ -651,11 +648,14 @@ void UBDocumentNavigator::dragMoveEvent(QDragMoveEvent *event) { y = item->pos().y() + item->boundingRect().height() * scale + UBSettings::thumbnailSpacing / 2; if (mDropBar->y() != y) - mDropBar->setRect(QRectF(item->pos().x(), y, mThumbnailWidth-verticalScrollBar()->width(), 3)); + mDropBar->setRect(QRectF(item->pos().x(), y, (item->boundingRect().width()-verticalScrollBar()->width())*scale, 3)); } } } } + + ensureVisible(thumbnailArea); + event->acceptProposedAction(); } diff --git a/src/gui/UBDocumentThumbnailWidget.cpp b/src/gui/UBDocumentThumbnailWidget.cpp index 45dbc52d..a948a9b1 100644 --- a/src/gui/UBDocumentThumbnailWidget.cpp +++ b/src/gui/UBDocumentThumbnailWidget.cpp @@ -318,6 +318,9 @@ void UBDocumentThumbnailWidget::removeThumbnail(int sceneIndex) UBSceneThumbnailPixmap *thumbnail = dynamic_cast(thumbnailItem); if (thumbnail) { + if (thumbnail->isSelected()) + scene()->removeItem(thumbnail->selectionItem()); + mGraphicItems.removeAt(sceneIndex); if (thumbnailItem) scene()->removeItem(thumbnailItem); diff --git a/src/gui/UBThumbnailWidget.cpp b/src/gui/UBThumbnailWidget.cpp index ba3694d2..9a866847 100644 --- a/src/gui/UBThumbnailWidget.cpp +++ b/src/gui/UBThumbnailWidget.cpp @@ -202,6 +202,8 @@ void UBThumbnailWidget::refreshScene() if (mLabelsItems.size() > i) { + mLabelsItems.at(i)->setWidth(mThumbnailWidth); + mLabelsItems.at(i)->setPageNumber(i+1); QFontMetrics fm(mLabelsItems.at(i)->font(), this); QString elidedText = fm.elidedText(mLabelsItems.at(i)->toPlainText(), Qt::ElideRight, mThumbnailWidth); From af52add37198b44235d3615982fe109e8d8c087b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Thu, 18 Nov 2021 11:39:09 +0100 Subject: [PATCH 055/130] remove unused slot --- src/document/UBDocumentController.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/document/UBDocumentController.cpp b/src/document/UBDocumentController.cpp index 1d2b08d3..02c0bf75 100644 --- a/src/document/UBDocumentController.cpp +++ b/src/document/UBDocumentController.cpp @@ -1805,7 +1805,7 @@ UBDocumentController::UBDocumentController(UBMainWindow* mainWindow) setupToolbar(); connect(this, SIGNAL(exportDone()), mMainWindow, SLOT(onExportDone())); connect(this, SIGNAL(documentThumbnailsUpdated(UBDocumentContainer*)), this, SLOT(refreshDocumentThumbnailsView(UBDocumentContainer*))); - connect(this, SIGNAL(documentPageInserted(int)), this, SLOT(insertThumbnail(int))); + //connect(this, SIGNAL(documentPageInserted(int)), this, SLOT(insertThumbnail(int))); connect(this, SIGNAL(documentPageUpdated(int)), this, SLOT(updateThumbnail(int))); connect(this, SIGNAL(documentPageRemoved(int)), this, SLOT(removeThumbnail(int))); connect(this, SIGNAL(documentPageMoved(int, int)), this, SLOT(moveThumbnail(int, int))); From 1cdcc2c8bed11e224d65e173dc7b977ecc5c5b8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Thu, 18 Nov 2021 11:39:34 +0100 Subject: [PATCH 056/130] changed version to 1.6.2a-1118 --- OpenBoard.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenBoard.pro b/OpenBoard.pro index 003c6fe5..85233990 100644 --- a/OpenBoard.pro +++ b/OpenBoard.pro @@ -11,7 +11,7 @@ VERSION_MAJ = 1 VERSION_MIN = 6 VERSION_PATCH = 2 VERSION_TYPE = a # a = alpha, b = beta, rc = release candidate, r = release, other => error -VERSION_BUILD = 1115 +VERSION_BUILD = 1118 VERSION = "$${VERSION_MAJ}.$${VERSION_MIN}.$${VERSION_PATCH}-$${VERSION_TYPE}.$${VERSION_BUILD}" From 7e83f910ca5caa193f49363c7cff4c175eefee92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Thu, 18 Nov 2021 14:00:39 +0100 Subject: [PATCH 057/130] restore call to QGraphicsView::mouseMoveEvent if drawing mode and not pressed --- src/board/UBBoardView.cpp | 32 +++----------------------------- 1 file changed, 3 insertions(+), 29 deletions(-) diff --git a/src/board/UBBoardView.cpp b/src/board/UBBoardView.cpp index 99402ca9..f60cbd19 100644 --- a/src/board/UBBoardView.cpp +++ b/src/board/UBBoardView.cpp @@ -1141,6 +1141,9 @@ void UBBoardView::mouseMoveEvent (QMouseEvent *event) return; } + if ((UBDrawingController::drawingController()->isDrawingTool()) && !mMouseButtonIsPressed) + QGraphicsView::mouseMoveEvent(event); + int currentTool = static_cast(UBDrawingController::drawingController()->stylusTool()); switch (currentTool) { @@ -1243,35 +1246,6 @@ void UBBoardView::mouseMoveEvent (QMouseEvent *event) if (!mTabletStylusIsPressed && scene()) { scene()->inputDeviceMove(mapToScene(UBGeometryUtils::pointConstrainedInRect(event->pos(), rect())) , mMouseButtonIsPressed); } - if (UBDrawingController::drawingController()->isDrawingTool()) - { - QGraphicsItem* item = scene()->itemAt(mapToScene(event->pos()), QTransform()); - if (item) - { - //if showMarkerPreviewCircle, showPenPreviewCircle or showEraserPreviewCircle is true, then the topmost visible item under the mouse is the preview circle - QGraphicsEllipseItem* circle = dynamic_cast(scene()->itemAt(mapToScene(event->pos()), QTransform())); - if (circle) - { - circle->setVisible(false); - item = scene()->itemAt(mapToScene(event->pos()), QTransform()); - if (item) - { - if (item->type() == UBGraphicsRuler::Type || item->type() == UBGraphicsTriangle::Type) - { - QGraphicsView::mouseMoveEvent(event); - } - } - circle->setVisible(true); - } - else - { - if (item->type() == UBGraphicsRuler::Type || item->type() == UBGraphicsTriangle::Type) - { - QGraphicsView::mouseMoveEvent(event); - } - } - } - } event->accept (); } From 136297d695db35767ef8b6650c38a0e6e37acc30 Mon Sep 17 00:00:00 2001 From: letsfindaway Date: Sat, 4 Dec 2021 18:08:56 +0100 Subject: [PATCH 058/130] fix: cursow shown on a ruler using pen tool - when using the pen tool on a ruler the "normal" cursor was shown - instead the special drawRulerLine cursor should be shown - UBGraphicsScene::drawPenCircle update cursor only with no active ruler --- src/domain/UBGraphicsScene.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/domain/UBGraphicsScene.cpp b/src/domain/UBGraphicsScene.cpp index 0336a63c..20103686 100644 --- a/src/domain/UBGraphicsScene.cpp +++ b/src/domain/UBGraphicsScene.cpp @@ -840,6 +840,8 @@ void UBGraphicsScene::drawMarkerCircle(const QPointF &pPoint) void UBGraphicsScene::drawPenCircle(const QPointF &pPoint) { + QCursor cursor; + if (mPenCircle && UBSettings::settings()->showPenPreviewCircle->get().toBool() && UBSettings::settings()->currentPenWidth() >= UBSettings::settings()->penPreviewFromSize->get().toInt()) { qreal penDiameter = UBSettings::settings()->currentPenWidth(); @@ -849,20 +851,20 @@ void UBGraphicsScene::drawPenCircle(const QPointF &pPoint) mPenCircle->setRect(QRectF(pPoint.x() - penRadius, pPoint.y() - penRadius, penDiameter, penDiameter)); - - if (controlView()) - if (controlView()->viewport()) - controlView()->viewport()->setCursor(QCursor (Qt::BlankCursor)); - mPenCircle->show(); + cursor = Qt::BlankCursor; } else { - if (controlView()) - if (controlView()->viewport()) - controlView()->viewport()->setCursor(UBResources::resources()->penCursor); + cursor = UBResources::resources()->penCursor; } + if (!UBDrawingController::drawingController()->mActiveRuler) + { + // set cursor only if no active ruler + if (controlView() && controlView()->viewport()) + controlView()->viewport()->setCursor(cursor); + } } void UBGraphicsScene::hideMarkerCircle() From e33fc007c78b29075d0fae5973e68ac1ddb47f65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Fri, 17 Dec 2021 10:24:12 +0100 Subject: [PATCH 059/130] prevent a crash when clearBackground then undo then redo then clearBackground --- src/domain/UBGraphicsItemUndoCommand.cpp | 6 +++++- src/domain/UBGraphicsScene.cpp | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/domain/UBGraphicsItemUndoCommand.cpp b/src/domain/UBGraphicsItemUndoCommand.cpp index 4787aed5..8ad68b22 100644 --- a/src/domain/UBGraphicsItemUndoCommand.cpp +++ b/src/domain/UBGraphicsItemUndoCommand.cpp @@ -246,7 +246,11 @@ void UBGraphicsItemUndoCommand::redo() polygonItem->strokesGroup()->removeFromGroup(polygonItem); } - mScene->removeItem(item); + + if (itemLayerType::BackgroundItem == item->data(UBGraphicsItemData::itemLayerType)) + mScene->setAsBackgroundObject(nullptr); + else + mScene->removeItem(item); if (bApplyTransform) item->setTransform(t); diff --git a/src/domain/UBGraphicsScene.cpp b/src/domain/UBGraphicsScene.cpp index 0336a63c..533b97c1 100644 --- a/src/domain/UBGraphicsScene.cpp +++ b/src/domain/UBGraphicsScene.cpp @@ -1476,6 +1476,7 @@ void UBGraphicsScene::clearContent(clearCase pCase) if(mBackgroundObject){ removeItem(mBackgroundObject); removedItems << mBackgroundObject; + mBackgroundObject = nullptr; } break; From 9f2eb892df63a7064d13b5581a393b867b42130e Mon Sep 17 00:00:00 2001 From: Marcin Serwin Date: Wed, 22 Dec 2021 16:55:06 +0100 Subject: [PATCH 060/130] Update polish translation --- resources/i18n/OpenBoard_pl.ts | 227 ++++++++++++++++----------------- 1 file changed, 112 insertions(+), 115 deletions(-) diff --git a/resources/i18n/OpenBoard_pl.ts b/resources/i18n/OpenBoard_pl.ts index 5802b1bb..3d7014d5 100644 --- a/resources/i18n/OpenBoard_pl.ts +++ b/resources/i18n/OpenBoard_pl.ts @@ -481,7 +481,7 @@ Grid Light Background - Jasne rastrowe tło + Jasne tło w kratkę Plain Dark Background @@ -493,7 +493,7 @@ Grid Dark Background - Ciemne rastrowe tło + Ciemne tło w kratkę Podcast @@ -561,7 +561,7 @@ Highlight - Wyróżnienie + Wyróżnienie Ctrl+M @@ -705,8 +705,7 @@ Web Trap - - + Pułapka sieciowa Trap Web Content @@ -797,51 +796,51 @@ Ruled Light Background - + Jasne tło w linie Ruled Dark Background - + Ciemne tło w linie Open Tutorial - + Otwórz samouczek Open the tutorial web page - + Otwórz stronę samouczka Reset grid size - + Zresetuj rozmiar kratki Small Eraser - + Mała gumka Color 1 - + Kolor 1 Color 2 - + Kolor 2 Color 3 - + Kolor 3 Color 4 - + Kolor 4 Color 5 - + Kolor 5 Draw intermediate grid lines - + Rysuj drobne linie kratki @@ -886,7 +885,7 @@ QObject Element ID = - ID elementu = + ID elementu = Content is not supported in destination format. @@ -939,11 +938,11 @@ UBBackgroundPalette Grid size - + Rozmiar kratki Draw intermediate grid lines - + Rysuj drobne linie kratki @@ -994,19 +993,19 @@ Saving document... - + Zapisywanie dokumentu... Document has just been saved... - + Dokument został właśnie zapisany... Deleting page %1 - + Usuwanie strony %1 Color - Kolor + Kolor @@ -1024,7 +1023,7 @@ UBBoardThumbnailsView Loading page (%1/%2) - + Ładowanie strony (%1/%2) @@ -1074,7 +1073,7 @@ Show OpenBoard - + Pokaż OpenBoard @@ -1157,7 +1156,7 @@ Failed to import file ... - Importowanie pliku zakończone niepowodzeniem... + Importowanie pliku zakończone niepowodzeniem... Import all Images from Folder @@ -1210,27 +1209,27 @@ duplicated %1 page duplicated %1 pages - - - - + + zduplikowano %1 stronę + zduplikowano %1 strony + Zduplikowano %1 stron Remove Item - + Usuń element Are you sure you want to remove the selected item(s) ? - + Czy na pewno chcesz usunąć zaznaczone elementy? The document '%1' has been generated with a newer version of OpenBoard (%2). By opening it, you may lose some information. Do you want to proceed? - + Dokument '%1' został utworzony przy użyciu nowszej wersji OpenBoard (%2). Otwierając go możesz stracić niektóre informację. Czy chcesz kontynuować? Title page - + Strona tytułowa @@ -1294,32 +1293,34 @@ UBDocumentReplaceDialog Accept - Akceptuj + Akceptuj Cancel - Anuluj + Anuluj Replace - + Zastąp The name %1 is allready used. Keeping this name will replace the document. Providing a new name will create a new document. - + Nazwa %1 jest już używana. +Pozostawienie tej nazwy zastąpi ten dokument. +Zmiana nazwy na nową utworzy nowy dokument. UBDocumentTreeModel Trash - Kosz + Kosz %1 pages copied - + Skopiowano %1 stronę Skopiowano %1 strony Skopiowano %1 stron @@ -1327,14 +1328,14 @@ Providing a new name will create a new document. My documents - + Moje dokumenty UBDocumentTreeView %1 pages copied - + Skopiowano %1 stronę Skopiowano %1 strony Skopiowano %1 stron @@ -1342,15 +1343,15 @@ Providing a new name will create a new document. Remove Item - + Usuń elementy Are you sure you want to remove the selected item(s) ? - + Czy na pewno chcesz usunąć zaznaczone elementy? Copying page %1/%2 - Kopiowanie strony %1/%2 + Kopiowanie strony %1/%2 @@ -1388,14 +1389,14 @@ Providing a new name will create a new document. UBDraggableThumbnail Page %0 - Strona %0 + Strona %0 UBDraggableThumbnailView Page %0 - Strona %0 + Strona %0 @@ -1406,23 +1407,23 @@ Providing a new name will create a new document. Exporting document... - Eksportowanie dokumentu... + Eksportowanie dokumentu... Export successful. - Eksportowanie zakończone powodzeniem. + Eksportowanie zakończone powodzeniem. Export failed - + Eksportowanie zakończone niepowodzeniem Unable to export to the selected location. You do not have the permissions necessary to save the file. - + Nie udało się wyeksportować do wybranej lokacji. Nie masz wystarczających uprawnień do zapisania pliku. Export failed: location not writable - + Eksportowanie nie powiodło się: niemożliwy zapis do lokacji @@ -1472,34 +1473,34 @@ Providing a new name will create a new document. Export to OpenBoard Format - + Wyeksportuj do formatu OpenBoard UBExportDocumentSetAdaptor Exporting document... - Eksportowanie dokumentu... + Eksportowanie dokumentu... Export successful. - Eksportowanie zakończone powodzeniem. + Eksportowanie zakończone powodzeniem. Export failed. - Eksportowanie zakończone niepowodzeniem. + Eksportowanie zakończone niepowodzeniem. Failed to export... - + Nie udało się wyeksportować... Export as UBX File - + Wyeksportuj jako plik UBX Export to OpenBoard UBX Format - + Wyeksportuj jako plik formatu OpenBoard UBX @@ -1691,7 +1692,7 @@ Providing a new name will create a new document. UBFeaturesProgressInfo Loading - Wczytywanie + Wczytywanie @@ -1721,26 +1722,26 @@ Providing a new name will create a new document. Set as background - Ustaw jako tło + Ustaw jako tło UBGraphicsMediaItem Media resource couldn't be resolved - + Nie udało się znaleźć zasobu multimedialnego Unsupported media format - + Niewspierany format zasobu multimedialnego Media playback service not found - + Nie znaleziono usługi odtwarzania Media error: - + Błąd multimediów: @@ -1783,7 +1784,7 @@ Providing a new name will create a new document. Transform as Tool - Przekształć jako narzędzie + Przekształć jako narzędzie @@ -1825,14 +1826,14 @@ Providing a new name will create a new document. OpenBoard (*.ubz) - + OpenBoard (*.ubz) UBImportDocumentSetAdaptor Openboard (set of documents) (*.ubx) - + Openboard (zbiór dokumentów) (*.ubx) @@ -1943,27 +1944,27 @@ Czy chcesz ignorować te błędy dla tego hosta? UBOpenSankoreImporterWidget Cancel - Anuluj + Anuluj Open-Sankore Documents Detected - + Wykryto dokumenty Open-Sankore Open-Sankoré documents are present on your computer. It is possible to import them to OpenBoard by pressing the “Proceed” button to launch the importer application. - + Dokumenty Open-Sankoré są dostępne na twoimkomputerze. Możliwy jest ich import do OpenBoard naciskając przycisk "Kontynuuj" by uruchomić aplikację importującą. Show this panel next time - + Pokaż ten panel następnym razem You can always access the OpenBoard Document Importer through the Preferences panel in the About tab. Warning, if you have already imported your Open-Sankore datas, you might loose your current OpenBoard documents. - + Zawsze możesz zyskać dostęp do aplikacji importującej OpenBoard poprzez panel Ustawień w karcie O programie. Uwaga: jeśli zaimportowałaś już dane Open-Sankore, możesz stracić swoje aktualne dokumenty OpenBoard. Proceed - + Kontynuuj @@ -1978,7 +1979,7 @@ Czy chcesz ignorować te błędy dla tego hosta? OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. - + OpenBoard stracił dostęp do repozytorium dokumentów '%1'. Niestety aplikacja musi zostać zamknięta by uniknąć korupcji danych. Ostatnie zmiany również mogą zostać utracone. @@ -2064,7 +2065,7 @@ Czy chcesz ignorować te błędy dla tego hosta? OpenBoard Cast - + OpenBoard Cast @@ -2123,7 +2124,7 @@ Czy chcesz ignorować te błędy dla tego hosta? UBStartupHintsPalette Visible next time - + Pokaż następnym razem @@ -2148,7 +2149,7 @@ Czy chcesz ignorować te błędy dla tego hosta? UBThumbnailTextItem Page %0 - Strona %0 + Strona %0 @@ -2183,7 +2184,7 @@ Czy chcesz ignorować te błędy dla tego hosta? Axes - + Osie @@ -2461,7 +2462,7 @@ Aby uzyskać dostęp do zaktualizowanych dokumentów, należy ponownie uruchomi WBTabBar New &Tab - Nowa zakładka + &Nowa zakładka Clone Tab @@ -2469,11 +2470,11 @@ Aby uzyskać dostęp do zaktualizowanych dokumentów, należy ponownie uruchomi &Close Tab - Zamknij zakładkę + &Zamknij kartę Close &Other Tabs - Zamknij inne zakładki + Zamknij &Inne karty Reload Tab @@ -2534,7 +2535,7 @@ Aby uzyskać dostęp do zaktualizowanych dokumentów, należy ponownie uruchomi Download PDF Document: would you prefer to download the PDF file or add it to the current OpenBoard document? - + Pobierz dokument PDF: chcesz pobrać plik PDF, czy dodać go do aktualnego dokumentu OpenBoard? @@ -2548,7 +2549,7 @@ Aby uzyskać dostęp do zaktualizowanych dokumentów, należy ponownie uruchomi XPDFRenderer Processing... - + Przetwarzanie... @@ -2591,19 +2592,15 @@ p, li { white-space: pre-wrap; } <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<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:'Lucida Grande'; font-size:10pt;">Klikając „Wyślij”, poświadczasz, że posiadasz wszelkie prawa do zawartości lub masz zezwolenie właściciela zawartości na jej publiczne udostępnianie w serwisie YouTube, a także, że zawartość jest zgodna z Warunkami korzystania z serwisu YouTube dostępnymi pod adresem </span><a href="http://www.youtube.com/t/terms"><span style=" font-family:'Lucida Grande'; font-size:10pt; text-decoration: underline; color:#0000ff;">http://www.youtube.com/t/terms</span></a></p></body></html> - +<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:'Lucida Grande'; font-size:10pt;">Klikając „Wyślij”, poświadczasz, że posiadasz wszelkie prawa do zawartości lub masz zezwolenie właściciela zawartości na jej publiczne udostępnianie w serwisie YouTube, a także, że zawartość jest zgodna z Warunkami korzystania z serwisu YouTube dostępnymi pod adresem </span><a href="http://www.youtube.com/t/terms"><span style=" font-family:'Lucida Grande'; font-size:10pt; text-decoration: underline; color:#0000ff;">http://www.youtube.com/t/terms</span></a></p></body></html> OpenBoard - - OpenBoard - - + OpenBoard Restore credentials on reboot - + Przywróć dane logowania po restarcie @@ -2642,11 +2639,11 @@ p, li { white-space: pre-wrap; } Show preview circle from - + Pokaż podgląd pędzla od px - + px @@ -2699,23 +2696,23 @@ p, li { white-space: pre-wrap; } documents OpenBoard Documents - + Dokumenty OpenBoard Creation date - + Data utworzenia Update date - + Data modyfikacji Alphabetical order - + Porządek alfabetyczny Sort Order - + Porządek sortowania @@ -2738,7 +2735,7 @@ p, li { white-space: pre-wrap; } Display - Wyświetlaj + Wyświetlanie Internet @@ -2858,63 +2855,63 @@ p, li { white-space: pre-wrap; } On Dark Background - Na ciemnym tle + Na ciemnym tle Opacity - Nieprzezroczystość + Nieprzezroczystość On Light Background - Na jasnym tle + Na jasnym tle Swap first and second view displays - + Zamień wyświetlacze Built-in virtual keyboard button size: - + Rozmiar klawiszy wyświetlanej klawiatury: Use system keyboard (recommended) - + Użyj klawiatury systemowej (zalecane) Grid - + Kratka Open-Sankoré Importer - + Open-Sankoré Importer Check if Open-Sankoré data could be imported at launch - + Sprawdź czy dane Open-Sankoré mogą być zaimportowane przy starcie Documents Mode - + Tryb dokumentów Display date column on alphabetical sort - + Wyświetl kolumnę dat w porządku alfabetycznym Empty trash for documents older than - + Usuń dokumenty z kosza, które są starsze niż days - + dni PDF Rendering - + Renderowanie PDF Improve zoom execution time (can slightly affect rendering quality) - + Usprawnij wykonywanie przybliżania (może nieco pogorszyć jakość renderowania) From cea99200036ea3c3cfe06df83d823e607735c79b Mon Sep 17 00:00:00 2001 From: letsfindaway Date: Sun, 16 Jan 2022 17:35:10 +0100 Subject: [PATCH 061/130] fix: coordinates when display screen is left of control screen - fix usage of global coordinates where local coordinates are required - show widget before changing geometry (necessary in some environments) --- src/core/UBApplicationController.cpp | 1 + src/core/UBDisplayManager.cpp | 2 +- src/desktop/UBCustomCaptureWindow.cpp | 2 ++ src/desktop/UBDesktopAnnotationController.cpp | 9 +++++---- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/core/UBApplicationController.cpp b/src/core/UBApplicationController.cpp index 27f558c5..27c5d75b 100644 --- a/src/core/UBApplicationController.cpp +++ b/src/core/UBApplicationController.cpp @@ -465,6 +465,7 @@ void UBApplicationController::showDesktop(bool dontSwitchFrontProcess) if (mMirror) { QRect rect = qApp->desktop()->screenGeometry(desktopWidgetIndex); + rect.moveTo(0, 0); mMirror->setSourceRect(rect); } diff --git a/src/core/UBDisplayManager.cpp b/src/core/UBDisplayManager.cpp index f24e9211..4256379d 100644 --- a/src/core/UBDisplayManager.cpp +++ b/src/core/UBDisplayManager.cpp @@ -236,7 +236,7 @@ void UBDisplayManager::positionScreens() if (mDisplayWidget && mDisplayScreenIndex > -1) { - mDisplayWidget->hide(); + mDisplayWidget->showNormal(); mDisplayWidget->setGeometry(mDesktop->screenGeometry(mDisplayScreenIndex)); UBPlatformUtils::showFullScreen(mDisplayWidget); } diff --git a/src/desktop/UBCustomCaptureWindow.cpp b/src/desktop/UBCustomCaptureWindow.cpp index dc26abad..1a319738 100644 --- a/src/desktop/UBCustomCaptureWindow.cpp +++ b/src/desktop/UBCustomCaptureWindow.cpp @@ -79,6 +79,8 @@ int UBCustomCaptureWindow::execute(const QPixmap &pScreenPixmap) QDesktopWidget *desktop = QApplication::desktop(); int currentScreen = desktop->screenNumber(QCursor::pos()); + // necessary so that changing geometry really affects the widget + showNormal(); setGeometry(desktop->screenGeometry(currentScreen)); this->show(); setWindowOpacity(1.0); diff --git a/src/desktop/UBDesktopAnnotationController.cpp b/src/desktop/UBDesktopAnnotationController.cpp index 138d0fb4..f63560e6 100644 --- a/src/desktop/UBDesktopAnnotationController.cpp +++ b/src/desktop/UBDesktopAnnotationController.cpp @@ -516,6 +516,7 @@ QPixmap UBDesktopAnnotationController::getScreenPixmap() QScreen * screen = UBApplication::controlScreen(); QRect rect = desktop->screenGeometry(QCursor::pos()); + rect.moveTo(0, 0); return screen->grabWindow(desktop->effectiveWinId(), rect.x(), rect.y(), rect.width(), rect.height()); @@ -556,7 +557,7 @@ void UBDesktopAnnotationController::penActionPressed() // Check if the mouse cursor is on the little arrow QPoint cursorPos = QCursor::pos(); - QPoint palettePos = mDesktopPalette->pos(); + QPoint palettePos = mDesktopPalette->mapToGlobal(QPoint(0, 0)); // global coordinates of palette QPoint buttonPos = mDesktopPalette->buttonPos(UBApplication::mainWindow->actionPen); int iX = cursorPos.x() - (palettePos.x() + buttonPos.x()); // x position of the cursor in the palette @@ -610,7 +611,7 @@ void UBDesktopAnnotationController::eraserActionPressed() // Check if the mouse cursor is on the little arrow QPoint cursorPos = QCursor::pos(); - QPoint palettePos = mDesktopPalette->pos(); + QPoint palettePos = mDesktopPalette->mapToGlobal(QPoint(0, 0)); QPoint buttonPos = mDesktopPalette->buttonPos(UBApplication::mainWindow->actionEraser); int iX = cursorPos.x() - (palettePos.x() + buttonPos.x()); // x position of the cursor in the palette @@ -665,7 +666,7 @@ void UBDesktopAnnotationController::markerActionPressed() // Check if the mouse cursor is on the little arrow QPoint cursorPos = QCursor::pos(); - QPoint palettePos = mDesktopPalette->pos(); + QPoint palettePos = mDesktopPalette->mapToGlobal(QPoint(0, 0)); QPoint buttonPos = mDesktopPalette->buttonPos(UBApplication::mainWindow->actionMarker); int iX = cursorPos.x() - (palettePos.x() + buttonPos.x()); // x position of the cursor in the palette @@ -967,7 +968,7 @@ void UBDesktopAnnotationController::updateMask(bool bTransparent) p.setPen(Qt::red); p.setBrush(QBrush(Qt::red)); - p.drawRect(mTransparentDrawingView->geometry().x(), mTransparentDrawingView->geometry().y(), mTransparentDrawingView->width(), mTransparentDrawingView->height()); + p.drawRect(0, 0, mTransparentDrawingView->width(), mTransparentDrawingView->height()); p.end(); mTransparentDrawingView->setMask(mMask.mask()); From 141c48e20d107c64f99a711b29451bbf57f243f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Thu, 20 Jan 2022 09:34:33 +0100 Subject: [PATCH 062/130] fixed an issue where reload thumbnails would be called twice with the second call not using the good source container --- src/document/UBDocumentController.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/document/UBDocumentController.cpp b/src/document/UBDocumentController.cpp index 02c0bf75..de19b90f 100644 --- a/src/document/UBDocumentController.cpp +++ b/src/document/UBDocumentController.cpp @@ -3217,7 +3217,6 @@ void UBDocumentController::addToDocument() UBMetadataDcSubsetAdaptor::persist(mBoardController->selectedDocument()); mBoardController->reloadThumbnails(); - emit mBoardController->documentThumbnailsUpdated(this); UBApplication::applicationController->showBoard(); mBoardController->setActiveDocumentScene(newActiveSceneIndex); From 76cfee3dc5bad8d83943b1f6860a23b46ae5d23d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Fri, 21 Jan 2022 10:17:38 +0100 Subject: [PATCH 063/130] don't reload thumbnails everytime documentscenechanged is called + reload after duplicate in Board Mode --- src/board/UBBoardController.cpp | 2 +- src/document/UBDocumentController.cpp | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/board/UBBoardController.cpp b/src/board/UBBoardController.cpp index 2401d30a..3d1e4fa3 100644 --- a/src/board/UBBoardController.cpp +++ b/src/board/UBBoardController.cpp @@ -589,8 +589,8 @@ void UBBoardController::duplicateScene(int nIndex) if (UBApplication::documentController->selectedDocument() == selectedDocument()) { UBApplication::documentController->insertThumbPage(nIndex); + UBApplication::documentController->reloadThumbnails(); } - //emit documentThumbnailsUpdated(this); emit addThumbnailRequired(this, nIndex + 1); selectedDocument()->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(QDateTime::currentDateTime())); diff --git a/src/document/UBDocumentController.cpp b/src/document/UBDocumentController.cpp index de19b90f..a551fed2 100644 --- a/src/document/UBDocumentController.cpp +++ b/src/document/UBDocumentController.cpp @@ -2382,7 +2382,10 @@ void UBDocumentController::duplicateSelectedItem() if (selectedSceneIndexes.count() > 0) { duplicatePages(selectedSceneIndexes); - emit documentThumbnailsUpdated(this); + if (selectedDocument() == selectedDocumentProxy()) + { + reloadThumbnails(); + } selectedDocument()->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(QDateTime::currentDateTime())); UBMetadataDcSubsetAdaptor::persist(selectedDocument()); int selectedThumbnail = selectedSceneIndexes.last() + selectedSceneIndexes.size(); @@ -3132,11 +3135,6 @@ void UBDocumentController::documentSceneChanged(UBDocumentProxy* proxy, int pSce { Q_UNUSED(pSceneIndex); - if (proxy == selectedDocumentProxy()) - { - reloadThumbnails(); - } - QModelIndexList sel = mDocumentUI->documentTreeView->selectionModel()->selectedRows(0); QModelIndex selection; From 827f5a514c470e765f493d668953491c862f0470 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Wed, 2 Feb 2022 09:44:15 +0100 Subject: [PATCH 064/130] reload board thumnails after adding a file to the document if the document is also selected in board --- src/document/UBDocumentController.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/document/UBDocumentController.cpp b/src/document/UBDocumentController.cpp index a551fed2..4b168e94 100644 --- a/src/document/UBDocumentController.cpp +++ b/src/document/UBDocumentController.cpp @@ -3013,6 +3013,10 @@ void UBDocumentController::addFileToDocument() { addFileToDocument(document); reloadThumbnails(); + if (UBApplication::boardController->selectedDocument() == selectedDocument()) + { + UBApplication::boardController->reloadThumbnails(); + } } } From 8216d2e9c67a4bf7e6f34b121d0091fe42e1d3a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Wed, 2 Feb 2022 13:16:28 +0100 Subject: [PATCH 065/130] insertthumbpage in board mode only if the same document is selected in both modes --- src/core/UBDocumentManager.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/UBDocumentManager.cpp b/src/core/UBDocumentManager.cpp index ac331d94..40ab420c 100644 --- a/src/core/UBDocumentManager.cpp +++ b/src/core/UBDocumentManager.cpp @@ -286,7 +286,8 @@ int UBDocumentManager::addFilesToDocument(UBDocumentProxy* document, QStringList UBGraphicsScene* scene = UBPersistenceManager::persistenceManager()->createDocumentSceneAt(document, pageIndex); importAdaptor->placeImportedItemToScene(scene, page); UBPersistenceManager::persistenceManager()->persistDocumentScene(document, scene, pageIndex); - UBApplication::boardController->insertThumbPage(pageIndex); + if (UBApplication::documentController->selectedDocument() == UBApplication::boardController->selectedDocument()) + UBApplication::boardController->insertThumbPage(pageIndex); } UBPersistenceManager::persistenceManager()->persistDocumentMetadata(document); From 22acd9888a66b0f41d6aa2b41556e31db461a27a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Thu, 10 Feb 2022 08:30:57 +0100 Subject: [PATCH 066/130] fixed a bug where by calling createDefaultFont at every UBGraphicsTextItem instanciation, some fonts styles could be overridden --- src/domain/UBGraphicsScene.cpp | 3 ++ src/domain/UBGraphicsTextItem.cpp | 58 +++++++++++++++++++++++ src/domain/UBGraphicsTextItem.h | 2 + src/domain/UBGraphicsTextItemDelegate.cpp | 55 --------------------- src/domain/UBGraphicsTextItemDelegate.h | 1 - 5 files changed, 63 insertions(+), 56 deletions(-) diff --git a/src/domain/UBGraphicsScene.cpp b/src/domain/UBGraphicsScene.cpp index 533b97c1..816ef1ee 100644 --- a/src/domain/UBGraphicsScene.cpp +++ b/src/domain/UBGraphicsScene.cpp @@ -1934,9 +1934,12 @@ UBGraphicsTextItem* UBGraphicsScene::addTextWithFont(const QString& pString, con UBGraphicsTextItem *UBGraphicsScene::addTextHtml(const QString &pString, const QPointF& pTopLeft) { UBGraphicsTextItem *textItem = new UBGraphicsTextItem(); + textItem->setPlainText(""); textItem->setHtml(UBTextTools::cleanHtml(pString)); + textItem->initFontProperties(); + addItem(textItem); textItem->show(); diff --git a/src/domain/UBGraphicsTextItem.cpp b/src/domain/UBGraphicsTextItem.cpp index 5a96dff6..7e850169 100644 --- a/src/domain/UBGraphicsTextItem.cpp +++ b/src/domain/UBGraphicsTextItem.cpp @@ -81,6 +81,64 @@ UBGraphicsTextItem::~UBGraphicsTextItem() { } +void UBGraphicsTextItem::initFontProperties() +{ + QTextCursor curCursor = textCursor(); + QTextCharFormat format; + QFont font(createDefaultFont()); + + font.setPointSize(UBSettings::settings()->fontPointSize()); + format.setFont(font); + if (UBSettings::settings()->isDarkBackground()) + { + if (UBGraphicsTextItem::lastUsedTextColor == Qt::black) + UBGraphicsTextItem::lastUsedTextColor = Qt::white; + } + else + { + if (UBGraphicsTextItem::lastUsedTextColor == Qt::white) + UBGraphicsTextItem::lastUsedTextColor = Qt::black; + } + + setDefaultTextColor(UBGraphicsTextItem::lastUsedTextColor); + format.setForeground(QBrush(UBGraphicsTextItem::lastUsedTextColor)); + curCursor.mergeCharFormat(format); + setTextCursor(curCursor); + setFont(font); + + adjustSize(); + contentsChanged(); +} + +QFont UBGraphicsTextItem::createDefaultFont() +{ + QFont font; + + QString fFamily = UBSettings::settings()->fontFamily(); + if (!fFamily.isEmpty()) + font.setFamily(fFamily); + + QString fStyleName = UBSettings::settings()->fontStyleName(); + if (!fStyleName .isEmpty()) + font.setStyleName(fStyleName); + + bool bold = UBSettings::settings()->isBoldFont(); + if (bold) + font.setWeight(QFont::Bold); + + bool italic = UBSettings::settings()->isItalicFont(); + if (italic) + font.setItalic(true); + + + int pointSize = UBSettings::settings()->fontPointSize(); + if (pointSize > 0) { + font.setPointSize(pointSize); + } + + return font; +} + void UBGraphicsTextItem::recolor() { UBGraphicsTextItemDelegate * del = dynamic_cast(Delegate()); diff --git a/src/domain/UBGraphicsTextItem.h b/src/domain/UBGraphicsTextItem.h index 584a593c..8c51adef 100644 --- a/src/domain/UBGraphicsTextItem.h +++ b/src/domain/UBGraphicsTextItem.h @@ -100,6 +100,8 @@ class UBGraphicsTextItem : public QGraphicsTextItem, public UBItem, public UBRes void activateTextEditor(bool activate); void setSelected(bool selected); void recolor(); + void initFontProperties(); + QFont createDefaultFont(); QString mTypeTextHereLabel; diff --git a/src/domain/UBGraphicsTextItemDelegate.cpp b/src/domain/UBGraphicsTextItemDelegate.cpp index 08f705b0..b925ae49 100644 --- a/src/domain/UBGraphicsTextItemDelegate.cpp +++ b/src/domain/UBGraphicsTextItemDelegate.cpp @@ -110,32 +110,6 @@ UBGraphicsTextItemDelegate::UBGraphicsTextItemDelegate(UBGraphicsTextItem* pDele , delta(5) { delegated()->setData(UBGraphicsItemData::ItemEditable, QVariant(true)); - delegated()->setPlainText(""); - - QTextCursor curCursor = delegated()->textCursor(); - QTextCharFormat format; - QFont font(createDefaultFont()); - - font.setPointSize(UBSettings::settings()->fontPointSize()); - format.setFont(font); - if (UBSettings::settings()->isDarkBackground()) - { - if (UBGraphicsTextItem::lastUsedTextColor == Qt::black) - UBGraphicsTextItem::lastUsedTextColor = Qt::white; - } - else - { - if (UBGraphicsTextItem::lastUsedTextColor == Qt::white) - UBGraphicsTextItem::lastUsedTextColor = Qt::black; - } - delegated()->setDefaultTextColor(UBGraphicsTextItem::lastUsedTextColor); - format.setForeground(QBrush(UBGraphicsTextItem::lastUsedTextColor)); - curCursor.mergeCharFormat(format); - delegated()->setTextCursor(curCursor); - delegated()->setFont(font); - - delegated()->adjustSize(); - delegated()->contentsChanged(); connect(delegated()->document(), SIGNAL(cursorPositionChanged(QTextCursor)), this, SLOT(onCursorPositionChanged(QTextCursor))); connect(delegated()->document(), SIGNAL(modificationChanged(bool)), this, SLOT(onModificationChanged(bool))); @@ -146,35 +120,6 @@ UBGraphicsTextItemDelegate::~UBGraphicsTextItemDelegate() // NOOP } -QFont UBGraphicsTextItemDelegate::createDefaultFont() -{ - QFont font; - - QString fFamily = UBSettings::settings()->fontFamily(); - if (!fFamily.isEmpty()) - font.setFamily(fFamily); - - QString fStyleName = UBSettings::settings()->fontStyleName(); - if (!fStyleName .isEmpty()) - font.setStyleName(fStyleName); - - bool bold = UBSettings::settings()->isBoldFont(); - if (bold) - font.setWeight(QFont::Bold); - - bool italic = UBSettings::settings()->isItalicFont(); - if (italic) - font.setItalic(true); - - - int pointSize = UBSettings::settings()->fontPointSize(); - if (pointSize > 0) { - font.setPointSize(pointSize); - } - - return font; -} - void UBGraphicsTextItemDelegate::createControls() { UBGraphicsItemDelegate::createControls(); diff --git a/src/domain/UBGraphicsTextItemDelegate.h b/src/domain/UBGraphicsTextItemDelegate.h index 76aaf8f3..30c64849 100644 --- a/src/domain/UBGraphicsTextItemDelegate.h +++ b/src/domain/UBGraphicsTextItemDelegate.h @@ -158,7 +158,6 @@ class UBGraphicsTextItemDelegate : public UBGraphicsItemDelegate void restoreTextCursorFormats(); - QFont createDefaultFont(); QAction *mEditableAction; struct selectionData_t { selectionData_t() From 9001b88a778551e381166dadde0f198822fef75c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Thu, 10 Feb 2022 09:34:40 +0100 Subject: [PATCH 067/130] ask for permission to use system events in order to use virtual keyboard --- resources/macx/Info.plist | 2 ++ 1 file changed, 2 insertions(+) diff --git a/resources/macx/Info.plist b/resources/macx/Info.plist index db9f96f2..5987de23 100644 --- a/resources/macx/Info.plist +++ b/resources/macx/Info.plist @@ -68,6 +68,8 @@ sparkle_public_key.pem NSMicrophoneUsageDescription Accès au microphone requis + NSAppleEventsUsageDescription + Accès au clavier virtuel requis UTExportedTypeDeclarations From 1a0c45b7d6137c0e5733eb7509cdc30e9328f1b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Thu, 10 Feb 2022 14:23:01 +0100 Subject: [PATCH 068/130] call processEvents instead of flush (flush has no effect on mac) --- src/gui/UBMessageWindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/UBMessageWindow.cpp b/src/gui/UBMessageWindow.cpp index c125f411..943a92ab 100644 --- a/src/gui/UBMessageWindow.cpp +++ b/src/gui/UBMessageWindow.cpp @@ -87,7 +87,7 @@ void UBMessageWindow::showMessage(const QString& message, bool showSpinningWheel // showMessage may have been called from the GUI thread, so make sure the message window is drawn right now repaint(); // I mean it, *right now*, also on Mac - qApp->flush(); + qApp->processEvents(); //qApp->sendPostedEvents(); } From bef1cb59053ed2f0edbd895d0b8f41ec6b92748c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Thu, 10 Feb 2022 14:24:28 +0100 Subject: [PATCH 069/130] don't call the end message in the foreach --- src/document/UBDocumentController.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/document/UBDocumentController.cpp b/src/document/UBDocumentController.cpp index 4b168e94..693fbd85 100644 --- a/src/document/UBDocumentController.cpp +++ b/src/document/UBDocumentController.cpp @@ -1566,11 +1566,11 @@ void UBDocumentTreeView::dropEvent(QDropEvent *event) } QApplication::restoreOverrideCursor(); - UBApplication::applicationController->showMessage(tr("%1 pages copied", "", total).arg(total), false); docModel->setHighLighted(QModelIndex()); } + UBApplication::applicationController->showMessage(tr("%1 pages copied", "", total).arg(total), false); UBApplication::documentController->TreeViewSelectionChanged(UBApplication::documentController->firstSelectedTreeIndex(), QModelIndex()); } From edf0631a1dfa9553a2ae8544a9bce8bebf6abab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Thu, 10 Feb 2022 14:51:23 +0100 Subject: [PATCH 070/130] Revert "call processEvents instead of flush (flush has no effect on mac)" as it was provoking some random crashes This reverts commit 1a0c45b7d6137c0e5733eb7509cdc30e9328f1b4. --- src/gui/UBMessageWindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/UBMessageWindow.cpp b/src/gui/UBMessageWindow.cpp index 943a92ab..c125f411 100644 --- a/src/gui/UBMessageWindow.cpp +++ b/src/gui/UBMessageWindow.cpp @@ -87,7 +87,7 @@ void UBMessageWindow::showMessage(const QString& message, bool showSpinningWheel // showMessage may have been called from the GUI thread, so make sure the message window is drawn right now repaint(); // I mean it, *right now*, also on Mac - qApp->processEvents(); + qApp->flush(); //qApp->sendPostedEvents(); } From 6e21cd4c279f222f7a078aaa785f724fe912cd79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Wed, 23 Feb 2022 11:58:08 +0100 Subject: [PATCH 071/130] updated translations --- resources/i18n/OpenBoard_ar.ts | 24 ++++++++++++++++++++++++ resources/i18n/OpenBoard_bg.ts | 24 ++++++++++++++++++++++++ resources/i18n/OpenBoard_ca.ts | 24 ++++++++++++++++++++++++ resources/i18n/OpenBoard_cs.ts | 24 ++++++++++++++++++++++++ resources/i18n/OpenBoard_da.ts | 24 ++++++++++++++++++++++++ resources/i18n/OpenBoard_de.ts | 24 ++++++++++++++++++++++++ resources/i18n/OpenBoard_el.ts | 24 ++++++++++++++++++++++++ resources/i18n/OpenBoard_en.ts | 24 ++++++++++++++++++++++++ resources/i18n/OpenBoard_en_UK.ts | 24 ++++++++++++++++++++++++ resources/i18n/OpenBoard_es.ts | 24 ++++++++++++++++++++++++ resources/i18n/OpenBoard_fr.ts | 24 ++++++++++++++++++++++++ resources/i18n/OpenBoard_fr_CH.ts | 24 ++++++++++++++++++++++++ resources/i18n/OpenBoard_gl.ts | 24 ++++++++++++++++++++++++ resources/i18n/OpenBoard_hu.ts | 24 ++++++++++++++++++++++++ resources/i18n/OpenBoard_it.ts | 24 ++++++++++++++++++++++++ resources/i18n/OpenBoard_iw.ts | 24 ++++++++++++++++++++++++ resources/i18n/OpenBoard_ja.ts | 24 ++++++++++++++++++++++++ resources/i18n/OpenBoard_ko.ts | 24 ++++++++++++++++++++++++ resources/i18n/OpenBoard_mg.ts | 24 ++++++++++++++++++++++++ resources/i18n/OpenBoard_nb.ts | 24 ++++++++++++++++++++++++ resources/i18n/OpenBoard_nl.ts | 24 ++++++++++++++++++++++++ resources/i18n/OpenBoard_pl.ts | 24 ++++++++++++++++++++++++ resources/i18n/OpenBoard_pt.ts | 24 ++++++++++++++++++++++++ resources/i18n/OpenBoard_ro.ts | 24 ++++++++++++++++++++++++ resources/i18n/OpenBoard_ru.ts | 24 ++++++++++++++++++++++++ resources/i18n/OpenBoard_sk.ts | 24 ++++++++++++++++++++++++ resources/i18n/OpenBoard_sv.ts | 24 ++++++++++++++++++++++++ resources/i18n/OpenBoard_tr.ts | 24 ++++++++++++++++++++++++ resources/i18n/OpenBoard_uk.ts | 24 ++++++++++++++++++++++++ resources/i18n/OpenBoard_zh.ts | 24 ++++++++++++++++++++++++ resources/i18n/OpenBoard_zh_CN.ts | 24 ++++++++++++++++++++++++ resources/i18n/OpenBoard_zh_TW.ts | 24 ++++++++++++++++++++++++ src/adaptors/UBThumbnailAdaptor.cpp | 1 - src/core/UBPersistenceManager.cpp | 2 +- src/core/UBSceneCache.cpp | 2 +- src/gui/UBDocumentNavigator.cpp | 2 +- 36 files changed, 771 insertions(+), 4 deletions(-) diff --git a/resources/i18n/OpenBoard_ar.ts b/resources/i18n/OpenBoard_ar.ts index 9ad98c6d..60221728 100644 --- a/resources/i18n/OpenBoard_ar.ts +++ b/resources/i18n/OpenBoard_ar.ts @@ -896,6 +896,14 @@ Are you sure you want to remove 1 page from the selected document '%0'? هل تريدون حقا إزالة صفحة واحدة من الوثيقة '%0' المختارة؟ + + Loading scene (%1/%2) + + + + Moving cached scenes (%1/%2) + + UBApplication @@ -1235,6 +1243,10 @@ Title page + + Refreshing Document Thumbnails View (%1/%2) + + UBDocumentManager @@ -1285,6 +1297,10 @@ Page %0 صفحة %0 + + Generating thumbnails for board (%1/%2) + + UBDocumentPublisher @@ -1985,6 +2001,10 @@ Do you want to ignore these errors for this host? OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. + + Renaming pages (%1/%2) + + UBPlatformUtils @@ -2148,6 +2168,10 @@ Do you want to ignore these errors for this host? %1 thumbnails generated ... 1 % صور مصغرة مولّدة + + Loading thumbnail (%1/%2) + + UBThumbnailTextItem diff --git a/resources/i18n/OpenBoard_bg.ts b/resources/i18n/OpenBoard_bg.ts index 303f13a3..1bbf4db8 100644 --- a/resources/i18n/OpenBoard_bg.ts +++ b/resources/i18n/OpenBoard_bg.ts @@ -896,6 +896,14 @@ Are you sure you want to remove 1 page from the selected document '%0'? Сигурни ли сте ,че искате да премахнете 1 страница от избрания документ '%0'? + + Loading scene (%1/%2) + + + + Moving cached scenes (%1/%2) + + UBApplication @@ -1227,6 +1235,10 @@ Title page + + Refreshing Document Thumbnails View (%1/%2) + + UBDocumentManager @@ -1277,6 +1289,10 @@ Page %0 Страница %0 + + Generating thumbnails for board (%1/%2) + + UBDocumentReplaceDialog @@ -1964,6 +1980,10 @@ Do you want to ignore these errors for this host? OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. + + Renaming pages (%1/%2) + + UBPlatformUtils @@ -2127,6 +2147,10 @@ Do you want to ignore these errors for this host? %1 thumbnails generated ... %1 икони са създадени ... + + Loading thumbnail (%1/%2) + + UBThumbnailTextItem diff --git a/resources/i18n/OpenBoard_ca.ts b/resources/i18n/OpenBoard_ca.ts index e7b01e8d..ccd562d0 100644 --- a/resources/i18n/OpenBoard_ca.ts +++ b/resources/i18n/OpenBoard_ca.ts @@ -896,6 +896,14 @@ Are you sure you want to remove 1 page from the selected document '%0'? Esteu segur que voleu eliminar 1 pàgina del document seleccionat '%0'? + + Loading scene (%1/%2) + + + + Moving cached scenes (%1/%2) + + UBApplication @@ -1226,6 +1234,10 @@ Title page + + Refreshing Document Thumbnails View (%1/%2) + + UBDocumentManager @@ -1276,6 +1288,10 @@ Page %0 Pàgina %0 + + Generating thumbnails for board (%1/%2) + + UBDocumentPublisher @@ -1970,6 +1986,10 @@ Voleu ignorar aquests errors per a aquest amfitrió? OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. + + Renaming pages (%1/%2) + + UBPlatformUtils @@ -2126,6 +2146,10 @@ Voleu ignorar aquests errors per a aquest amfitrió? %1 thumbnails generated ... S'han generat %1 miniatures... + + Loading thumbnail (%1/%2) + + UBThumbnailTextItem diff --git a/resources/i18n/OpenBoard_cs.ts b/resources/i18n/OpenBoard_cs.ts index 92187e20..682ec6c4 100644 --- a/resources/i18n/OpenBoard_cs.ts +++ b/resources/i18n/OpenBoard_cs.ts @@ -897,6 +897,14 @@ Are you sure you want to remove 1 page from the selected document '%0'? Opravdu chcete odstranit 1 stránku z vybraného dokumentu '%0'? + + Loading scene (%1/%2) + + + + Moving cached scenes (%1/%2) + + UBApplication @@ -1229,6 +1237,10 @@ Title page + + Refreshing Document Thumbnails View (%1/%2) + + UBDocumentManager @@ -1279,6 +1291,10 @@ Page %0 Stránka %0 + + Generating thumbnails for board (%1/%2) + + UBDocumentReplaceDialog @@ -1969,6 +1985,10 @@ Chcete ignorovat tyto chyby na tomto serveru? OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. + + Renaming pages (%1/%2) + + UBPlatformUtils @@ -2132,6 +2152,10 @@ Chcete ignorovat tyto chyby na tomto serveru? Generating preview thumbnails ... Vytváří se miniatury obrázků ... + + Loading thumbnail (%1/%2) + + UBThumbnailTextItem diff --git a/resources/i18n/OpenBoard_da.ts b/resources/i18n/OpenBoard_da.ts index 60f55f91..1b072aee 100644 --- a/resources/i18n/OpenBoard_da.ts +++ b/resources/i18n/OpenBoard_da.ts @@ -896,6 +896,14 @@ Are you sure you want to remove 1 page from the selected document '%0'? Er du sikker på, at du vil fjerne 1 side fra det valgte dokument'%0'? + + Loading scene (%1/%2) + + + + Moving cached scenes (%1/%2) + + UBApplication @@ -1226,6 +1234,10 @@ Title page + + Refreshing Document Thumbnails View (%1/%2) + + UBDocumentManager @@ -1276,6 +1288,10 @@ Page %0 Side %0 + + Generating thumbnails for board (%1/%2) + + UBDocumentPublisher @@ -1970,6 +1986,10 @@ Do you want to ignore these errors for this host? OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. + + Renaming pages (%1/%2) + + UBPlatformUtils @@ -2133,6 +2153,10 @@ Do you want to ignore these errors for this host? %1 thumbnails generated ... %1 miniaturebilleder genereret... + + Loading thumbnail (%1/%2) + + UBThumbnailTextItem diff --git a/resources/i18n/OpenBoard_de.ts b/resources/i18n/OpenBoard_de.ts index 9a67156d..aaa9a3ba 100644 --- a/resources/i18n/OpenBoard_de.ts +++ b/resources/i18n/OpenBoard_de.ts @@ -896,6 +896,14 @@ Are you sure you want to remove 1 page from the selected document '%0'? Wollen Sie wirklich die ausgewählte Seite des Dokuments '%0' entfernen? + + Loading scene (%1/%2) + + + + Moving cached scenes (%1/%2) + + UBApplication @@ -1242,6 +1250,10 @@ Empty My Documents Eigene Dokumente leeren + + Refreshing Document Thumbnails View (%1/%2) + + UBDocumentManager @@ -1292,6 +1304,10 @@ Page %0 Seite %0 + + Generating thumbnails for board (%1/%2) + + UBDocumentPublisher @@ -2028,6 +2044,10 @@ Möchten Sie diese Fehler für diesen Computer ignorieren? OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. Openboard hat den Zugang zum Dokumentenarchiv '%1' verloren. Die Anwendung muss leider beendet werden, um Datenkorruption zu vermeiden. Der Verlust kürzlich vorgenommener Änderungen ist möglich. + + Renaming pages (%1/%2) + + UBPlatformUtils @@ -2196,6 +2216,10 @@ Möchten Sie diese Fehler für diesen Computer ignorieren? Miniaturansicht der Seite %1 wird geladen + + Loading thumbnail (%1/%2) + + UBThumbnailTextItem diff --git a/resources/i18n/OpenBoard_el.ts b/resources/i18n/OpenBoard_el.ts index 329a7b9a..f7b515ab 100644 --- a/resources/i18n/OpenBoard_el.ts +++ b/resources/i18n/OpenBoard_el.ts @@ -896,6 +896,14 @@ Are you sure you want to remove 1 page from the selected document '%0'? Είστε βέβαιος ότι θέλετε να αφαιρέσετε μια σελίδα από το επιλεγμένο έγγραφο '%0'? + + Loading scene (%1/%2) + + + + Moving cached scenes (%1/%2) + + UBApplication @@ -1226,6 +1234,10 @@ Title page + + Refreshing Document Thumbnails View (%1/%2) + + UBDocumentManager @@ -1276,6 +1288,10 @@ Page %0 Σελίδα %0 + + Generating thumbnails for board (%1/%2) + + UBDocumentReplaceDialog @@ -1963,6 +1979,10 @@ Do you want to ignore these errors for this host? OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. + + Renaming pages (%1/%2) + + UBPlatformUtils @@ -2126,6 +2146,10 @@ Do you want to ignore these errors for this host? %1 thumbnails generated ... %1 μικρογραφίες δημιουργήθηκαν... + + Loading thumbnail (%1/%2) + + UBThumbnailTextItem diff --git a/resources/i18n/OpenBoard_en.ts b/resources/i18n/OpenBoard_en.ts index 348265ef..792ae6d4 100644 --- a/resources/i18n/OpenBoard_en.ts +++ b/resources/i18n/OpenBoard_en.ts @@ -888,6 +888,14 @@ Are you sure you want to remove 1 page from the selected document '%0'? + + Loading scene (%1/%2) + + + + Moving cached scenes (%1/%2) + + UBApplication @@ -1166,6 +1174,10 @@ Empty + + Refreshing Document Thumbnails View (%1/%2) + + UBDocumentManager @@ -1216,6 +1228,10 @@ Page %0 + + Generating thumbnails for board (%1/%2) + + UBDocumentReplaceDialog @@ -1845,6 +1861,10 @@ Do you want to ignore these errors for this host? OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. + + Renaming pages (%1/%2) + + UBPlatformUtils @@ -1974,6 +1994,10 @@ Do you want to ignore these errors for this host? %1 thumbnails generated ... + + Loading thumbnail (%1/%2) + + UBThumbnailTextItem diff --git a/resources/i18n/OpenBoard_en_UK.ts b/resources/i18n/OpenBoard_en_UK.ts index 348265ef..792ae6d4 100644 --- a/resources/i18n/OpenBoard_en_UK.ts +++ b/resources/i18n/OpenBoard_en_UK.ts @@ -888,6 +888,14 @@ Are you sure you want to remove 1 page from the selected document '%0'? + + Loading scene (%1/%2) + + + + Moving cached scenes (%1/%2) + + UBApplication @@ -1166,6 +1174,10 @@ Empty + + Refreshing Document Thumbnails View (%1/%2) + + UBDocumentManager @@ -1216,6 +1228,10 @@ Page %0 + + Generating thumbnails for board (%1/%2) + + UBDocumentReplaceDialog @@ -1845,6 +1861,10 @@ Do you want to ignore these errors for this host? OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. + + Renaming pages (%1/%2) + + UBPlatformUtils @@ -1974,6 +1994,10 @@ Do you want to ignore these errors for this host? %1 thumbnails generated ... + + Loading thumbnail (%1/%2) + + UBThumbnailTextItem diff --git a/resources/i18n/OpenBoard_es.ts b/resources/i18n/OpenBoard_es.ts index f3543d1a..bf30a45d 100644 --- a/resources/i18n/OpenBoard_es.ts +++ b/resources/i18n/OpenBoard_es.ts @@ -896,6 +896,14 @@ Are you sure you want to remove 1 page from the selected document '%0'? ¿Está seguro de que quiere eliminar 1 página del documento seleccionado.'%0? + + Loading scene (%1/%2) + + + + Moving cached scenes (%1/%2) + + UBApplication @@ -1238,6 +1246,10 @@ Title page Página de título + + Refreshing Document Thumbnails View (%1/%2) + + UBDocumentManager @@ -1288,6 +1300,10 @@ Page %0 Página %0 + + Generating thumbnails for board (%1/%2) + + UBDocumentReplaceDialog @@ -1979,6 +1995,10 @@ Do you want to ignore these errors for this host? OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. OpenBoard perdió el acceso al repositorio de documentos '%1'. Desafortunadamente, la aplicación debe cerrarse para evitar la corrrupción de datos. También se pueden perder los últimos cambios. + + Renaming pages (%1/%2) + + UBPlatformUtils @@ -2146,6 +2166,10 @@ Do you want to ignore these errors for this host? loading thumbnail of page %1 cargando miniaturas de la página %1 + + Loading thumbnail (%1/%2) + + UBThumbnailTextItem diff --git a/resources/i18n/OpenBoard_fr.ts b/resources/i18n/OpenBoard_fr.ts index a0735e73..9bd5c135 100644 --- a/resources/i18n/OpenBoard_fr.ts +++ b/resources/i18n/OpenBoard_fr.ts @@ -897,6 +897,14 @@ Are you sure you want to remove 1 page from the selected document '%0'? Voulez-vous vraiment effacer 1 page de ce document '%0'? + + Loading scene (%1/%2) + Chargement de la scène (%1/%2) + + + Moving cached scenes (%1/%2) + Déplacement des scènes mises en cache (%1/%2) + UBApplication @@ -1251,6 +1259,10 @@ Empty My Documents Vider Mes Documents + + Refreshing Document Thumbnails View (%1/%2) + Actualisation des aperçus du mode Documents (%1/%2) + UBDocumentManager @@ -1302,6 +1314,10 @@ Page %0 Page %0 + + Generating thumbnails for board (%1/%2) + Création des aperçus du mode Tableau (%1/%2) + UBDocumentPublisher @@ -2041,6 +2057,10 @@ Voulez-vous ignorer les erreurs pour ce serveur ? OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. OpenBoard a perdu l'accès au répertoire des documents '%1'. Malheureusement, l'application sera fermée afin d'éviter la corruption des données. Les dernières modifications pourraient être également perdues. + + Renaming pages (%1/%2) + Actualisation des noms des pages en cours (%1/%2) + UBPlatformUtils @@ -2208,6 +2228,10 @@ Voulez-vous ignorer les erreurs pour ce serveur ? loading thumbnail of page %1 Chargement aperçu page %1 + + Loading thumbnail (%1/%2) + Chargement de l'aperçu (%1/%2) + UBThumbnailTextItem diff --git a/resources/i18n/OpenBoard_fr_CH.ts b/resources/i18n/OpenBoard_fr_CH.ts index 2b764e91..da3a92c2 100644 --- a/resources/i18n/OpenBoard_fr_CH.ts +++ b/resources/i18n/OpenBoard_fr_CH.ts @@ -897,6 +897,14 @@ Are you sure you want to remove 1 page from the selected document '%0'? Voulez-vous vraiment effacer 1 page de ce document '%0'? + + Loading scene (%1/%2) + Chargement de la scène (%1/%2) + + + Moving cached scenes (%1/%2) + Déplacement des scènes mises en cache (%1/%2) + UBApplication @@ -1251,6 +1259,10 @@ Empty My Documents Vider Mes Documents + + Refreshing Document Thumbnails View (%1/%2) + Actualisation des aperçus du mode Documents (%1/%2) + UBDocumentManager @@ -1302,6 +1314,10 @@ Page %0 Page %0 + + Generating thumbnails for board (%1/%2) + Création des aperçus du mode Tableau (%1/%2) + UBDocumentPublisher @@ -2041,6 +2057,10 @@ Voulez-vous ignorer les erreurs pour ce serveur ? OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. OpenBoard a perdu l'accès au répertoire des documents '%1'. Malheureusement, l'application sera fermée afin d'éviter la corruption des données. Les dernières modifications pourraient être également perdues. + + Renaming pages (%1/%2) + Actualisation des noms des pages en cours (%1/%2) + UBPlatformUtils @@ -2208,6 +2228,10 @@ Voulez-vous ignorer les erreurs pour ce serveur ? loading thumbnail of page %1 Chargement aperçu page %1 + + Loading thumbnail (%1/%2) + Chargement de l'aperçu (%1/%2) + UBThumbnailTextItem diff --git a/resources/i18n/OpenBoard_gl.ts b/resources/i18n/OpenBoard_gl.ts index ba0a0751..b84aa5a2 100644 --- a/resources/i18n/OpenBoard_gl.ts +++ b/resources/i18n/OpenBoard_gl.ts @@ -896,6 +896,14 @@ Are you sure you want to remove 1 page from the selected document '%0'? ¿Está seguro de que quere eliminar 1 páxina do documento seleccionado.'%0? + + Loading scene (%1/%2) + + + + Moving cached scenes (%1/%2) + + UBApplication @@ -1238,6 +1246,10 @@ Title page Páxina de título + + Refreshing Document Thumbnails View (%1/%2) + + UBDocumentManager @@ -1288,6 +1300,10 @@ Page %0 Páxina %0 + + Generating thumbnails for board (%1/%2) + + UBDocumentReplaceDialog @@ -1979,6 +1995,10 @@ Do you want to ignore these errors for this host? OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. OpenBoard perdeu o acceso ao repositorio de documentos '%1'. Desafortunadamente, a aplicación debe pecharse para evitar a corrrupción de datos. Tamén se poden perder os últimos cambios. + + Renaming pages (%1/%2) + + UBPlatformUtils @@ -2146,6 +2166,10 @@ Do you want to ignore these errors for this host? loading thumbnail of page %1 cargando miniaturas da páxina %1 + + Loading thumbnail (%1/%2) + + UBThumbnailTextItem diff --git a/resources/i18n/OpenBoard_hu.ts b/resources/i18n/OpenBoard_hu.ts index a66081cf..a1912b13 100644 --- a/resources/i18n/OpenBoard_hu.ts +++ b/resources/i18n/OpenBoard_hu.ts @@ -896,6 +896,14 @@ Content is not supported in destination format. + + Loading scene (%1/%2) + + + + Moving cached scenes (%1/%2) + + UBApplication @@ -1232,6 +1240,10 @@ Title page + + Refreshing Document Thumbnails View (%1/%2) + + UBDocumentManager @@ -1282,6 +1294,10 @@ Page %0 + + Generating thumbnails for board (%1/%2) + + UBDocumentReplaceDialog @@ -1946,6 +1962,10 @@ Figyelmen kívül hagyja ezeket a hibákat ennél a hosztnál? OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. + + Renaming pages (%1/%2) + + UBPlatformUtils @@ -2113,6 +2133,10 @@ Figyelmen kívül hagyja ezeket a hibákat ennél a hosztnál? loading thumbnail of page %1 %1. oldal előképének betöltése + + Loading thumbnail (%1/%2) + + UBThumbnailTextItem diff --git a/resources/i18n/OpenBoard_it.ts b/resources/i18n/OpenBoard_it.ts index c134c503..9f6be1ba 100644 --- a/resources/i18n/OpenBoard_it.ts +++ b/resources/i18n/OpenBoard_it.ts @@ -896,6 +896,14 @@ Are you sure you want to remove 1 page from the selected document '%0'? Sei sicuro di voler rimuovere 1 pagina dal documento selezionato '%0'? + + Loading scene (%1/%2) + + + + Moving cached scenes (%1/%2) + + UBApplication @@ -1238,6 +1246,10 @@ Title page Frontespizio + + Refreshing Document Thumbnails View (%1/%2) + + UBDocumentManager @@ -1288,6 +1300,10 @@ Page %0 Pagina %0 + + Generating thumbnails for board (%1/%2) + + UBDocumentPublisher @@ -2020,6 +2036,10 @@ Vuoi ignorare gli errori per questo host? OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. OpenBoard ha perso l'accesso al repository documenti "%1". Sfortunatamente l'applicazione deve essere chiusa per evitare di rovinare i dati. Gli ultimi cambiamenti potrebbero andare persi. + + Renaming pages (%1/%2) + + UBPlatformUtils @@ -2187,6 +2207,10 @@ Vuoi ignorare gli errori per questo host? loading thumbnail of page %1 Caricamento miniatura della pagina %1 + + Loading thumbnail (%1/%2) + + UBThumbnailTextItem diff --git a/resources/i18n/OpenBoard_iw.ts b/resources/i18n/OpenBoard_iw.ts index c44ddd5b..2e769753 100644 --- a/resources/i18n/OpenBoard_iw.ts +++ b/resources/i18n/OpenBoard_iw.ts @@ -897,6 +897,14 @@ Are you sure you want to remove 1 page from the selected document '%0'? האם אתה בטוח שברצונך למחוק דף 1 מהמסמך שנבחר %0? + + Loading scene (%1/%2) + + + + Moving cached scenes (%1/%2) + + UBApplication @@ -1227,6 +1235,10 @@ Title page + + Refreshing Document Thumbnails View (%1/%2) + + UBDocumentManager @@ -1277,6 +1289,10 @@ Page %0 עמוד %0 + + Generating thumbnails for board (%1/%2) + + UBDocumentReplaceDialog @@ -1959,6 +1975,10 @@ Do you want to ignore these errors for this host? OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. + + Renaming pages (%1/%2) + + UBPlatformUtils @@ -2122,6 +2142,10 @@ Do you want to ignore these errors for this host? %1 thumbnails generated ... נוצרה תמונה מוקטנת של %1... + + Loading thumbnail (%1/%2) + + UBThumbnailTextItem diff --git a/resources/i18n/OpenBoard_ja.ts b/resources/i18n/OpenBoard_ja.ts index 2af7f683..31cc8803 100644 --- a/resources/i18n/OpenBoard_ja.ts +++ b/resources/i18n/OpenBoard_ja.ts @@ -896,6 +896,14 @@ Are you sure you want to remove 1 page from the selected document '%0'? + + Loading scene (%1/%2) + + + + Moving cached scenes (%1/%2) + + UBApplication @@ -1220,6 +1228,10 @@ Title page + + Refreshing Document Thumbnails View (%1/%2) + + UBDocumentManager @@ -1270,6 +1282,10 @@ Page %0 %0ページ + + Generating thumbnails for board (%1/%2) + + UBDocumentReplaceDialog @@ -1955,6 +1971,10 @@ Do you want to ignore these errors for this host? OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. + + Renaming pages (%1/%2) + + UBPlatformUtils @@ -2118,6 +2138,10 @@ Do you want to ignore these errors for this host? %1 thumbnails generated ... %1 サムネイル作成済み ... + + Loading thumbnail (%1/%2) + + UBThumbnailTextItem diff --git a/resources/i18n/OpenBoard_ko.ts b/resources/i18n/OpenBoard_ko.ts index c5b62ec7..c61d339c 100644 --- a/resources/i18n/OpenBoard_ko.ts +++ b/resources/i18n/OpenBoard_ko.ts @@ -896,6 +896,14 @@ Are you sure you want to remove 1 page from the selected document '%0'? 선택한 문서 '%0'에서 1페이지를 제거하시겠습니까? + + Loading scene (%1/%2) + + + + Moving cached scenes (%1/%2) + + UBApplication @@ -1224,6 +1232,10 @@ Title page + + Refreshing Document Thumbnails View (%1/%2) + + UBDocumentManager @@ -1274,6 +1286,10 @@ Page %0 %0 페이지 + + Generating thumbnails for board (%1/%2) + + UBDocumentPublisher @@ -1974,6 +1990,10 @@ Do you want to ignore these errors for this host? OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. + + Renaming pages (%1/%2) + + UBPlatformUtils @@ -2137,6 +2157,10 @@ Do you want to ignore these errors for this host? %1 thumbnails generated ... %1 썸네일 생성됨 ... + + Loading thumbnail (%1/%2) + + UBThumbnailTextItem diff --git a/resources/i18n/OpenBoard_mg.ts b/resources/i18n/OpenBoard_mg.ts index 7adefee0..c2eb0752 100644 --- a/resources/i18n/OpenBoard_mg.ts +++ b/resources/i18n/OpenBoard_mg.ts @@ -896,6 +896,14 @@ Are you sure you want to remove 1 page from the selected document '%0'? Tena te hamafa pejy iray avy amin'ilay rakitra '%0' voafidy ve ianao ? + + Loading scene (%1/%2) + + + + Moving cached scenes (%1/%2) + + UBApplication @@ -1226,6 +1234,10 @@ Title page + + Refreshing Document Thumbnails View (%1/%2) + + UBDocumentManager @@ -1276,6 +1288,10 @@ Page %0 Pejy %0 + + Generating thumbnails for board (%1/%2) + + UBDocumentPublisher @@ -1970,6 +1986,10 @@ Tena tsy te hiraharaha an'ireo tsy mety ho an'ilay milina ve ianao?OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. + + Renaming pages (%1/%2) + + UBPlatformUtils @@ -2133,6 +2153,10 @@ Tena tsy te hiraharaha an'ireo tsy mety ho an'ilay milina ve ianao?%1 thumbnails generated ... %1 ny kisarisary no voaforona ... + + Loading thumbnail (%1/%2) + + UBThumbnailTextItem diff --git a/resources/i18n/OpenBoard_nb.ts b/resources/i18n/OpenBoard_nb.ts index 5b4409ae..0a574139 100644 --- a/resources/i18n/OpenBoard_nb.ts +++ b/resources/i18n/OpenBoard_nb.ts @@ -896,6 +896,14 @@ Are you sure you want to remove 1 page from the selected document '%0'? Er du sikker på at du vil fjerne side 1 fra det valgte dokumentet '%0'? + + Loading scene (%1/%2) + + + + Moving cached scenes (%1/%2) + + UBApplication @@ -1227,6 +1235,10 @@ Title page + + Refreshing Document Thumbnails View (%1/%2) + + UBDocumentManager @@ -1277,6 +1289,10 @@ Page %0 Side %0 + + Generating thumbnails for board (%1/%2) + + UBDocumentReplaceDialog @@ -1959,6 +1975,10 @@ Do you want to ignore these errors for this host? OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. + + Renaming pages (%1/%2) + + UBPlatformUtils @@ -2122,6 +2142,10 @@ Do you want to ignore these errors for this host? %1 thumbnails generated ... %1 miniatyrbilder generert ... + + Loading thumbnail (%1/%2) + + UBThumbnailTextItem diff --git a/resources/i18n/OpenBoard_nl.ts b/resources/i18n/OpenBoard_nl.ts index d3e15859..ba7eb1a8 100644 --- a/resources/i18n/OpenBoard_nl.ts +++ b/resources/i18n/OpenBoard_nl.ts @@ -896,6 +896,14 @@ Are you sure you want to remove 1 page from the selected document '%0'? + + Loading scene (%1/%2) + + + + Moving cached scenes (%1/%2) + + UBApplication @@ -1222,6 +1230,10 @@ Title page + + Refreshing Document Thumbnails View (%1/%2) + + UBDocumentManager @@ -1272,6 +1284,10 @@ Page %0 Pagina %0 + + Generating thumbnails for board (%1/%2) + + UBDocumentPublisher @@ -1960,6 +1976,10 @@ Do you want to ignore these errors for this host? OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. + + Renaming pages (%1/%2) + + UBPlatformUtils @@ -2123,6 +2143,10 @@ Do you want to ignore these errors for this host? %1 thumbnails generated ... %1 miniaturen opgeladen... + + Loading thumbnail (%1/%2) + + UBThumbnailTextItem diff --git a/resources/i18n/OpenBoard_pl.ts b/resources/i18n/OpenBoard_pl.ts index 5802b1bb..99ddbd60 100644 --- a/resources/i18n/OpenBoard_pl.ts +++ b/resources/i18n/OpenBoard_pl.ts @@ -900,6 +900,14 @@ Are you sure you want to remove 1 page from the selected document '%0'? Czy na pewno chcesz usunąć 1 stronę z wybranego dokumentu „%0”? + + Loading scene (%1/%2) + + + + Moving cached scenes (%1/%2) + + UBApplication @@ -1232,6 +1240,10 @@ Title page + + Refreshing Document Thumbnails View (%1/%2) + + UBDocumentManager @@ -1282,6 +1294,10 @@ Page %0 Strona %0 + + Generating thumbnails for board (%1/%2) + + UBDocumentPublisher @@ -1980,6 +1996,10 @@ Czy chcesz ignorować te błędy dla tego hosta? OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. + + Renaming pages (%1/%2) + + UBPlatformUtils @@ -2143,6 +2163,10 @@ Czy chcesz ignorować te błędy dla tego hosta? %1 thumbnails generated ... Wygenerowano %1 miniatur... + + Loading thumbnail (%1/%2) + + UBThumbnailTextItem diff --git a/resources/i18n/OpenBoard_pt.ts b/resources/i18n/OpenBoard_pt.ts index 8ba43ccf..28c49903 100644 --- a/resources/i18n/OpenBoard_pt.ts +++ b/resources/i18n/OpenBoard_pt.ts @@ -898,6 +898,14 @@ Are you sure you want to remove 1 page from the selected document '%0'? Tem a certeza que quer remover 1 página do documento selecionado '%0'? + + Loading scene (%1/%2) + + + + Moving cached scenes (%1/%2) + + UBApplication @@ -1236,6 +1244,10 @@ Title page + + Refreshing Document Thumbnails View (%1/%2) + + UBDocumentManager @@ -1286,6 +1298,10 @@ Page %0 Página %0 + + Generating thumbnails for board (%1/%2) + + UBDocumentReplaceDialog @@ -2002,6 +2018,10 @@ Quer ignorar estes erros, deste servidor? OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. + + Renaming pages (%1/%2) + + UBPlatformUtils @@ -2158,6 +2178,10 @@ Quer ignorar estes erros, deste servidor? %1 thumbnails generated ... %1 de miniaturas geradas ... + + Loading thumbnail (%1/%2) + + UBThumbnailTextItem diff --git a/resources/i18n/OpenBoard_ro.ts b/resources/i18n/OpenBoard_ro.ts index fc63c391..afeb969e 100644 --- a/resources/i18n/OpenBoard_ro.ts +++ b/resources/i18n/OpenBoard_ro.ts @@ -896,6 +896,14 @@ Are you sure you want to remove 1 page from the selected document '%0'? Sunteți sigur că doriți să eliminați 1 pagină din documentul selectat '%0'? + + Loading scene (%1/%2) + + + + Moving cached scenes (%1/%2) + + UBApplication @@ -1226,6 +1234,10 @@ Title page + + Refreshing Document Thumbnails View (%1/%2) + + UBDocumentManager @@ -1276,6 +1288,10 @@ Page %0 Pagina %0 + + Generating thumbnails for board (%1/%2) + + UBDocumentPublisher @@ -1970,6 +1986,10 @@ Doriţi să ignoraţi aceste erori pentru acest host? OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. + + Renaming pages (%1/%2) + + UBPlatformUtils @@ -2133,6 +2153,10 @@ Doriţi să ignoraţi aceste erori pentru acest host? %1 thumbnails generated ... %1 miniaturi generate ... + + Loading thumbnail (%1/%2) + + UBThumbnailTextItem diff --git a/resources/i18n/OpenBoard_ru.ts b/resources/i18n/OpenBoard_ru.ts index ece64aea..a230bbfc 100644 --- a/resources/i18n/OpenBoard_ru.ts +++ b/resources/i18n/OpenBoard_ru.ts @@ -896,6 +896,14 @@ Are you sure you want to remove 1 page from the selected document '%0'? Вы уверены, что хотите удалить 1 страницу из документа '%0'? + + Loading scene (%1/%2) + + + + Moving cached scenes (%1/%2) + + UBApplication @@ -1226,6 +1234,10 @@ Title page Заглавная страница + + Refreshing Document Thumbnails View (%1/%2) + + UBDocumentManager @@ -1276,6 +1288,10 @@ Page %0 Страница %0 + + Generating thumbnails for board (%1/%2) + + UBDocumentPublisher @@ -1972,6 +1988,10 @@ Do you want to ignore these errors for this host? OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. Потерян доступ к репозиторию документов «%1». Приложение должно быть закрыто, чтобы избежать повреждения данных. Последние изменения также могут быть утеряны. + + Renaming pages (%1/%2) + + UBPlatformUtils @@ -2135,6 +2155,10 @@ Do you want to ignore these errors for this host? %1 thumbnails generated ... сгенерированы эскизы %1... + + Loading thumbnail (%1/%2) + + UBThumbnailTextItem diff --git a/resources/i18n/OpenBoard_sk.ts b/resources/i18n/OpenBoard_sk.ts index c2abbc34..3c5c9cdb 100644 --- a/resources/i18n/OpenBoard_sk.ts +++ b/resources/i18n/OpenBoard_sk.ts @@ -900,6 +900,14 @@ Are you sure you want to remove 1 page from the selected document '%0'? Určite chcete odstrániť 1 stránku z vybraného dokumentu '%0'? + + Loading scene (%1/%2) + + + + Moving cached scenes (%1/%2) + + UBApplication @@ -1240,6 +1248,10 @@ Title page + + Refreshing Document Thumbnails View (%1/%2) + + UBDocumentManager @@ -1290,6 +1302,10 @@ Page %0 Stránka %0 + + Generating thumbnails for board (%1/%2) + + UBDocumentReplaceDialog @@ -2008,6 +2024,10 @@ Chcete ignorovať tieto chyby na tomto serveri? OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. + + Renaming pages (%1/%2) + + UBPlatformUtils @@ -2173,6 +2193,10 @@ Chcete ignorovať tieto chyby na tomto serveri? Generating preview thumbnails ... Vytvárajú sa ukážky miniatúr... + + Loading thumbnail (%1/%2) + + UBThumbnailTextItem diff --git a/resources/i18n/OpenBoard_sv.ts b/resources/i18n/OpenBoard_sv.ts index eed47fcb..222ab123 100644 --- a/resources/i18n/OpenBoard_sv.ts +++ b/resources/i18n/OpenBoard_sv.ts @@ -896,6 +896,14 @@ Are you sure you want to remove 1 page from the selected document '%0'? Är du säker på att du vill ta bort 1 sida från det valda dokumentet '%0'? + + Loading scene (%1/%2) + + + + Moving cached scenes (%1/%2) + + UBApplication @@ -1227,6 +1235,10 @@ Title page + + Refreshing Document Thumbnails View (%1/%2) + + UBDocumentManager @@ -1277,6 +1289,10 @@ Page %0 Sida %0 + + Generating thumbnails for board (%1/%2) + + UBDocumentPublisher @@ -1971,6 +1987,10 @@ Vill du ignorera felen för den här värden? OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. + + Renaming pages (%1/%2) + + UBPlatformUtils @@ -2134,6 +2154,10 @@ Vill du ignorera felen för den här värden? %1 thumbnails generated ... %1 miniatybil genererad ... + + Loading thumbnail (%1/%2) + + UBThumbnailTextItem diff --git a/resources/i18n/OpenBoard_tr.ts b/resources/i18n/OpenBoard_tr.ts index efe8bbfb..e1e2d349 100644 --- a/resources/i18n/OpenBoard_tr.ts +++ b/resources/i18n/OpenBoard_tr.ts @@ -896,6 +896,14 @@ Are you sure you want to remove 1 page from the selected document '%0'? Seçili olan '%0' adlı dökümandan 1 sayfayı kaldırmak üzeresiniz. Bu işlemi yapmak istediğinizden eminmisiniz? + + Loading scene (%1/%2) + + + + Moving cached scenes (%1/%2) + + UBApplication @@ -1224,6 +1232,10 @@ Title page + + Refreshing Document Thumbnails View (%1/%2) + + UBDocumentManager @@ -1274,6 +1286,10 @@ Page %0 Sayfa %0 + + Generating thumbnails for board (%1/%2) + + UBDocumentReplaceDialog @@ -1958,6 +1974,10 @@ Bu host için yukarıdaki hatalar yok sayılsın mı? OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. + + Renaming pages (%1/%2) + + UBPlatformUtils @@ -2121,6 +2141,10 @@ Bu host için yukarıdaki hatalar yok sayılsın mı? %1 thumbnails generated ... %1 adet önizleme resmi oluşturuldu ... + + Loading thumbnail (%1/%2) + + UBThumbnailTextItem diff --git a/resources/i18n/OpenBoard_uk.ts b/resources/i18n/OpenBoard_uk.ts index 8a9513e8..d1eb6c08 100644 --- a/resources/i18n/OpenBoard_uk.ts +++ b/resources/i18n/OpenBoard_uk.ts @@ -896,6 +896,14 @@ Are you sure you want to remove 1 page from the selected document '%0'? Ви впевнені, що хочете видалити 1 сторінку з документа '%0'? + + Loading scene (%1/%2) + + + + Moving cached scenes (%1/%2) + + UBApplication @@ -1234,6 +1242,10 @@ Title page + + Refreshing Document Thumbnails View (%1/%2) + + UBDocumentManager @@ -1284,6 +1296,10 @@ Page %0 Сторінка %0 + + Generating thumbnails for board (%1/%2) + + UBDocumentReplaceDialog @@ -1979,6 +1995,10 @@ Do you want to ignore these errors for this host? OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. + + Renaming pages (%1/%2) + + UBPlatformUtils @@ -2142,6 +2162,10 @@ Do you want to ignore these errors for this host? %1 thumbnails generated ... згенеровані ескізи %1... + + Loading thumbnail (%1/%2) + + UBThumbnailTextItem diff --git a/resources/i18n/OpenBoard_zh.ts b/resources/i18n/OpenBoard_zh.ts index 2621530a..f222bad2 100644 --- a/resources/i18n/OpenBoard_zh.ts +++ b/resources/i18n/OpenBoard_zh.ts @@ -896,6 +896,14 @@ Are you sure you want to remove 1 page from the selected document '%0'? 确定要删除选中文件“%0”中的1页? + + Loading scene (%1/%2) + + + + Moving cached scenes (%1/%2) + + UBApplication @@ -1224,6 +1232,10 @@ Title page + + Refreshing Document Thumbnails View (%1/%2) + + UBDocumentManager @@ -1274,6 +1286,10 @@ Page %0 页面 %0 + + Generating thumbnails for board (%1/%2) + + UBDocumentReplaceDialog @@ -1958,6 +1974,10 @@ Do you want to ignore these errors for this host? OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. + + Renaming pages (%1/%2) + + UBPlatformUtils @@ -2121,6 +2141,10 @@ Do you want to ignore these errors for this host? %1 thumbnails generated ... 已为%1生成缩略图…… + + Loading thumbnail (%1/%2) + + UBThumbnailTextItem diff --git a/resources/i18n/OpenBoard_zh_CN.ts b/resources/i18n/OpenBoard_zh_CN.ts index 2621530a..f222bad2 100644 --- a/resources/i18n/OpenBoard_zh_CN.ts +++ b/resources/i18n/OpenBoard_zh_CN.ts @@ -896,6 +896,14 @@ Are you sure you want to remove 1 page from the selected document '%0'? 确定要删除选中文件“%0”中的1页? + + Loading scene (%1/%2) + + + + Moving cached scenes (%1/%2) + + UBApplication @@ -1224,6 +1232,10 @@ Title page + + Refreshing Document Thumbnails View (%1/%2) + + UBDocumentManager @@ -1274,6 +1286,10 @@ Page %0 页面 %0 + + Generating thumbnails for board (%1/%2) + + UBDocumentReplaceDialog @@ -1958,6 +1974,10 @@ Do you want to ignore these errors for this host? OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. + + Renaming pages (%1/%2) + + UBPlatformUtils @@ -2121,6 +2141,10 @@ Do you want to ignore these errors for this host? %1 thumbnails generated ... 已为%1生成缩略图…… + + Loading thumbnail (%1/%2) + + UBThumbnailTextItem diff --git a/resources/i18n/OpenBoard_zh_TW.ts b/resources/i18n/OpenBoard_zh_TW.ts index 8ce86b8c..a55735fa 100644 --- a/resources/i18n/OpenBoard_zh_TW.ts +++ b/resources/i18n/OpenBoard_zh_TW.ts @@ -896,6 +896,14 @@ Are you sure you want to remove 1 page from the selected document '%0'? 確定要移除所選文件 '%0' 的一個頁面? + + Loading scene (%1/%2) + + + + Moving cached scenes (%1/%2) + + UBApplication @@ -1224,6 +1232,10 @@ Title page 標題頁 + + Refreshing Document Thumbnails View (%1/%2) + + UBDocumentManager @@ -1274,6 +1286,10 @@ Page %0 第 %0 頁 + + Generating thumbnails for board (%1/%2) + + UBDocumentPublisher @@ -1959,6 +1975,10 @@ Do you want to ignore these errors for this host? OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. + + Renaming pages (%1/%2) + + UBPlatformUtils @@ -2115,6 +2135,10 @@ Do you want to ignore these errors for this host? %1 thumbnails generated ... 已產生縮圖 %1... + + Loading thumbnail (%1/%2) + + UBThumbnailTextItem diff --git a/src/adaptors/UBThumbnailAdaptor.cpp b/src/adaptors/UBThumbnailAdaptor.cpp index 0dedef4f..f01cc27b 100644 --- a/src/adaptors/UBThumbnailAdaptor.cpp +++ b/src/adaptors/UBThumbnailAdaptor.cpp @@ -54,7 +54,6 @@ void UBThumbnailAdaptor::generateMissingThumbnails(UBDocumentProxy* proxy) for (int iPageNo = 0; iPageNo < existingPageCount; ++iPageNo) { - UBApplication::showMessage(tr("check generateMissingThumbnails (%1/%2)").arg(iPageNo).arg(existingPageCount)); QString thumbFileName = proxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.thumbnail.jpg", iPageNo); QFile thumbFile(thumbFileName); diff --git a/src/core/UBPersistenceManager.cpp b/src/core/UBPersistenceManager.cpp index 2aec77db..b2e26cca 100644 --- a/src/core/UBPersistenceManager.cpp +++ b/src/core/UBPersistenceManager.cpp @@ -976,7 +976,7 @@ UBDocumentProxy* UBPersistenceManager::persistDocumentMetadata(UBDocumentProxy* void UBPersistenceManager::renamePage(UBDocumentProxy* pDocumentProxy, const int sourceIndex, const int targetIndex) { - UBApplication::showMessage(tr("renaming pages (%1/%2)").arg(sourceIndex).arg(pDocumentProxy->pageCount())); + 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)); diff --git a/src/core/UBSceneCache.cpp b/src/core/UBSceneCache.cpp index a3c92bc6..f449c57a 100644 --- a/src/core/UBSceneCache.cpp +++ b/src/core/UBSceneCache.cpp @@ -222,7 +222,7 @@ void UBSceneCache::shiftUpScenes(UBDocumentProxy* proxy, int startIncIndex, int { for(int i = endIncIndex; i >= startIncIndex; i--) { - UBApplication::showMessage(QObject::tr("moving cached scenes (%1/%2)").arg(i).arg(endIncIndex)); + UBApplication::showMessage(QObject::tr("Moving cached scenes (%1/%2)").arg(i).arg(endIncIndex)); internalMoveScene(proxy, i, i + 1); } } diff --git a/src/gui/UBDocumentNavigator.cpp b/src/gui/UBDocumentNavigator.cpp index 345afcdd..73bf9724 100644 --- a/src/gui/UBDocumentNavigator.cpp +++ b/src/gui/UBDocumentNavigator.cpp @@ -136,7 +136,7 @@ void UBDocumentNavigator::generateThumbnails(UBDocumentContainer* source) for(int i = 0; i < source->selectedDocument()->pageCount(); i++) { - UBApplication::showMessage(tr("generating thumbnails for board (%1/%2)").arg(i+1).arg(source->selectedDocument()->pageCount())); + UBApplication::showMessage(tr("Generating thumbnails for board (%1/%2)").arg(i+1).arg(source->selectedDocument()->pageCount())); bool found = false; if (UBApplication::documentController) From e9eeffd807d61eaf5db60c7b548246534be9ac26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Wed, 23 Feb 2022 15:51:34 +0100 Subject: [PATCH 072/130] fixed missing ubz icon in linux (xdg-icon-resource does not support svg yet so ... to improve) --- release_scripts/linux/package.sh | 4 ++-- resources/linux/application-ubz.png | Bin 0 -> 81588 bytes resources/linux/application-ubz.svg | 36 ++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 resources/linux/application-ubz.png create mode 100644 resources/linux/application-ubz.svg diff --git a/release_scripts/linux/package.sh b/release_scripts/linux/package.sh index c53e3081..ce2e8f70 100755 --- a/release_scripts/linux/package.sh +++ b/release_scripts/linux/package.sh @@ -180,6 +180,7 @@ chown -R root:root $PACKAGE_DIRECTORY cp -R resources/customizations $PACKAGE_DIRECTORY/ cp resources/linux/openboard-ubz.xml $PACKAGE_DIRECTORY/etc/ +cp resources/linux/application-ubz.png $PACKAGE_DIRECTORY/etc/ if $BUNDLE_QT; then cp -R resources/linux/run.sh $PACKAGE_DIRECTORY/ @@ -287,6 +288,7 @@ cat > "$BASE_WORKING_DIR/DEBIAN/postinst" << EOF xdg-desktop-menu install --novendor /usr/share/applications/${APPLICATION_CODE}.desktop xdg-mime install --mode system /$APPLICATION_PATH/$APPLICATION_CODE/etc/openboard-ubz.xml xdg-mime default /usr/share/applications/${APPLICATION_CODE}.desktop application/ubz +xdg-icon-resource install --context mimetypes --size 48 /$APPLICATION_PATH/$APPLICATION_CODE/etc/application-ubz.png application-ubz ln -s $SYMLINK_TARGET /usr/bin/$APPLICATION_CODE @@ -400,8 +402,6 @@ echo "MimeType=application/ubz" >> $APPLICATION_SHORTCUT echo "Categories=Education;" >> $APPLICATION_SHORTCUT cp "resources/images/${APPLICATION_NAME}.png" "$PACKAGE_DIRECTORY/${APPLICATION_NAME}.png" - - # ---------------------------------------------------------------------------- # Building the package # ---------------------------------------------------------------------------- diff --git a/resources/linux/application-ubz.png b/resources/linux/application-ubz.png new file mode 100644 index 0000000000000000000000000000000000000000..6eff7417eab4fda1a0c46dfdf283f4fb4c46d74f GIT binary patch literal 81588 zcmZ6y2Rv2(A3uKXwfD-FRT&9gGkc{}NEBt~lD$L5<(9owgp5lxW$$&VgsiJ<7q>FQ zwcU$r{Lc0H{vN-_|1XaU*L|P+KCk!d^;|dJ#P~WBJs&*)08BRwu9^Y>CHP-TfR-Bk zC*rJBB>5|69U~n8s7z%zd`Ja;Eetm>H3EPTF#teD0l)$HE#wja1Y8DyWqSZn%>n>! zpWLQ9>fjHk9d2E}3Y?Jt6tup72|g0IaaHH;lgZWA;4F`O0mKsx#hu5C&lxh+go~ie zaTHhB7w1Oee9yzY9UOK7o)qzPE%BRhT!84)(RLUds<;oc7QML!OOcR>Lo#wZ39ii# zz=JIE4vy{w5vN<%CWk(n+1#7PVYYY7?k%oXzxQuy9t|q5Rnn#viDK3L|NNEqMSLZI zhL&w3`+OAR#ZucKB>4dyG|AGx81|;vbh{r*2$H{3yfVmoP9B1i*QJ$YNuVM>jU*+L z{4b`Eee2C^u_%|m3q`>)$rjX0 z!bZX8yE3gXBgsg9xIZPIQ!$4whD8Gkevy~0P}b{__yzzkRgR(Xi+;l3$3M5rA-dfj z_G#wxy&qEl`_b>gcF~WH@m-0wFHK`ubwhN@Ev7;}hs{{YOHniOS)6|*$9OonD0cuZ zkta741!iiL9W^Pbu| zlMn^K{^4{-SbmdCvN=v+c-o z;sid^>u#15p;;1@mR-zwg-cadYw@W&e3He+YPfy9cwpG?qZ9d;Ohpww#ijC4*vKy> zf6;>e-Z(;))*tzw2A+qBUR`>U zy<40vY5I!1e`j^|68GYa{$@%pt^iz`JA-;eVhC$g+JI~AWT53Mr#p*QrK7_nb+Dr{4y!oDZo94Rp@k)4CXEJ}#RKS=G)~n%93|@;gfg#v8@uXRy_(yO1 zpOmmsdOX!W7El;&cOI)uuX3V?o}mECFHOb_EII?6>F{2?#qLV0AfaPs~@7J;w zyv0~BE)-WejwUVBCrds?o0lX5Je9q}ZZs#&J0onbQAcmS4#daD=aKA!6!?3J(EE!q_h+zTVb#UtBQ!wZFo2S|8&HlWP z3x!vHkL#ETUO`!=koS?QkOue}XSDIY^)>Muz@-IlWDp_ba#SCe!t8Vl&DR*un|YRv zatC$`>QM)_9oocxBM%KU>5uB-Mdu!4W8<}~+}tU3O--~bxJBD*)6L3W(@nd_r1iFw znH+EQKKgd})=T0lxNSm{y4n=&d&k5De~$2S2&!ItVuq@6(X5vZyo~bz74U|w`e0q| z_$}aby_`m`Tllve`t8GU>H7Vw;|#@4Ig-qGrRqNFIs(G#Lnhw*q!X!t?OW|?4h0y+v6w#y`a1f$Zld8 z90t|t*&ml9pXyC*2-2m)<4@)n&qHSy2H=)p$WkHh)lqbWi2-kr^1_Fgs-#ja{bf9H zQKl{eJsv^|6Q8XscdfPjaQ7k&Z$afQ7c9J2H>X67#8w?5yVy69FLu9y@pwFpuFSvx zk^dN2J6XOgCgZe83?fh)8eosotcjo&qg05 zbVXjO){=yS#T$MCE~?})Q8iCcTWgr@^bVWlZ#%}E9AapGa#4r@tNK=FF8`sZlK?sx z{#T)zPr(JFD1rWsu@qj2yt z?y5XiuVo~6y2V03+e7uiQGLY4E5g9kucjWH}4}c?!u34>xydCR8$&67YFj%W@=oA0@@DeWh1tZiESsH;C0xzBtk+jYkVn~ zddK<~@tJ2Z-U5gQ!2Rr?paLAs9`EABfvI2QwXlOp3vU7p2+gtLV>!RLrH#=O9-kf@ zR$t0_#Gg_WlMewQ&tDjpe+~@$u@;hj-^9IFDK2qGTrm|X#VCA@k}e)7Lw8qB;2#h_ z)q*zJQfH_|B78+oJ?<%Mtqf z>QMVlu!pAMjw`dD!$*Zo$ zLIK!b9ZA02Z`Y0k(zim3e4C@}jTZTyF}~|G;#|`}FwP8(d_T&z{N8)r_n*?|+CQK4 z5@Q3;Zug@w1_``Y%hr3HT>ho9BG&JTQtwX6_LDu!yIXi`LPr=PtqI?iWimz9a@4jb zl*Ej32#qFc1VzH;&9`#*>es^q<*rX2jvsB7j=PV!&BotQ{g8|u$=49K)LPvRJVHA@ ztvU&`0R}ykh@N1l>T28X_eO7{iLL82#F@}_Js_|M&pX)h^y)J+o4}T)6jzi@mjgH` z!6^-;2Rc|+j@K=Z#wd{&tm-@(3&$wVIi_v!mb9``tDcQq$-M+mgi2=b*ZM?{j~vK# zt*E`@-T##}x*$`-p#8z9|3i??``!9DVd0f=l(PNUJENR%AAIdtI3DjELAvJ>O0{}5 zwE2ryFqH`{d7__@09f_dh!OpJ%35G^HUoq^k}*T)MhAt?+-Xm`Ls0o{up2Di5e402 z-_200Gtsi%F5n%m^mIchLWIREkhf^#G`vynwU*u6VW04is=)ygAG+R3oRQKz*~gr8 zaice)u@H|~-2ZCRRP+oG2lnDD`0JInTfqIMfR$0Z-4J^+0HE7Q80MEV32i-#5~Gj$ zDpjMh-T1eQUK*S@*6Ul8w82sxA=h1I6WMsJV_I$%S7Mr9Fz~Upx8P1Dr@-e)EpJ|H zD$fmG$UdR4=byBNa9sJ#SrAMSm2@&J067BztENv9e7VA!$GAm^a6}T|V^dCZ-Jq?g z@hCRwv6et+t{*U3%K%1*oLgH*@7XXl<&qc=1*_G(=zYexJ1xR4q4rx|y&cGe}9CTUjN z1t~@u)dHACA&(ED$+x-*9DvY{=9AvllR8oaX97^+iLG-F)wrU2@YFbvW2tihC~Rta zZYBJkBwTwmTvaJZv-SGT=00rpHf+aCAJUwJQ4@a0^l!ksLaX^Jv~TfIwTkrAmjPaK zp-)Gg9G&cbmkr+>LLIF-ds9_V(XdmSp4ONulKis2pODmnPvb&R^0Ib2AwE%z?3S+% ztC&t~JNHNjcZQR3$JRAMc{~KkdFySv4r#4g8@&H2Gc{o`D;KbQ(AYiEEA z_Fkl^z}&hGH5)x<1VH@OFKtz4z1fICtl4RTxNIhhq7cGV(+W|56ns7ejy7YTf`e6( zjvk0_T8N9yoNX0tA^hVJzP1dGko$Vyaoi3%BXTAPU>(ObFpF7LX0Gx!Kni zSbB!X^eeQuQYJYJ+i!=R){cRKxKZ*;yT{opDk`o$hBN*c4ZHolF0JEo+%cg(BC+ne zHU*6?g(@dh0t&91y=wvpBV~XncHrReNeMXetfyDLu>DHib2#cJdDW}Ew53K_i!avB2z zEsjavc72z7>SEai7|vEK{M9UNX$$7=i2}T#`>1k7swqWXsM7#T$KF@Xg?vps}5-ubp3GS-Zy`YlqkO_9#Yr7Z#yi|)@L zp1HvWT=YorZ~aL>f?Z8^ zDvpo>L014F6y%K`0yjR79;hR=?EnRUwgHOmrmixFr+XK?Y&ZFv@~bL+WV+@pme{mE zHjXl3{%4wGzV~Wd{mB#r2m+1jP>-?j(p=9e?BYD^rIvjJB9siaH?>Z-wb~flDF6`t z)BdJskZ3Xk^vO7b;>O#tv%&}?^WWB7zS1JavBjNnnb8Zrtn~0f>rHqkr@y*W{@kXC z-CP5_^XI?TG^qZaN_`Gh>wHSF2SUKziy4t$FDTskhVBe2yL)kHrruKeZpp~Ust2zM zElZ7U%s$?IH;Ik#+F2?}4WCs82iWZs)X8>)F+~M?J1aBokfkR76V2NzRZ2!@r!Rrm zQ+$0xh-Whx7`KaBl{G2cJNYO8s0FST&;TOzuNgvD69~el*M5BiyH=8(inG%Nr6eZl zxQ{eGvCJJ$6!v;z56Zt!k|qhYg-*v~eoz+?7C%nASM@7Z76(!pJ;MoYKd6A3m%!v~ z^)_XedrtpChjjgv-;WZzqvhK`gAjfSc&)76&43hvkc;r_4j?@J>@T~>^Ym7~=~S1J zpCa{7LIoAzcB6Ssa_cM3hHa+HFchL>3r%q7x?nMi*ymEs=8)!&Y^_@F855?G!Shz= z3r22I54J$(o_rcv9`x-xOY8)3?MchdJ|^P$2Ji~_P$X^a{?OBMDd&j3YjMz*ybJ5U zwWMDxLs(ye!=IwzaaYP&E&MJ($Ey&+hQhJH!FDc(&R88ZhUV>*bO*qGaGG^y|B^*1 zUzK=j_sWUp!r!6gpm;zt6#x+%@UC<#ry-owvU%omSAWSSdj-i!2s~0ag5BX??!}fP zy!Q!94F~0#K9)nclV3O>6FG6zdy`hg(Hul%kM9BFjsD_PfUZEt=lhs+9W@Du$Vcac zs&OJe3;UYPI$y{g4_5%057(uYjqwE5GyRzOSGo{9h^3PyxR_0>VL%ih4*fpxqr_li`(}Y$qt& zE3xjha3Xs2xIa&a??suP)9!n$gWf6^0=)&xrzopzXrT^!_Vmllo7>H{Qr^~w^QqHf zNXb-6Y*wZf_06Z+CG{h2g+E>FG5H$x5&OM%;aITmZQ4P{Qz)=hZqWZuLN)sWAPhUh zmLMU+COXI%uIAO`p-eEbJ7BUvt!8;+wH2~wopM`0O9gz^EWFxlk6Q`8IiYFN%l2FD zSTBIT$g1<0wD4W&*hob!r|oFSL+SbA{#`;us57LJqJ7x8S`E2sQEL9JC3rg}jC>6f zT1P`#t*q_zH9>SAzOg7IRfoI9u#{u;Cr6^1gu>5r%MbmzlwjE?g;cpZ_;Y)1N z$2;tt&eKX2rBp3THOscHW-90&{JfIP>--);)(2e?hh5yjcv6iGv_TJK2Wd}Z1cBm9FFtcLCXC+sMpXq5 zYA((GP@Z#b%aB+yCiTfFx!M4y803}N8~Xg%wQQt}=rj;5kfV@mQ}Xj8zGuWSJ3~{9 z_|$u*uQID69KGe#=jxI%{TbQCcS1n;B*R8)?MC55J<`#rI#A==!x;g5>OxA|m72d- zhcb3b3HQnkyD2iVuvjpwtyYYM&+_B;mNvVDBF-T92U`K~GHZyye^>ei#0?;36V|T_ zk@|*jNy<2W zYl(q*3af8>+dR9Q4=!Qr^q_sUBW|2}dU`x(&muahxUDzhGeKULN?ifqOQCqmdZZwG zK{RbQG46^n87y8o19T^`9mru{S-Fl z9%lE=vt!mv&AdG?o~;D0UUiiC#!>KjvC+k@#(8+4tzh?u(y)ynHDPyhs&_@f_se0H zI^^{j@@PiWynO7r(*&#>fp1$)Mnl2TJnJ0sjg)QeKwg|PIDy`P*t zY&{o#Uligi;Sv;X6SFgr~dbJdq(=W!C zObvFufs%6cL7sPTuN9iko*#K*Nd@x%N(kiE-&{J|6z=v6JQuOJ-5Yn5s?QfKGb=Ba z*=~q`AxwIEf$H(@gKeI{&go>|jwe)m1Gk8ztdO~qGd!!luQNhaQp*nT_rsgd^3t1> znvdLzcwifIW_Tq4`~)!%)$Opq9GK?4zlnBGd)~=>;9kRbAJla~0OU&K4dIQ10+oNb zIdmV<+PymQ;%$)UJZ9w>Nc+1hTuy9E41%LQd^;Ckd1da7eEi;)dG_M(5@n|@YL!Lw zWx7UML30zOX^2o}{;t3xv65Kt$jaW7-?|`rs z#8lA!?x8C{{CChFQPP8G=hwNgiRE4&y0J+=giNUQCbMoeE{n2O$#a1jwi=R&udhDk*u-V0X;UV4YNnY4Kn z&1k;sj~AnMsXW>WpMXCPXsoMN2*?tEET~lY(>Okec~l*o=Hgi9slW$rNc%G?220|U zKSp0R><@!1~i-hhZmP>2nCfBc~m+#BZ5LiLPdy^X^1_4xzAp$}oqNS7CRW zZ4;#AE@`LqD|&5PmW)(5^>Tf-s$aW@+aCy;Nv@;4{On&m1c9YAr38u8uS^i?MWw04 zVR$Y+bJu!O+JAS}`j(e-{mQLN%0nGIyOl?m&Rx}8(_|egTMr(rlNmN&#uMgxGzV>? ziKQFvKaL$^BK~nY@E@20X@U^HSP1EUsd>&8>v)6jxzREkQo?$~32B|eM*_U@5-?$H zHW1kwpmf2Z&WXR3SEpN4`gO0TX-WrPR^3Zl9_?{~4-JsYf<|WfQp*iLd=8u$xo&~8 zKDym1RH!n1z}nhQpMr{Z_&UqU1`BTYcO=UUmAw#>n)y7{ zaJaB-HcuM3=eI%S8k_@wU+u4XIUI}Es#CH>s5b`({qJ7$@_4$};@U~Rc=>R%WLsQr zLmhY0ZciF2arKPwNcWL9?R*j|^k^@esRtvYGpkFmB?zh<>V zjKO9Hs+Tm)f%vaI8B!7JZO1EZ18U13ibl`%a{z;_kR>`$?Q*;Zs+hDOoUOEImI|M? zC_}~L-+QJveigGG`yuywlV{mVqPn&B{xG5S_P@4r{29m>#eut(66Oj%jmNPE4{`A3 zT;G0W>K1QqNT{Zxjb+x;OWw=X)5mXpely&9F{>O5QSzQix>odbzBg0rpFfH;bv;$< zc<&q}>5VjW{0Br&6nS(JM@k`URA)hQ&8jOZ>F+e7r0u};C47gGmUmqyvR}|UX_Mcq ztt4#Ot+Bj35QQk)E_2u0Bo_LOmfLOnM8pKe@pT zA%=f$%*fVIqSzv^@%`reM=Yt*cAc*tI(e&^*PzoqaFU*GQg57QSS(tn-O7i8jZ5X7 zR+V16gTRKga7X^b!50c0!u6G+zyamsyWeaG-x{(=3zD_A59+-yP5P=8+VRPiNoO^FMrXUWWzv6%^%QXtr{(+PZ#4q6ls5(5>eC zU8dxCoS0W#;`{Yq(KF|%(xo;FaTL;lxV3t`XF;+}^FYTDcGsOa zx#rz!+XZ6O3h>~8{a!zj>uk_W@FHTBEaSuj3q@*w0Q4-_*A3Xo&Gr0qIX{TYvzy8- zY#xtHS*4nBpyBrJvl!oAi_+YuHn99OyBbD%HH#Y#Z+*L8$G>g8&lnb}IrpR(Qu?s` zv-|cUL;T=ctA?$G%QHiE7t~l3WoUlbykXo|&|7-nLcf4PMa(^L&I{O-vfWI$PH`Uz z4qXn4xzMWuEmt1(iBo17!Nh5RdLN-FoxiZ}0F`Pfp3Y_z&M` zxaGoZI=x{db6ANm+_m4xyEQG{P81lg+%1$t6Rh7n&6W35vxWKXl@E^M-yfy!kU z5-dy2D?^E1t00nF-dkE)DvUT9i$H9WeIzZqc3nf9=w+u8?fdJEr-bU zOH(a15S-wql#P%T9FQI*LkF$qTpM#SsDhKxuXfPaSX2S2BmV9OEYZ( zCvwQvf)FqcDS!xHS9^{MruxfH`E_$5fIJyC$urLeSOO{ZKbo+=FH~&L>mjm_R^E{! zWV^VqZG$Q(3D(e7&-Ql%vF_rLCUy$2TYTRoiB~z!dEB|W#?Gp?{%abis7$DQRQ<32 zK>_c` zd=wj*-uP?zXZ}Z%4>3{#+G2D555*}A^W;mzdAaP{Ue~SKK-_BiKvyT`S`kb-sC{E& z%xQ&l2*FtCfHlD@9 z|Nafd^ZsUdI@t3rf$vvE#%_>X5{Z)9!ocL`1|?P+w-)Q3qiNBd+`Ck6USj-IUw#tN z*|NeHI-q-lvNq5o5@$^75>Ek_4f&ZkK8FakkR=1<5dpOCKGXic%U>k-fa3F} z$&zD%&anC34 Zc1hLgkL}`*)1UX1)5?4^mA6F1uQ#_@gHiTuDYbxYAX~zvzPPAA}k8w8dm6=yWz7#0+xZ8Ov0&+voZA zW{Lav@Jg7SgBt#pn_8J4Olml9;OTmA@J*6_a_8X1hlY(=!6PZyYdKPPP<2|3-d+yt zK|MT6Mzl(k?Ol>5D=q44)fm@(yjk0wUTvHOl{oxdLk+Z897)k*s^WChkZM$7#9 zwtEUNobWd#4+wwn+OUanx*%!|z>7H|=P@+*7gs)}Dl9>}Bi`>M4z z<8L3rPIcOUutR5^rq`4{VPP#k?CmCEUbq0HnqE^m+JrjTGeG*?`bW4yX}y{1kI&wj zu*ac_V?nZ}>+a&)FG5Q5qNjKsS>l`=l++?aT@Ic4Jl~IX5ya;#%Ug+g${kP4&g60T zYgjjZYc5kJ+N_BRuP{3OW2xn6vdt0l&ue8zsm2C=)qn%N$KC1>ORk7g@4EIs*!cDg ztZx3$3+QAy=ybu<*a{?3iz=`C#X7sXe7i+r?SH(uVAh52K=wb9Z(DfnK7at#kYhT) zYS5T*y!}f&P+N>NWHbOv5N>JM`z!x z9(SsX%g@iV4Y6Rm>FK!9oIm9b#+h_=Uq}sT5?aM*_o{wp;=jD9<*at|YR<{AFlxEn zg|B?e^S92uNn;V#Hmh%bqS&`**k;uFYl#pq4)Z!JTl}`TRem(?gwF^q^};f=n|!FJ*N1F%%G$M-rRr4qT=@N^seJB9VUSNT#W zl^n}BC$m@KcOWu>t9eOAd^PCHapR0fFhmd?Lzy*DB|X+tvmp8A!h0#X>vdW7%{A`_ zZY0nyP1@*kn1sc&N~gK_?x8hfuF5qt8@X)LQI(~|yVWMBV=6(rOY)AkBuZlemb$+} zwsMV54b5Vj)71rkV{z905Ar38bLz_5^jDO31737}DTBXTxalr6!ha6*QS%in2cRhD zsK6N|FSGCLC>>9ZPZlA1S?*zPQj_+BGFXD$qA97cR>HlLR?yLv0Cg~LVfH4P4Z2#D zag$0s|H)rF#C8rYjE2mibjaKS%i))7gPS-c6^{z##WEN1WcZ z+Lmkb;kD-xleMqio7`pQTAF%XL?rwIY(W3?ncD>=Z2{2A%pPTtgn~E&o}#jl0s2%yC(P}Ir!U?y22{*xMdXvbcd<^w z8Ty7*UH6Fxatnn9YL?wm!9uesxPOP=qg*@0L?35tUZ`c*#e^r*J+0{NaeM+p?CyT& zjFnyN<_>%lZMYXjm+8B<9I3vh_Gl3p)P;aLMmjh0v)&pjSP&KAM+cdpihGC&UD-Ww z6JUiHR!yhT+6zSnFtd?$g}ytdvz}(pT*hp9Fp1yOT`{>T$sZLOxSD0kxMxz;T-9my zC^&Odww#EkWH&Q@!#f9SP`^|&eWtiDhwt4Vj*D~tdCz*sQ+Hqa@}3Z!gYB$mfcB^7 zW94L@o+aWulqx|~8_5_E8hUNJ=~4gdMZ4hn3?Tu2ek%(5Gn=Boe`F6dNx_3eVGZk+ z9BcSnWFxhSo-K29+M3MTLSkDr#U6hdsmn_muszFUv*_1ar`3BTK|ym!IW#oR$|dOG8>5~L-fSpm#wNBq^>zMb_!3uodwa=X zw`_yA9qm^+TV1X=Lsg{_%1EeAtI{aE#hB*gu?`7k&>MSVL!8_V`+@GBC5$_)JumSx zvuL%wUOmx4z+>OroNI-6+_9hU$+&&gV}bTNrW4Qgp#-@yr|bWtDcO4D^TaC0)Nj-L z`7%ZPt5S{OS6rd%^f?l8g!DSSZSnHT`aO!tvZJ~?Sl4mA1e&;9wNe?i8-;k-@2x8m zs+iXzr6VgL*K}tjWFB@eI9Vzqm-uh-eT70y|U818Vop!JPWX>#FU?ck+6!y&hMlH znMH}tq4qMpZO1|LuX_8;4!TsPou$!l_ZBWFWyYv4va{D*20X26Ety`vd^rxX9i44F z1I_(9kBK7L#<>>^sIBz(>H_WRkFQg1oFx;Z#8aev-rzIj-nApxUebnKom{#gYt`{M z{)~+pmCG2Zj&cWo<@a(kM(S${o=8XWT0C;0_7C=vYyh)fFolEl0b-wft`=4Z6~Ry% zQGPHl0YArEf~)zw3NFwnXeDw8> zV&M8ria1$6u{r!?(Yjm-0;RiYb@znR|6_HIufSgVm=l%rw#52F{u_J_rn1CDQtCw^ z=Q|T2gg^UDFHW=il4FxLM9IT4Rb9Amvb0m!N2z%9fiC=#>3i=>`m_1X`UyI&7>iFo z`ADI%7E_4MTzsG>fAM6UkN)D9`D-Y@+cs^=roZEYufZcS9Tvu0#-{$_RbD-NYP~Nj zhk{{DZjMJ9)mkQ_J2}63Yw5`?A{oUmZ=c6t*TMMI-xge8aHuojALik)x*?BI--ee* z9Ic=Ldyz>|B<%PtEim5-lKpdkuYh;+0p-c~%f4+}Qf1rYyFT_8zJ`~+X^sB*3ru;S z0Gj4soqP&a9;>yMD!9sY&F_CdSy?@&nEFvN*YYEydb~1Fm22Xd4I}90RZ|R zaRleX*JoNm@rj8`#`;5@ljdS@ig}6Rz54y!MHLX!DBFSIfe6I${^AK`dmz9Bs32eM zCLEO$Skf*22YItE3{oKbXq`$cI&E{B*hiB&%`K*7>VJ&~CqAf@2~~$l9l>Uxg|N;Y z4Fwcz?A;TmsO5lQ=cqDYUejr7{JOXUWk$NHsrnt$4c;!8sQxmp*#v4R|^3(J~9MiWOn?CW1Yc^edT#1_NZ_fO=cZ*db zi1TtxlPgx~cWg{d1(

!~kO&&qb)^YV4mpdGeFjId}6pn1(yTP*+fDpY;_*icz=! zN87qD{m-|t%tA^vH@jz4d;lHSS<8>fbnBP*7;%FEmi#h7fnzsA?}SaPn)P&9=iE#R zMN1VIb&n?!)Xr!Yo*nvog3#6JTQ6SU9>`OBAI#{2Nwwb3w4!8YV(O3h2j&_J!5{>Y z3U~$O`TFHgu(q|=|D{R1KBp5rm2TdY7KUwshIKw!Rs0xy-Slf>v3sjWTO1^Z@68!_ z`lSE42!~cNLp1O}EFGccWQ&m6v_zTdo%0ww$np`qAtC!LuvEp1e;e$(pzEsjp@qxO z@q~v>I}TbT`{WCQrq7>f?)(%AQDm#W@Tn1cN{}3>r<8Za^nU?Rxt{B|Wp`-brfp@*FwsJx`Id0R^ zQxk7zo%{ju1od1Rq$pV@)%g!Qoq-X;HfPXy&cy_YG>7w@rg!f@Ctl@iD06(0C$_zf zVD3-6ZQ@7Ahpx;a)}`5}syssLLE|w;whWMs{&>cqG(^U18iF=BufgH4_V~y7@LEfJ z!8A~u8U5KT1TbJL0M!#?mHG6bFfe?JU_#Dcxlg%qf#Q@6`ZLl4#VZZ$p^f1++gqv+ zye0B~jXe60I(gsPZbxr+XxS+H<=~zTJh@dV@H5PAPcOi2o8JwiD2_%)EnIngg23Eg z%HPlDkQSWafzy9Kb~I?g8Cj64xBGuHa2?VNC6rugGS9vbhQpl&PGygOPIdf1$M}zlHo>1kbo@$qo733oeE7 zKq?;l`E7}}3JQx%pAYC&#fJzBRIkY;IA0Xf_F%6Izl!xOe5pDIEB#y|J*M8&LpoUP zZCT$w|6@DswE;W7AFI4pnS0`bq0jD}g&LF$;{`Bry-JQi1OrNtEMO2A+&a__*7Ceq`_Cu@M1)a0 zOknyVDvf!gQHDh8#F{CU)vDA4H}`4=Baf~)H71cbo65|5TA9qP^n1iNaHW8TmODM& z&(oUl4&tIokA$1mAr1@T3)#t=cL!UTi%D&gAmk!9Ac<7j(zdcWWT)EShS?O-wp3Ze zUvG#2)ShRDtkF1}a+oHh=i>wRT-R%FO3-PU+8!v~$4xh9SfX@kRX@s$JuGVF^WdMf z^ay!gDg^KPyJfN&&n(%Y`LeR?q--TjhJ;qK`NM>srqg|>UIvT$n(0A20*xot6PfM} zMrmKTODytSs`?funY`c_ew%>&p(YARP8K?*{I|S8(jUPUeb(j+`H5AGz2R~@&5AU9 zuOa_9ymdE^oe2Hb<)w$cXtx*-aVvpNT1Ip!BfOQ`daSFj9K&%go%Ux-j5@?xO{hBr z)P}ayyY}{;_^nZ)Xu%K?=v=#xl_CC+dv3&sv6Yga%)PfjAP%n>5cx}P{hBdk{Ph@u z+AI@Emcz}g9{qN+JOA9K{082_kSDf0eC%S|!dcT!u4jc;gUagW6-D&wyQlX31UmX^ z#XQoZANUe^|5!fL`zKN`Vg|bPXO*?UHA4xj@$`_5V6JgG>WpbfMab^*q76t{7Rzi~ zLo}Gu&g17sW)=@AYyWYw5eK9u?K#MoK@Es%abTX@U$IM> zqU|EF0)CTZ?vAPl-vV%MkPD=5Gxyor!!yHtB0%IlkpaDq-IBfnIR#Y$_8|1^i?_72&n{_j_vVYE`d{>h7>v zD#c}%X*Nh08TRf#!04UDCL*{J7i+HSXd_u!RwZjGWHavwHXxsoKO|kS^!9r8CX+P; zXY*IAlD9I5{n_isF)7h&clxZOmg%RpgkVyE?4F@Y*ulC~cd3+0qA)@5J73g0EwoDN z{t=S~L!t9~j$)D390ohB`AM-f>;e+$#BUgEvH9eUT@UfI1(vak*=%97m^*uye$z(8YfLz@S+%oEazB0(R z>^;DQjGj9s!VBjQDU9SWq4@U>t>%Nre}(NzT)KE=KucQd-!9;sAjfW&kvDbmokoF{ zg_m=P%z=~jq{j1!DN?*Tra2H5=6~<;N=v$uL$9lU8aY>g{jKSp+2yxVXoS%RE&Y5N z5})UQb-<6~s4d2*t4Ud^x?RPE>nrdNn*SQ@XL>QeD8%Oer7iB)kh~6DZNZ)h25Y*z zY0b-RO9`-H@v)N8SNl_^8OciUY^n{Yu6!P|3*Vnt2F9rhAO&;<^LtMh@1d`Eki%gb z&jf*}^sCt=FXSf{!xgJYwRY127D;!o-rFyhqfM_(6B>>6pO1$(sb2v!vhy1|YZJxlK{xqIayBXG#2_NUX3qz2SqHsSxFdco6XbyGM%akM_D#Rf&-*5;f(cQj3Ij;vU<=DF%_Uzq-Cwru!AUAA{3 zrck!;s>9SuVuEoYYl^5#8x5KVVU(M&DY!9jU$HIb@+MCS709kbY68uLrG18Z%Ctq@ zV92#=*O-H+S8RiR=FIH8dk@@?^1BLnnv$&@0~DB)BMV>N{2E}s`j(>@E~o@kbprEU z(T~4DFFyM<^hN?JdsA0gncp`4nTmw zB`B~diX@!+9o>wl{WB!aozuLRrhvEU|}4w+e?@Rb+_U!c)XpXulUZ<$mbm~(;Vb&qTt6%tnx(B3hM!@ zAeTk_A)9NdpvNE-#*DlGLZ{0(Zn!%1_ui`NHE@nmpK1q)XVykLr5Ag)TdjB3-qPCh zTTBu{34O$aL=TPG8(YZpum7UN{)FJBHY{K z1HoOXY2(+t4HzYX?_mOG*0!e<(@`0SmfBNSv$yCCR^G<&LPcdz1}4rdxLmemeO1{ZhHMKfy&~9S=p=Qk3~1kGihsgDxLp<4vMW2#h?u z$k;jU0mUhHhKF5ytZbgBhPP6l9yjY$FhgwY_HwQZE&J0~iY7ifA8S6KH@jKOA&(YS zVRQ)RHcj#&-P;y>9)GMGuy++GO@g(Hi;1<&!)b$ACp<~)6r*mng+s<__|{C@kbgpv zrx;oFRGeV^n=p~;3rUUtxu)DSp**(iFY6EKCYU9wSUe)isb5hD+BXqseN`l^7**gB z0bdS?HzAaiX@}ivCx`M0H9-*z z@~B`=@D(Z=azm_x0kY{}AOx0IAFV#eRZbg-ug&a%-&rhf(9P=RbR~kWmfyd01)+@Y z?wX3sS}`+cfKNTLk@EkDFYYw%8ZJkuu=E?bILT!UkGPEm5PBZ{tW*0JB)iS#@{oTr zg&B>GvEIbX=|==1{+zcIj2^#F99(}|7pncin56RS>&5eRp<%1?JnYYPAMD#8#wIIH zxUxf}G7_3N3)UwHjeDfdH{hF(romHkbDj);vE*d^F(}acf)?;CY6G%LeQ<^zaJ=SQ zdb9hU*n`pCWqF6%s=%YdaF_1B!L7s$sfXz+iMgHv4a<)a|F-k0wR&Zz%Nk$#7mtXO ziZh;fUhUJ^;$507S;=Z)DGN?vGqc`)n|woX$oxLM%aw@BzlAljSg8lChqVk3i(}jI z-R}dnhi82>w5lS??k#2A>pSyMe1RybpXJYHR<7aIoTrY7EL)ru=xx|YcXz@$wzR2> zb=jBoZ(HZFcrMc8vxg33Pu?{ph2V)A3*N6jnhhjQU$@hImR9h-KXVoS)>NgNqJ6}; zdx_Zpc17jx7BH?x_}Y4#?cd37x*siHx3(2-e@sz2b`(_VlN0_LBd;>{ zn%m6n1m1$2s~l96v(~BO+-i4K;Jbq`*bLcBzGW-*5;B<&dGP8Fm`gu?3Fa!W&X6l- z+Ap7`WGFxi{L5%BHJA0Ncfjo?Sns{oyG0d@k(b^wTSVB*u2D;N7M3%kV2Wn55-Z{F z6E2H*a~l;b?HtE24vQ{Y!&&f8ShwtbN4O{1hknz;FIz#H4qp8|D6i$DkMFLE^oPdz z0H5vJj=x*j2G5f^J3E=XAZ*&VXg|dNzc7=*jsHxf^N;jL1YTC|P6mj;`^UWMJ4am^ z5y}Vmie*JC_Plm)btZ6oHgm{Aac=dks=0D9c0zjDgj#o9#7GZzpEC@exldEGqANv$ z!7DAMJt6J)&>U>S=?q_NkCds~&d^j{`nEh$ax%tgVeLEcHJV`z!lU0p3!HYd{L>+F zKMajT{25tR3k0l$Fya=_Lm_tD)wm}hxP)^<-hH}-rl->JO}2JFt?w4 zwGGHYb7z2Em&L28e6RmTM_b6&O@OX*$UdIzC_1G#4Nr@4mimD-8eMyYWiGjMbpD zBoj*+^OdtkW$l_!?NtQqs5W)q?@^W?x|I1XXnF7^{GoZb2+$^XT`<_&f;L1Yr73G0 z8Ux!+K7YQ>lG7TAsFTA*kds1w`IC2BLo|g$mp1-X{ofI(`%_pdc9X4L$uG3>@A`64 zuy@;})1wfXMA~y@VGK^}Ti1MQ7v+6wF&~d!VNkhR7NU*AjW_8$E7qOgn#OZwh0Or4 zc^egFhs8U#E9?-*{q)Q977a$9?XzBUw1;bX&$Pt0o?=^Y*KZ3Z{}a`wa&4y64o;0D z2bDxm?>R!Z=hH`7odQB5Hp)6zQO914bibqE#Tcfz0<@H8kIQYa@f&~H(`zy!8V^va z&Xu!%rFUZoCQxrJ;(DiNcBdNhAL8WC!qb#bWj^`sf8pnzr;n3Dti zbF*T~xMNUWpYyV!W~To{W|{=&o0p4qOSO-7C+lAe&GMCJjC_SyOW_x?rsyjSG-I=z zCo*^jcR$DKLOfWWf&%b8?~tJ!O+u$Kdh-Xs^+g)mKnWpn{#8Gv8S$qLNVALE;w%ST zKux;$WmNZB2hSnkB<=!G)obxdUXw!LLQzv>&zybt4)))6XL z_g&}rcDXP$QWtn>C)9=5TiY8eNfrdufv`8aNx~PBce6H0lnkZDSPtz3;M=!vy~oSt z$IB?PDx!7Piw%flp&9%yvqFB;J2<%oaNOZ`>~%buvpf{$@^aQCuh}!L8tS0D)t?0T zB0bOC)ccu{scm&96JC`p(cFe+JbqodmYBgZO=E__e4kMooP&94!bz<3I3EofwCA$* z>`vqCt)GP)Iyx!mcAAAzyrg?8%{JEq)#bsPmb(=8`l+(C5fjJGkDx3`ILHT2V&$S;xUM+TIxzOGZG z77d6a&wLg3}4MRv1d5hwm+nZhrNeEEJXwWN1W`I()MJkZGdW5JCHpLq#r8?G>F z#E0CYM$?bNnC-}ciL%3A75|5>w+@T4``U(Q1|*aYNhy_(975?7#G6u3kd6UGLK-9n zMy1;vP+Am~?jBGOkPcx8Y3Y<0V21ei;Qf1^=Y8Jy^YG9=uetWM_u6Zn>pai(W;-W8 zK4SfKT~0yZG(&*h_-fZsI6EBay=xx0QL8^8u!b2yc&*WZHskp^BLBM=oVjiCQ>fdU zzERgk_rI|2+n;u8NWiv_W-NWWA^jEPBj`X^#A1#)LKS0;Ve(+UT>e;)q?U#ry!Fk69@myZw8n)D<{)pjP zQvQoBNtTc@ybLYfPQ7fqD4=0rm~AYe0m=Pq~Ys-l>u=zrKDVdVOEAvFB&} zS;=NzA5I{u=`1vOb@Pu^B&D37bAjE$SM-SW+4BzHf3*gTI&SCtLmYiVV~=mis&=wM zKTY#vQXm!6ZZR+*ld~$srs03)r0u$$;M_vOef1%F;P!)_@)<6l*4J&Z)1w&oM4^Fq zye<1!2I2ExnYp>fxcwjThcR_cVP7nN|7lA#bSq24n)~-XOF02`+shbENJ*EZCPY-_ z>DTU;8Bv$alp*F+MVct5ax?$KS?-hg9^g1p2rc4llo*}_|T9;59%<@6eZD%DxsdCv1Jt;1~nZIDL{78L?F2{qTH7ete|} zb*IvUMUo_h?dU99Mk@Q))U(W(Jt12v&H0YXEivFbK_GpXm8IVKSluq^2MOJ>hHjR= ziIOsX?jQjXQG8CsHY?(I6-jwtK|UyVJmzxuLspHCm{7T z*u6bD$|do0eC2VytQIi60` zQHiQoK%M71<(MEB6ei_38i@=OfDpg^Z-$AzKHxNh>;UV*X5vtrlY{%A6?3F-86_Tb zheXjnGW^B%k5ov|Y+v2Apla?Ky|&s*^JduPx{Cg__H?@1^ib zSKQ7)&E(8U6URu5a|a(zK%Iy3{J#<*V%pfaxC=D@O(}VCdNbNHgk1ClZ|IyfHxR^0~z$HH((0A+-yrt+@ zFqG4I!)2@lX)2~g~oE^rbKN+_vQ9!S58;md@B_}v<}V&Pm(C06-r<-vQ@abr6dKNyJhUE*)h z$g$$C`08fdYdPCL-~4*->-jKHAcm1QhsQa#V32t)Eok9#5DOp2Ts-d+-WEpHbL5G9 z#OyD9lNbP4(t@^rv}RqP=@q3XnsEI4ncHF$*{vtf6+&v)SWWz}<*ARaF#3YsmHegQ^TU-irfR~u_$br2YvJA>o)6tW1MECuRo7wYeNY_ZWGX0% z9{-uSVeQ?g$)Y?bWZ*B0@?VUnm{I^*wbJvT?IqA0T%5R};QBsKc~44nM&-d5?jzZV zqv0I2^#{wW77aP|!_2B0;Ur8qyRko_gY$%Ik*V|H;wy0v73N0usk^frGs*Ieot>D+ zUpMw(bFxBeEv7|HG;pRN1>QEIyN3Hut5e{6$vvy_R3+(%}rk zoXxQVgZj%uPGabOKCd;izJA;hjnX*`VJcuA2}9=||Acp6jjoZXUEZp%8I>Z_Ti&o) z-FTN9pF~9v@>&cGcTiDRAgglEPiZ}L_2}~I++Q_Oc#?KCt#=~{x5x95#!JMP6X(Aq zIz>od^s3)`9e2bb&{uJIE{eXu6}Gnhmd>Ufya`d?8GJ&R4EfVxg)0H%^GJ6))pXW> z5z|fp+awm)3D}od&!I(KH|}H6B53oE&V(<$Xs)%G8fkl(_qujW`m;pd8N6j;NR0i_Xg$0o%DF@KfU4v80Kd%s(sy!h*{Rc_J-B@qBPXTOe^`2!*Tuqe)(G--Wl1OR@fVq++&8!Y;ePE&p34)tK44@DOyQb%b`urP};+xDR=7UxteoL%t7$ zc3G02>9?{v8C1+V_3WkiNeX0QoXpYGXB{RwB=NWH*9{AzjyNtdHUo2q>S#BrYv||f zWBToic;EQQYJEoIpmgN!$Tly5yR<3HzkI>qoG@GkpcEG`UIb<6-JSaA<-MG=9c4tk(E%p9k-??_DoY~D?te==eA%Snr&(K(y zJ9?aUFf;#GM9E=o?5|q{Gku8l1x$e9yq5F(ffNX)$1EPA*Y@)|Dxe-S#a#IKdxXm7 z6Iw;uMmvCy9E&8RRPz6 z(4=zM5F>Nab@6M5zZR7DNC08=Ak^*yFUkkwUIH zR>B*ad5LuGMs|g(B%AzXudvC@s5h#&2=$YM`n_O~aE4s|#fK86B?k7kl+zNQiWS2g z+%>LfU)SlQF&Sncvh-6>81X^j)$NAGeLD${W##A1=u)0bPnnX_6K$pm$Upg?oXx+D zB4U<|v=u47PxzJ8Y}6=AKftV98ZBMy_?{7UPK;jl9Hm4WgpgojptSqb52$X_17uKh zknp|jl4FpAQ9svC2pT3Dfxq^+>9?d-*v|7wwAvYT9E$URm*tI6MMFD>Bc{n zpL?Fp>vMm(c=@wcr8-Nrp?9D%VN~*^Ov^l_wkS8^6G53p>AnE@&-O9027k90sIOfk zAZHQ@60smcnLi+rWg@DUd3jDZ5729a=p+koNw;uaZ>C7@n1;d;1~Qnrg`cF)Dr_&* z-nAA$yzVi7E_YKVT!FGOakp*r*|2*=)=acz#0Fln?rP9eLzg3XCJ7v-Pfy>72GQ@J z(&Pv@?j4&FC8k7FR^@NjOA3gzo%u6EUZ}h)!6U%gWoY+ze1VDk8HLm(nC21JH*f;2s(H;mZON__H{4M6E|+2y6M)9CW& z4#*WJ)-IXoyr(%oIW&@m+f&&qc&ERisVA`M`U`_|{yYgj%Oiac`Pznk?Z))~kS=Ch zEGpMf;d_G~)7wWll`II?su;?w* zy+CGlp8_HzkT&`HPEl-d8{TO7CLCiV+2xF&bx7N=4^}rnekjMJ?q$^ced6-B58cTL ze8?+i1#L(ApKtK%NjpwMa0#j8s`I2ouJLCg>ZcMg6D7b!ZNuaW&EVjJoD?yrHa7XA4>$cF;4!E)*rSnLIpUnStWkTES<+X zl)HlR6MWl@!Y=U22!sS#$ae1q`bNJL zqRGbT^H9z97PPMB0twY6WjcaPx&oJjmAwl@nr=fAW&Y|qzJ5;t2C zM(_G42o`GE&bK{3fJmOXaGviPI>nmM85FJI8tcUJjXYX!35>EHc=x6p0auP9^7;YYeZ zsxv4pF?Fm$+0n|LKBI$^kD?$#2)*42cjQpZY=AzJ>&)9d6hAD9IRv;-GC+yC$DPTO zqe<&SBBA*<PdKoJ5I`e`c_p|L~lz`+48$a-c!ZI~K%EmNZ}~XP_qcpEi8}xP1HXmW8Yqi2G*R z>wLDpk2I~e%#|xHuHp6TNkVAO!*@GOqjkP)#JE29m5+KLCV35Wpk0k&v`b6vl__5D zemhM!`>k*=eW6;)sM=RA`R20WeEW@tFp=R9M3C^u%m&Tk5NAV^7Q`7tYZ^!wMQIbY zp|4i&yv92YZ185Wdf{hm9^DvnFu2ujp?~gxwEK{9kM!jMokE9JSG-3H!k+ zW$>0z+t7KrtK?7A*q6kfd@HX{EmRqmV)o3%c0X(>V~So|kQtm+KhED)^5VU1)@_@% zFfc!YjZo}KJt4POa8TU-Z0Bn_!2$RAKyj!%-8)Cga|XIZS!7LmZ=cv8IxOu`CR9Vt z&)U#8-r;)50g3M^K!8NzJpcwQ!L`RI>AAWE*LvIGzQgt@^R9gDBiBL2@)I!@XM)b> z`>eyr#Dy~P>!X|m<2}pt^1vm@O_{#N-OHAYx9p6kq-_O;?&17}+Iob9&CNoPJvID% zKZ+gK_GfY}cay|xGp~oVxc3=ngI2ooZ9S>8|8pDl$`N9@?DfSIzTWF3vcroo7_CMD)#z z_=eR9e)efT4vF-~AzB@K;=rTj)u$nlk8`9F6A&7Dn^jGq>GWSn3tT_-d%x6E8+&T~=zHIhR`Qk4DR49hD zmP!rqrL?l{PeuGnv$SqBGBGYM{%5v*?~DoKO^ZDC}Y0*jBmck|2h>?kI&Ce3#mBYVBk={=iR3lDGtq@E(lTviXW^FG$N zAEh~xJq_w!O|qNhB#`pua|1U!s!QlUIjLwbW<=~hX?F6}GqHRvJ($sj{;cDC<9-6|(NSOB$m#=Q9Y=u^zwF32 z%C@p^?$GlLP5E8$qO^mnW86Bo+!~n9*vnNjXY9~PEaVP4t^G7IKw@*0cRx2kP7!hi zQu{P+!HparlXs&uk1q0tGChSsl5bn&nqb7GJnsq*^}cE?$Cu3(Y|Ym@zy3$zeC^p4 zrvZo65oF(T)WOVhfE#wryx3=V@&UxT)vgX|mqv?)qpPx!54|a21p*! z2OeGJ<7qt<{jn0&Ar;0}wZ`Wj^v?6{Dj&?Zb7DdBZToik^Pki~xecTHGLw^5lSRqts&~F?U_Q4y$WhA9$^Jma zl5zH_NJm5{k6fYH`%5jN=5I=SYN?W#5RJPE%N#kmT<0WDKSkHGs!aPwSZ48)qvQYNxoCFL`gSxfB7VB zctO6rK1opk7Y%X(O<;pFk9Wf&q97wt0%`lFm697-5d-n~mO_Y8OVF;ag*|kX<=d+Y zyY^Og`f*7~^)%Ylp|Kyosv_abM$0Q#9d)-?YUqcoQAh1e zUd+$ILT!WCpU!iEqJEkJAtpg-QobCSD~R-sB*Ln~{RpNu<%r%bi{V{@y8rL9Wvk}x z+oPiNJh+gDY+NLn6?S9WQ6EPih{7}Kbx{6FfF*N0<7`bBRRBcvaNq+;1}@Q1Dv2YT zpRa_f61O7ZC99sACW3@rF6RmIXNebid|kx+98LKh((K3CkEA2|OB*#m^?2s_8h*Xg zbSEw5$K{zV20=uaijSBELO1{7cSfE|Lm#3c%G0VfCmjUQCK#Z8G;!gIqyYOpdJiT` z^rsT|x4_qTReVGwx$3RMqVK6c)qf7J`GrNUF{j=T_4qXONaSsC z(z4aMFvmsq1NBFCV^nhok@Y*d zQduQBB=by5=fgn0dUCV}pu-3fR<-}p;2=gBVBG+vv!Y&qaM4Vv1%ESTkqJ$x74LIg zZLj2qeZXC2%2_kpjr-H7ZE1>aId9Y;p2EXVj=Ij6zFJ|yr!8Q6HP!GsX(YXs;dKa* zDgd)U2IU?q*A7+0BFQa3O3)6|pjF!O2aHne$-~{{!IkIyFh~vE>24?9>VVOwO+Q4Xp+L1TT{ z4JL&gLVbMrU{${A6k+C}?X|w-MPd5DZ(ve~o~Sv^K?2o@lH1y7Ic*a#28(|DU-a{A z#3{E=q_xr^{oI<4PXk*YT)3>18te4<`SnKyC7I54o^+0mQCoKS_iLAUdYpH^^iqu5_3<^H86+Ya?_{fs^ss)577PvK}zdfzl0 z*z~01i59f)iF)onh*sZuNDVKn6Qg1eyy8yLi~GF1v%{R|+)pS>``gj`;(w~Y_k;&Q zaI9TP?OBnxH@*nW-&;(X-`dc@m|V{;p>FMc6N*`*oP%RG?g(sO=y0JRFREM^=vCCV zm>}^zvDjjp^W>^{!HTp-nd8LM zd-NGbycy=x;ATZm^S`B-63z`PtWq8WzN`srls4_6_ll+#fps?$UAG9*DJIkziUrcm zDCl3_i?%ee-nM`9zFM-wtMTif!O`X4JnTKi>od<*>1|qOy}h!1$%n~cEZHgn}m8GOHw}5$oGFZ)MVfJyYX{Ajg{+u zG#Og>se723_o#qq$|YYhjb{5&@%O2{ zvK>h|`tjf2?7A{8Zv7LrREblV?Vz*?Bpv<>d$0Qx)@~BL5zn^ML(5LncZR=e<7d4X z(y^UwqTv+A9Tm)F)aSn_y`U!e(ejN?^_|uonU+DRT}QeZ*G0JtL{B zf%S>e|6U+@9rbf1CX_UEUeVhcfOMsr8PdmkvZ$LhveMFJJFg3a=FSjVhUt_xlqh+c z^H7v5{NMUv=5JZ<5mbQ1`A^xr|72~_g%levCL7_I;qarkixVPP&=+UEEP7Vbet>^f zl9gA^cGjb4O!$gf=kHPr5Z_XphCP})jR3c*D{1dC)hj;K1wI>V74YCqFl*r8;6XYP z=LHpM`O1f4Q~fW}MvamAo~HQs-y>^FKBMam4s_#s1}jIH2pdvA*McD9pC<3pxNo%I zYT2HaWcYx6Dja+H%o_fl+nIy9hjLW%Yu1id?Zo$tr7*h0shWH7evBqs@I57o*lo3f z{SMIXbB7px1k4Qsg|)I$am@aV3G5!3<$rh^Ld^dzEt^Dt*vN6o1+&X*et6An)>nT< z!o3%{B4!GauHN!o59wOYDvx`1C9HWRuYsI=T$hR!vEz3Iw(pzQTd+Mxnt0^x53BNo z$SOjea#B3oYn2av7#yGZUx4=Izw27Y>&9`6xvRT2YGqSv7C{v?a)s=bZS&i}3&NY) z3C~`IJ{KApEAPn+keBf_QnU%m?Zj0}Dl*yO>5MQhD{3^s@4`>XuebBsPzCze>ySzm zpNQRVeD!(I>GexM;cx?yvZtN~_|+;wi7fE@e_?d;D7anr*DN-aa^+~SW&v4*aT&R- zF1u%WE#s^;2EXj-P%9o7@ic3eqRUiQssAx8pv6LM_?4*JlXCd50P4*tTWY7YLDSAD z_AS$fkyKTXy-EWz9vOw{P{xT(qJvl$#P}&OLWiF+;=B@nijR)A7YZAws?a+p?o$;7 za~LfTcdR!2>K5a-%~pKVZ*1y-V1%95b5DB9G*CV|=~RApV<=Jn_)(u8J&!|}$}2y0 z_Dyy}-OWK;s$j%ots+S$AwuPFI)u1oxi?7NDOhP#f%y3}0lEFWC0Ltuardkny)PG^;vG{0p$*EG=)uYyy8}y6b9H&*0D4aaaTu5D6tpw&ndD^C;pktBQMaqVb^>m zgiWmdNLN4%%UkJdYOKR&sx|4r9h7#VU^Ag)p1)#JnfLz37u0Voug;AoGe8X)NbtcM zmNTZhcB*3vs%IgfBTJ-lOapT7AKL#r!TJ}F?4V>b#|-KO&Y83vISb#{#FY8P++lgF z;E{U&6HSdDsxHDaO&#mhYB#%V>2-~9dx;@Cx?tJa!O%Db+2H1%=69L2@tOeKL7|JA zK2YrH*Y_J1v6WFPZYH1f6)y1@lNMQ?%=vEzgY>HBXcWL;{|n!y6Zm`a4J=(rJABH~ zewhB^Pd&@*-mdkV4Hma>$%U-Nq-gf1Tgg!}-w-7;&RQue$8Vm~!9O0|Vw-$i%Lcnu zW3_L!p-H@hz@@VzPgt)h4#XUDlYHTS}pk-%u z@3L#VEE1u&^uEx-$@6=OP*bni?C5X`Bo&8UnAxbAERfH>EnfH>G>yigy2+==cX0x8 z^?xDXk{ze#$Ugd3XFkq~-Q#V?E>E8@!sTP@0-m#G=&tg7unx(O@_sXW#nA%hw`%7H z77|g3GY59u;w6^0l}mo0it*oN+uK5#K7Dp{=!AW@ElI2D7}H4f{R>&IUa&e$Sx{Uc zC6dTrQ4t$NylNJK4IPQ7pUr_s;kp5`pJpR7UBu&tUmre-h|oriS&i$N4(`Q?U+!$= zq>U1&_dYQ|p1&^%w_)>fj5>q(rjmW@gKhf2a)lpBqSy(zpMiG0*Z*F9=LbQ(9{)9DwKND=0hJ;V(B`5C^kdVEEvh*oe} zTO$e+Zn`WwxdcxI)pQC8z!&xbaHoR!|D8bn z}+(HoSJak{156XE$u0a#VA#b3Ew{gR#=ewgS?4|??9c0^%D6=y?t(-cVQ zBqNrNLfFBwBGnepmFjTz8V?%2{Pf{f@{j7?mI+$n@^L=jbFF;2zc9`nOfcz-qIZ93 z*S8Aw)EHNASN6m@eAI-Peq`k3ovV)7WlcOf>kk7A&8i9t&I=bT58h(y=D!i$JcI;q zkp5-g-e)5o(NNvn(iO&!gjh=X&N~%aCsPZb;O_lCTP^(6QbhAb`|H=T{SMl#^8$ii zL7MZx{NGplxJ0mjFS(uamX<6EsrZUfHMpYm*m=%DA7TnJ;zR}-2}Q-aorIJ91o6j& zvB_gykSzS4Jb+HV(-15aTG78DId360nAvmmRG9o5&skPa3o#RaK*`a!6ADO(VV^Jy z)TFYQ^ck(I9`#{+dxTr<3EgM(vdj|>$?@6$b!em^lU>{3qLYTb4iN%l3z&bd?1qUW zmvnkm2&<*(BsP>FDbsDW^CbCeJ|U@dS`K9AYMBZoV+m)vN;9dNO422?^aN(KVub^B zal5aa%eUVO&W*N+S6~uzgPoGJ>6<)1g5!}9i`S@Dwy3IrNcTLcP(amL#T=kF*3y9x z889D;S?Z?F0O=Ue&G7;|YKy^PY6IaFXUo2tf%@Tenjd4x0`Z(oYy59YkK;UkX6V&gAK4B8hSqR;}g^Y+(IS zeAKJX_WPghjI$ihio8unE3HvxG;^YbVd(S4YX}Or;g{!YLz%f2rgeJ<*myG_i6oK2z ziQNnXf^BS?Gsj&*>7Ps5yQBcO93=MmEQxlI){uKNHmqwGV3IwwV0jbe^iGa*z(}ip zld9M+Oz&Oj`aF6cO>6#OC^XLakmg49)4VS!WfBfiO((Va&x+<4yXT`{df!3FuO z!0!!A*xw;zs2M#j(|Jb$2RM}RgWIS65nS6zt%qGis1OUi_oPCAnCSk^tktt99IjNF z#Fk%jDUcmjn>eX+gcS#CaTq;MI^t`qTFRolWEr0HP=muf&*bSmZ{Lzv#^v~TdOJ>j zD9JQEbnBDwRPBK(M?=U{=Hqe4w za+=U-s%~qqn5VHSD+tzNpBmRDWhs4X^(L?~H*Of-{s23tl(@y&CI=dl8IE?1B__K@ zq?td1eO@p&`f$6>PGGEbs6MShGAK!K2Y zo@7xj2zzEHKDC#+sTb>(f1*kCL!asfcAs1m5_w@vw~UeFCASy1#qNIE>!g)c&;GHC zmtey_Y{b=*uZPCV&O}OKj&}f?b$zW)mvN!j-=jeM2$$VT~{2Op8 z;jFW={KCP73ylx53C3#(<5;7X3mjGwjxg2EVR=z*FH6%NQx}fROQDcDD+uuPX$?ho zTOI9zp(1Gz&|DAx|4WES8mEpasnXV?*e9s1AUdJ70S=Hx?7vND)#7ape#Jij~q%xa=rygoAi`pUk z>wF~(9Q4uy59ZJ(13-0^p!${qvZzknG50A?BkU`!&i01;JFUT=*U3q4#_;-Gj$Da2 zegkjmJ;=%}6?=B&>V&*QFv9r2NCWE9RIi=(w1jbJ_eC(EZnbBJI_!@|9Vl%A{bMif zTD7}BOxq2jaNXv6G;8cyQq04^<{Qa-i#x>sWV zLns|k+LiH}AggwSya!VX$LXMCCs2O-_){T=wn?9bIkhBHhBeh;5_|;V9>r!T$ZCJl zfWzckY(C$QBYF6x4_}!His9;)4AbV+aOa=z=*X%H>NhybqAuYklJELka{#Qc0j-4* z7XZ9r;4HG3P3%`>5hr`JLX#raBZ-#7iOK+|ClZD)&emDB++Jqc3BG7AK@;6*>+S%* z*5a}J#*f!-hnCbouOc$-Ffs+@1d*h4=^6GU{H!K~wp0?mvZU#o>fq&b! zIl=J9Wv|i^QqdF=j=-c(J#*2$D2dNKwk}FIj|28E zI^7X28t0@+dH{vxx4-i4S({P#J3y73_noNoUjeSwm6npy(mO5U!0R9MQU;hvdPh&> z2c>CPx997R&i)A=b>{@86G|T&j>i(M5{c$C%q!(jD?CMjLBoT0mO%L&{A&dY*dfnY zFZPRJd$c^-Wyw9LGCh)e3>*Kj1;JbAyIl#IQ3s+Q(&CKKlX7sj?AG7WTC<*p6`)ieXG%FIc?))jgtf5o3 z@OMB=Pn2le9vZ?(*`yE%&4c`7xtn%-Zxquv~@BZY4l0uDyt0>h0?LZU=JcscNc zfPEbq;Xby{QTk)-KJaxCx;POmgD7fZUk!d(eZhV*=xOx*jP{ZWwALF3qY0R&vORLN z)P9nKa6Fvu>PG6-UB}JEr3Gfv02_PYDIg&!`LwU^Hef#4+S`)?R%~BS#7c?|X>Y+x zujwmp312V6v^6GozDLR%l7c}F!&^j0^)40*_ph|m0MJ@GouhNww*W^jd&RL}T5PuJ z69-Zy&aD@yRx8C2Yq<&;_;&35Oiv$#?Nh%@7WwRBw zZYQkoC%e(Mv4JMma~sKUvhu(Wlvm!;y|>I^Yb>#nx=LU-E#$wlPvv9|1V_^?f>-BU zzcyYi0@PNCt5>h?O3BMd`)|+N0vC0of}PaIu&S07c@}mwKddOl+;qFx~-xEhA zdKCJf%XXP&G3W2`DxVnsLx{x!-+Qo0h&_&QOfI!)gxl_T`HKR}4dD&>k5Lr3joJ(P zQ-L)0V&sG>o$R8*?cKqv>z1zVdvEE-oysM^j|O#(&Gfghq-$+ws>SfoU=ZkaUV(M`>Kp(Ta7**yPdpB=Q`248+%p$QA2^fpi%_Pe30bg+n@& zGw-TN#B0nZi&n)>F|qjb=1mf5?cxks5ZNvxjBq>(`}gV;@BE+gv93$_XaN| zly+1p%2^0+6jaNQeOg0K)w;b+q66-^qlw#DU{b;Jf;pjHMKWE-FjpH|T3SS3mgc>> z$8Z4T$AYtF>+QXhp}e1_=K82CkevZ4J*|EgVn_-i%v!lQ{EqAa)IW z7ZKAEQlNMJ_TsV*5J1?0vwVh-woIAPl_08r=`l?Ac#2#_b_Jlrhmxo+$3U1g&<-V< zp85BbUUbwdJ`geKAorQGD4>gIk^7X*+N0bg=Rjl8@}Uq@QV&^0Xx4F+IorieuwFYJ zzN!uc9xT3CPOwWS@5Kp18(js}4ngx?5T0oc zpvis=ZQI&P=^*#cGpscED71P4}U|6 zQb@nk1GN)zB|F}THUSHxV(%qGu;JDDP(zh>3{Xc$?Zn}kwDrq(3-P3&ykT_QAokM| zO(FlT<7`!ITGDUSvv_CSXVTqw-q$l~D3*68X0nfc?C3?4l@y8vI*f@GvQ-xxe=o>t z%^Rwv`2KtB$UvsX(sh4cEoQ)vl0n&{QD$5FTi}x}9xULux@$GBC(9O5}_3^EHC`&ZeWr^HF-?556ZXAoa zv-Q>8t2C!(*n?m0-mCVU`}v3jq))XcfVM6wlpzY_%Vd?~zGDp-pxad9ZqzzGF8>Kk zsjLWV_NJ0f9=2ADTv2itnA+l3!YAGlUZ>H%MOsu$?N`e!F|meYHV`E?aKv(S~k zerrWZl!gPh3(U4whgUrP9+p8i{*K%vCTz^QLvxV@9=b#n)`hDif zNGQ1QWmk)u-1ny=h*6z<*Rq3>ZiTYQW3~k-H``5)*T8g*4ORGmwW*2XRATt<6y&9E z6kE`Fb4{>d8*WSUEbSc6_f6?pA2t94HI@gYU6JCyc?Zf9Kv|I$QXf+qu)Q*b0u({3t0giZ% z)T7qv#is_Ox+%gDy~ax)1TPYP>l`oE_}!PWavm$kyH!3c>>qOgiL%W#pD`e3;s<|j z6=I8tKhv`IJTTY#aziziq6mnaI3_{Pel$tsXjj69sXx7Pq1pQ>BCh&M~XoZcl$=92YFeJYoguB@>M-4jt;@ z+uUyll4dao3}I&5{`o?7QN|iqMihzX+$86(Q`tw&U1x3`Vyzs8qLM^xuJ`<&w%vN2BgO;L-|DKl-r;0&`9nh z`5M!d0@oG6ShLB^L7>VA0O5!17awGb<=?3@#M$P5&&u1tItxmw)b#vLve`*4oqwuJ zl%N(z+NQ~S7ZOH%cHuU2>Rg6cX05su4#@So%%`u-G{Gdn(mP(OpGNB2(ac*{D%w4JPxxgRyh zeAq0D$KdDx0MY_`iACGEN?DifDyyMMl}zPcqTf?~F74ZejD-oeDAe1_d8a=*QberO zrzimF~M-%X|rVqb5 zl9)G!kA2zC(s@SF4hmI{=vaP!vzwjQIHvXSJOe~`vg_-5&b-jn_T$7>yBD_Mbpm$t zg8cSP$BI|4h8IuECtiC#r)QtfFA2#P(KyGPVW?^K*{%mexjI@7eB+QR(tS53g!Sl( zOis!rplF_qY8*!)WZU1<)ANc!Bk|-19|d%#tEeneO2Fa8bHwq&@5HO7o^pQ!uj26^ zjak&cVQEPx`?eeRUVeD^uMgzVu1&T}8)U8`#SSs7)9|&H0#r^-Qf<4pXQwjPie92w zdZ9{zI|PgK&BSH1#5gOCI{Axe*4?nHhY^c6H*bpFAIKz?YUbf$%KX%KeWSG`tdrCk zXYQ;t_Og~dKht(zR{6dVKC)# z>Rm4iIlCJijg!E(xjA>UkAiytSFHM{C`(XkpBdUjOxbLS0oaf>sy{KZ(E-! zO{np;(8Y{k3KoYg;S;F=lOOm4wLA{lf+SG48pDqR#4pHCJSH4l&pb_%xK(} z`>kDO%oXKYt~snU(YQWdn~L-tcauAhj(DoWS`-%|MAIfp(v{H3XroR-oJ0%KRxp_F zk<&Rf;4he^_P%e}3*d4MYe!&YUZr~xg-s{~1u{b^n!jml|Fx=LAB^fDRGQS0!O0{h z!%aLhQkN{JxrB17mDQtG3<+a5m4tSkD5+!eqQMa|ZjiaQ^C}f;KhEE65r{$lV2GI@ zBrUYfX_mPkD|WiAs;;eD^EMF3$|`<68(Pm-^lNvo|$|$ljU_ep$B0oY-X^ z?Xr~FHk5)|9=&1fu5TswgCzm8XfM6Am$wgMpUM^Jvc%$)ccaqJrdNFnj#8P|x)0tL7rB zLc~a&ZLFd@<6Dm4=)+_4CrV?ey1}csi(9_;m|>1k(=#K(eD=BV>bq?Mc7zf{QSOt6 zwov$lc%~waXZfT}!OQg%Tp%H7hphJ-3_vkISH9fMCHljD#*Cx^2X?P5!sN-ZX~_1q zkZlLZJ1}bH@5r80mqTJu-BE&vySo_gsX5%zq;lmfC_ZQziDIRbG*qx06kmB9-IvmD zfDLlWJ;csjGyGQ!dPhCQdtE^5yy#eU z#dW*POryg6%oW-|&M&RXl(fIMixCUm&;B9Q30rK7Zj`(Ba`c=Fj4#h8b%i%#Ch`__ zKnaw42o4J=B+sgzUD%``<;wsCv`@#Q3I0O^UyMG;wQ0u^6w{c z4rSrf+-;xDthOSnKEQg$WbTLyI?@KSU*U=yjq3UK zY9VvKJR>H<44HFz{GFLJNQ25h9Xg@bae6(O_X zu%qFS6cy2nBXuPH?*AMONr`<=(%}{pZ3GR`7)A-X3?=Lb36GTK!ZPlZGif`OeJ;R_|?Gb}830|EvD72B0P zUEa06u|3b71=JXRaVI27urEG$hSDNOf8@+QX*XOQtsf%<<|BQTO5g6!Fr2j)L|In4 z?vGVNIv*eooz=n80^vC`m>>xk4Qb4;oWK_XVX|;Hc@aG*tCES+;)r0*lcoo<52s0r55U@6H*igO} z!WKTgeO3ZHLU|}{0Ij7qr$~%~tw}sl?197KM0|^wGJ@f@Msq}$jGp5@?xQ~PzVpx) zB3we8rb}Glr9eX}Th}lPVQkO7F@fAe5zKCUC2B-UF~}XLC$N^h2j_d;;VvvGj*;w_ zQT!m5jtvJt*{&e2+PdS7NXw=YHtS$p{C)XSce2bet@l~hG9+hF6l#9 zR`?aBZE;QZ1Jj`oQ4mR&#&QJ|vYwI|0F%UJ1=Xu@frZ@v$JSW~Mft|B;WiA4}Z8YQGbOkfG=Mp8nW1r}LpiKQEsJ@@*1-!pS&j>EqW56^SQ zx316i1obZ|Bw+Su{@{mFqqMa^;RxIY_a8>ZldC3qBikQiezV|BMrq8V zzv{0zJrhv76ErOUefi3zi+p3Ys#SU}BsV_tW(7pHS3CfFrM&z?KW_+WKL*F>uqd@~ zQsPb9ZBV3zTM3Cfz1CamhN;;-R9P4oqqsybog*U#Jxy~ta5IT>BX!vP=c@ZcJ>mQx) zN>%loKS95P|3yC$80GZ`jCK95`fK{2j(x+d!0n(?tJ4vWe6A`ne`}@rs!$iU@H>E0 zZ%@#tHy^8{A7X}$=XoLJtVYHgn!COztLJ>4&)%Egdl+2ohd`kjTFKa@E5f2fzi&6 zJ~$Um;t%E?(2%iaRPeqw^q;TbRG0|4NO6@Nta#A&<1o~ad1iN?#`Za}!=~-AI(H$} z>5M^mhR+b(Y2a<9;q$=fC`WJaYdjOeCuP`-@+P+P{DC)Ih7a=kY->(>I{I8<)bc88 zZH9QJrYpg z5L9GTtcPQRVHyAm=j)Bav1{Op_#6%uBmxdHz;E0d^kiy4o(amZrJjpDBPw97C$riI zI~6&LAiqxn5|TP15H%3SGx~ZO#AXanf2>Jv*)e&pi^fL;^Z0&o6Ah~$+5Q<&QLEeE zm@q|$k$*@`($DmR%_UFbV^Kv{(O;*acV?`}0BJYsd)?$w9LST%$ViFuUFd=wj?Tz< zQwKBgs+e^LO9#o=Y{@6F)`OYF4F%Ey%`0k>w4Rx0Jvr#v=RNY$M!+uvw6aHq_)nQw ze!u|(C%XhFGuwTSB77C#M$rXgR=rvR2HfwcjW<{tA;Cp{6*w~8`Zs`0KYW2%V#lP@ z?LB+ znP_L2>N_{`Zxgks7q)3FO$EED)=Rv6HSlF$BIE^grDBQp?k{tq`TCQtBaFWAz|fQ@ z@3*&b&{dL_K>;gJ_*v6fh>Mna{jUR#q18bR3g<+%B}#GCPj?&5a;{nbAR+`7zz`K! zT`L2hc;$0KXxWv$9b=yfu}~S-f}B2SIs^~@3!Fs2IxS{6HrcqpV{%zksXL;ywHhXzQvzD*K|9f9JNcF0+9zMvl2k_{%kT;xVvm$*HTZ)$JBa!HBlh0 z7HL#$jc-KWcUjL`(&jxGTGFZ8s3&Gk)rkm-4Kkym8{8pc zql27!6&t`1&-tOrcWmzvPc#cdAY4J?&+JYA%~w&j5EU%{F9>OgP!0eBe6FcfQkrAJWb!q4*QD4qyWafDa2Y*S zf)yM_W1*I-PbG=}bZ+MD|8Wa#h&RD6rX6BQI=iwC)#^X1$XCKN&b(7u9`-7QOo(xDihSo zB=eLz)i=xqLL({BSs{I7JvZI^_t7^A)5rG{Tt2al8Ht7(6fR10Sy#W?^JjOk#ZGKb za8s=BHgQ8W_+dWq=nKCz7pqluf)Do{?yX~u=R-oeh8*=sqi>f`rA9Xo{7vmLLFw|V z73wQ~@(1y|Y~{V&>i}~|Xg`A6a($+eQ_*)rX+730aK7uDV6^fW678-={6!6OjXoD_ zwFILQMK$nBnOpTDKC`#;%lTsro~sOms$0s&@Jp*$oKObzee=8X`&003K%I}T-w|)G z*mQcNG9vk?Y4ch2u4Z>r%@p;%A!m+hqy*!xlBz?~Lr>tP>$_l5zj3?RpkwP3Cf{Q% zerW3WeknHlE%beoIls>`g}>W6Tx_!*Ybx-H-)pm?xy%mPUkfNLPkghN9eLN!7yaXu z1|9&9vKh1s#{gn`+XGZzlS2;dLk*@=qwYd9tm; z`;35hBTx#uMI8?pvQtf6Fh%VfaffkOi}{kXz3B0r6}!Qkx8N`8>x9~6lH$oBi@e)+ zIaUr8*gY2f-KfZ=_JX2$Qvzhtu$WikNQu^DhD}mC=z%xUalh(KrxETT?1`#YCF$cx z+H|SZ&tXn8pxKhX)|7epG@fcQdymci5?RGAth*H8?%pIOGE^9eID>xlN;IHzgsANH zN##VrvLT%?L^dS+;=k2$3o{%wbU^dEz@OeNULzWwn>Rf2$ZlZRTfFaj zZ~D;!#`!8?sqp_BhkNfGZt~;Nf!L;;hDOA}r9k@5ColS%c06Cz zUymnxm^6}!%iIvboY>4dK~dKX{gtQbTH5Z&!tZFc(gFebPk*8t8jhJ$?nwfMn+ zzoL9@v@NS4R9_z7f9A<{pYuuArm;ur?gBbxXmOag&MjR?{fY-40>C=_-N3~`+14sE1}=?fJTuhHi?6@GOUO{0g(=C8G#ryCo9WsEQIDo-joUb>fok<88!*pEQIS2fXG z>jar2iUu}!LYJEi9lKU)rOdH!Y2hY#ZL~G;fFkn%Lu$CAeY$COh3-`mN-6Aeb9k1T zc<VagN?Ff^<^C?{>bB%QxTx}5Mn?PnoBq^q`1zoo!Lj5mTrl=LL0)iqY+unaW{RYr8(eJsAUGv8 zf+g%XD--b*@++>(FHog?C@V>nPF0DgK}lIG^;U9!^G}`up5lR`3%i1AWxZ@-!0rmL zB9>?@@H6RkF;_tFHs~z7tPEnev(LbUE@&CxI`Dih(Q2;`r|dQCE)mR!=p}{7JAi@< zIz^dL(iD7?vqZK@{iB0PT^^HV7fbx+H#z2P`qe_8roNjg$Ynw1gFl7BcGTs1<_&cX z+Q^61Mk0N@--tMaBbI){7Eb6Y?){p5@EDc-I%=xn`Me&^bPTz@NHS=3V7gxm44zrO zTb1nJUUD9+qLtqKD2Lc1?%oYN=RIrX4LqtW3BrDbED6pqwIpBQ>y$zyg#M$^Bq)SB zRic(R*6TlElws1OzyFFkad}>dN!M9lYJe9%W!@Zb{7VcJbQ z71aDBVkt>@fQF+CM~h7x{K+-paoBW%E&r-*@+Cw<;;76znJ7(o$z+^&q&Y3Plbn94 z(t6%_sAj**0bB$QleIG^nXC!1eVW+rE=wz$tLgoAqjwsrip|!~9J86rV^VZlf_ZzG z_|P)%aWW+kj9-Bo5C;{=sioKoo9%xy3|cr0-aL#W6Q|3HhLGK}Ea7F*==pChEL3P= z3VUKzl$`vm)YSGQD(##(R$w-!h+==XKEt_T&2i>l0_9^}mWM~-Be_28b4Zqx(c{r1 zohj0qhF?n7q91-Q-nQl3nv#)ZZecsA(n0J|gV+Y7BibJyK05^cq2mUEvn@G9&;hNW zr3JZ6a*=>#1Zj~S39_+QlVZ$7ID4tVKyQ4=>EU$I=~s-D!uiBbR$p}1Q;`mcRL-Nf zoSmb_6*1#+Rhkj{)VJ7mhqB}%9G^Vitfx43Q6)Lp;NqIcl)5Wi)5&^iTB!v-<;ap2 zN4tb})7})hf2M;*Sk)thurK1dSjBgQ?$f~@AjjEnkT`N-Xk{e=HYi|j`)tb#AiTps zKdunu{x`OX!S!VKj~Nu+_uQn@D8@a(s$!pmol(L8%^mUgCh?VW0c@cmGVO#>EM*K? zsK+Uy34fD^jIx~yblm-xOyhsMot(X`UM_z-1Fzn+ufoR}gXzFYkw0Gh!tEiMUKrPL zv=Ca1H{rRLCr*J<9ttOR5nc3WuP(W?{?_cWC$MSGNS8x=?M3R>;WG}0g)Kn-Nf@5n z2e}QLj$jf(FGR3Nf$+m<{$*$1U+NkB4f2Nsx{ifeoz*XCyH3 zFu!c(iiAs79bETAk1b!s4|)@?b*6_J))ktJ6;(k*Pf~2t44>zFujwaX;7o(gRu;~c zI0edU!sB_K3-hI)pFC=eFK}wQoHpU*8e@wB&I8oqH5xjB0!c;Yy<3}(ng>IfsT!do z)M;A~rfcFLS_B36ra#uarxQ+)>#ciP&_Gp4z5$MGL0$0XKL%8SLiE5B;|PZCfiSx> zr-+mkr3Z0ANWPp`dYdKE33a%J8fuBtMmvZ4n+P^bb-36m)9&?G)6otfhxOamt+gRj+@(;hwFF_i3 ze@k_IWK52_y79hu6fEp!k8%~? z4e#NPOHkqsI5TOtW9W{{;e{qK<-9SXa>{Fkaq8Z<4`rY8;8{l+LV;cjH{43fPkY*@ z=~8=}3ROK9RXaG}68RlZb;FgfFM|II1> z&-u2H7stPSwRAo2iJ0NA6l(t~c0ZtD>R5fx)jjs(>(+H9wHqwZf?oI+{hjx^w;8{% z&q;J<33F)(KeC~2@;{6s`=n3cULLDRyBVX#v#S$n>i!Ly7!e=DJG=cF6}7k zQ_j0nNM7aVSJ71c$W!b#JeXSY@bzFgaC&K|GAM+$s4EgZMHA(NkO$~vXQ<4!UeYPe zQs~RGlKGInrhr*IRFiZfRMo(1wT$5uk>H`%%eU*9=(1`L4VEf0<%b~8=>)WHrYA&u zf5s&uhb4l_R6kc4w!h`8a??T%wL|+M6{n6%E(K;U0|@oND=&@fa-c}31a~#OGAPD zi=e_ei1Tfj$AO5D1z`zHi+c$3AXom2Ql*Zl`9-MtY}!xD>jy~fdTZXqwOv|$5irny z$L9_=|Ka+(?XK5cSA|=V$)^IicNz$hQZNlODvJqSatlU`AH*(w`+@Dha>|6a)iuZq zSeP&K^?UG`Hi=DeS|$WpXR_}wkTzhd`nZ|(8sD1FVu*ORe2AA>1X0@Il5gR|ueoL= z`u*FWzQKbh;J-v1HmT{~GP{4vm`Z@up_mE8ONY}3HU6(?kfkR;x>(*+i7CuUt zMCW(b^FMXVS$$yxI0f75S5dXpikQ=Umz3kc32dCS&j4$&+yjMTlJuoz?}Xq2 zO^i9iMS;^`Rsa#3RXF4x;uZ^iK`y^mXqr8G`NXud!2!qp)wbIWudstIA-ya_+I!?y ziMT_O%(C`CVa{f~Bf%?9I`Q(t46hgFVI!=dzAucFV6?!cS_oMm#%NYk$k zPQ=&y5bLAfru6f;m>3&^3>9sDzLToT0r~}=hKNB&RZVDj5ol@h1G8$~B7WYCW7T~> z)w!u_gVl^G4$QT@K!&Afx@$dMW4frw{#Jskm)vWM{pHm1ySl+1qS)Fnqh%ODiy8CtMe}nU!RJqY>KO8E3l3HDr(0}1c%RVppWs^| zx;NLecCU-WF-cuA&06V_9jxq7bTRt^3(%&xCY&!>-IiuoF2OVr@kWW$n>sGh)l1kK%6VV}0PiYYoc#k@5HOYsQ z2rN>?e~XkKxSxL#9uJXa?>@NTe>H+x=EZ63s6>Hto1wPqLk)vKln-O|rX(wXQPmn< zUwgfRaZiD9A~km7%!&F*N#m|-%7(Ci)5#3qepQ%suKfbcvo5b29-Eyy$4&C9=MBau ze2%$lU9tsSD%`HTEE+i=FCFNmht+ehP`I}<=rn9K?EZpFApFy^ndvQ5Apff$)O!$t zZr;Q>i^pI+CU^f(!q^r(x(-Y^RP!cI^#K2jvvM5sG(KDacU$7Jrgx1JOJK8V@|}qF zR^y?D!~ker%(p>6T_R-ua%c~$;p4aVR<@VlX$Z5wy4iQ ze1b&vp^%X(Zt0RwuJ7F}@XPB2M$g*H=bZCfcL&HU;1I~`9;kR)$XR#hKS6zh!mG{R z??d;jZTprUT8hiIUXf%jf{GKm?j>Ywg#fibJFgDz)zWt zS~;elW5w{qxp1ai{6E0WgUU>DTef7JMvTOz`TZdDrOt8Vg}TSpfhcc@ zpb>)|8Ix_y)KiVJx&5M7UHYY|_1ms~g;X~2 z?rb`VU4hS;*n`Piz6Lq?z@KNNs(n-v1^ZLr)bb0=}bVE}<=IAt@o7$}TGQ&k1GxQ*(%$oYbYMF1^i z{={vWrnH2egF^%Gh_aV2U$^)hBlYVjgkVp}+(JKn4EaO@v$z6bzxzL%d}7SN@HJvu z+x>p5g^RP%U026n;p+TjB$z&(qR5*=3ehrqE^QL;DhB16Mw_0aj#t0O)8tW1pJ*=l zVg`M1ycNse#t02aT4j#OA^r9v8$qMHZ)BRVl3$rwUTgdaD-?Y%*piG_t2Ya{GvQAn zUG{^VR#d(9;M0GedXG^3IjZ8dc9@O6#7sJLe5V9-#t0f9!L=BW+$&Zzr@oD|-k-*wFF=zTVMqERQ#uXpf5xp>SX7>}@&- zdxFT*h@Li{=iQOD^9w%{%Nk z?j6HFop&3Iu{$O)jSG=Y@{C{LYH#=OJ)v$NW#F6}{>c??(qyV_@4c)If=mVwWYT=v z21ijP!(&>weCfG|!_qNuh8GPpI?XDx{$sj(*`wcu-0*i^c$?DjU`4qQQ znTcJY(~m0p21#o5D#q^O%fQ;R(ug~N;B^5SVH;=`HK{XpN;Oi-|Kd!gtA*2fo=86j zly{iJ^{xeywBq)Fm>R1(Ynna1Q?{L(2?Ng^Un72_B*UWvyoid8gAr#<#t6F)^9pJH zQ)p&8T?2l_)or#C+UocKYPgFGcT2^XYrj-WlXksZmLEyDhK$Dk?NGtdxFS@|vD44< zK8)`3iQY4a>}6gAR)SBNZan}93*3L$rkWl!L;G}q<3l#&ktq=yc*cJ`5RKPiY4Ua} zc3D=$^>c8t*aY~oGZocqmY9X<_Fp>BviXPU_ZEe2QJ5#X|p<)c0SI_lFLQO_{a6h*j?3%%x_~;xKMfX@5;`q%1+fzgTn4ctbt7 z7YuetwjlAzg!~NJrOZC>?>AlMU?!U5dS7$kwXS>VsTCEmqzZ=XEy_Xk zyb4e}Sm_o%7=P{-r`m>TkXX z{cg4bj~5zOB2;8}(1zo-&L4+=rg#oFPI2jeG=P?4OvBC&oDj-rvXOrgBhA(Y>+1~{l zxez9a)LU-LW6_4 zv_Zb-=)8nN)nn5{01Ol)(sNRmc()iB*^p4-ez9NPheEfk%LK~_5D~@7)@k)g{1F;- zCV7SrZRGPn2wv*oxW#-jb3u?BdPFn z=%0ux?j4==!i#Zm&>KCt*Z+Y24y9oU(b4aLMEpT&%S=|-^RsH5H_nrekK_FrJDmi( zf#IZ(;e>V{?hS)epskGcQ(j%+fCKg+taBDn2x0`6Ljk>w`Ip3yuLvL;AhP_{F86Oj4Kcm$ zQoJ`!3g2lhx7wRb)g+a4Q~|}(LlWq5lhN1Qoj;_~Zn&#P=nY!)prIe<7Fk>kTC@s!?HIA7|Je>RjG^i#$me{}d!E5M5|i z>>@}!F`t-C+=;`yt6X*f6q08AIA8>OKq6-8U=|u`lJx(s4h_cbVyg@H_UuEV^q-et z+-sG4lecQG5IcBG+g8o?!sr9+7QIE|A;tGrR{pFRdt-bSs?XVuan)6^e?x;vuMsMz zlMnQ7epvL;P3^)elrQ%zR`FQ-eO`bJHZU)2e%8I|j+F<+;)B36Lt~TIlUW)Y;TA}_ zY?7VYg+uG9(H~c3WS6DUGNTNtz&yF>#5l9k=~+>Cp1fPC8^)bAiqkBHohCTYR~^qwv1M3A6OF zSVq-JNi3(kJtzVP7A!EmE*7l#u_?J;Ie--5TXbw-rR2-eHylg5S+dJcg$cL@5vVt6X738a&X@7>nGGB;$vJNyN(xB+$DTR)Gc;np!hE|>i1X%^Ea zwraJnL`+o&88g^q*1C1bRbjp=vOhKknSAh@r7RTD+f!1%eN`P+vH!|6GAIS^f)1RATPHGY&aeP6*?hIp+sll*s8SP%&!Z7(&QVmZ8{1^#uEiq@Ux1@Dn%Osokrt||F z(Ip6Z-leN+#O6P$1xP=!hG3mal5D#-2-RgnsG=Ml_?FohZF*7po_$vx#+@_E3Ju~Jj?XAmpLD52{jtaJO zY_V9<{<5K=AsGTdVkHeIOFD5m;AV4U`_ue`@a94U3|p*XVI1x4^wv*5{b4kDGhR{~ zU07IG&#N*2X;TohhtBS|y}EGEW;Z3j{QROt2ZO_#_7KU;w#sx~quP}1b;;-1c8|OK z_}l4EI9VKfGXekgn|<_GT`yEMI%!^RKVuu{f-U^sD7kIEYIrwN>>$ zq83yR%V(Ze_-M>f7+pcQjG`ycE2|)PnW#;S894%fO1oHNW}s>rA@w{Xs%Yz$QN=!j;ppa9kzWRi8tf$^n$Ci%e@|#Ob5!i zzSVM%h|vVQWZYB`15o;}1Mj4w*edPE52Q$lk z16u(y$OIdvLhP}?&QNIt`U$TU+kf!zqUc9v~sK}{*MB>^o%JNTGtD%HX0aTaGH z=1j9f79(RFv`$&fT7E))tuP)ou1rdkxQ9t@*Z=jlB*rDiQFJ{$@4Dfb49a^FDNK0uDc70lUY0L=qbTa+=>Qcb#ecZB1*vG) z6*YlT1*^XEC?#Y>I#iQM^B%XSbGd2%>qF(P;bM%h*&CXa3oX4s|8EQ#qz)k*Yyaw4 z^uw0|&36`4P5kxhI~#In%67g>l&H%kXeR|tKN)9P=B=CII_&V$E!Z|FevH82h>jx) zLxH_q4LkcQDT++Z}W8C)IiVGC`kIqAhh zOkL`y_ZsuAhMrMZvGJW+M9QRj>HGIQGNZLnp$&(oMH4>ce@(PosqVI?DC*j@lPn3$XWD8(i1%!e-c?3gLQQ4>0S%MjRotQ5=DsINed6H`iyAY&vr6tYM?mq3k*@c z`xoJ#yjYW%gO#hnmz7Ge{Y* zD=MX4v06A@O+ZDgZY3*ri}xVLv8dc*wA0|!ck7n#=hX-!?&K$3eDI0+!9GW_ex1pJ zg?{&9i{56T^uorfxnc@BWI>Qoj#Y!MRX7(g`N6lp>7!OR> zSRhNYEhz}CMhFl}k{*>v0a%VC+mY3gX`u_U{i%d;7|XLo?EbWaaMZzRni&2&d+L>U zNqnN8Tn)LA7u_Byy%0*TK7$my(Cr0$yYYEUqsrpaw>5nYrydxC4wk%&Ap-n(9|m&lyFLl56DbPWPtMk1m)Ma3=$e;oUIsqvTd;T z0;B^fDfnNY7V!G)CD)tHefAJf8ReVJY@?!KT`WAUfzU-JBkW@FXbF_(@ud3 zZsX3%?Se$7lglL(#V4o4a`iELD&j!PpwTmURB>QZOY5BXBT{U%b&-2kcyo8Ir<}Tr z%d2k}7{oMk0q2yr+)z6Vuh+2ee{aSvEg`zFx6&Y3LDqX>(Jq56xA0Ns?A9l)+V;FwdLe}stQRkfG&W-K#!SIOH zBiDuMGxB2?CnC_ZlnD>*{`Tatp$hfb2Yr$V?a!SnQz@dd*$wOTP*jg!w?%SLdHn5p zvjAlW(%sgVgD+BX`PPZZbJ1A=?ZH80;^D=rc%*{tuKUhUAv^;@;x7-x$PDrIGAOEF zP5y7*&h#URbUMvlJv}QcE23CCnE#B%_sNz88>V7|I4~fq{b6z~n^EJ|)mtS=!#^cQ zjcKme;f-R>H&R=UM5!D4haK}|s@RUxb@J<+Mbi7v_yos=sy^N`uRZKsq`64rU+>~? z!tr~Dy?kKNU**ZnC}4w>r#oDbA~SM@e7}^-%DTv5zMg3aVjyGM2u7A3AFMTi6Wlm= zJ703+@dgyVb9yzHTHJwH#Pg?3Idw`eq6K^C8T%3V%FCDjFDC_d?K93u3r*taUpRL1 zpoHw!5-TvYgCAnsfC8#4=+sB4#)AonPv5wptE|xn*oj7ax_?5UyH-|#j7+f<3;mhf z?cwx{>Qri#L`s3`RLyd3RJ3gdHpN`U(DOU*p$-co(~nG}PEkq}f@}IMJC~x%9EJp9 z;$lF5{nq~dt8EE~w&R=3soRcm&y8W7ODu`VQ%|lcV0&(ia5B5A*m*xS$ajn>UB`cB zl>3^{>7_P7S?7tZMFKHvleCn(g?Ng97%Degql3w1oN})1 zS-zN9_(l`ip^CdINIzaS!VfJyJ}#(H>n{zHZW8_dtioWv|!Zr}f>w^5V123%crMo|=nh#2)d9(a6)HK$! z`C9U4K-RtYKxejFkT;@@*7TLX^=?Ci!}}Sitv{&)4+NP2O)acfHc;eJOYs60dN&z! z>_b`8E$X+Oym&dQX6s}t1f!J>3C=?H%{$2@XVQo)(5g}sgAB`Up~XPJOZukq^k5cQ zzu6fp_d7fz*h^}o>C_c`Kq@Atd7z#hDL4rIrF{>K#NUjoLjR?R_R4Iki?Y5@asTI3 zeoL+wisz5+;pkR$G0TF8lfpQ)D_%7PG?<4H835Rc9JENg1bKqFcs$n2fM8~;^-!#KSK^bcQhZ5 z_ktKhV&*NNEwbnR@IMy(knF>7<_LbEv1$4|=4c5vNDats-G7&a#I7MvS$O=m)MGZ$ za<+T0Q`4P*-_OA0-76aiEju*3M!KtQsq%h5BCw@Dk-8=OvitAxYqINogWaLu_VsOg zUjN1q19^ZKN_2g?l8R30h!2V1r+ErLa#T7f6PgX5kMFXKRGX80^ftW~Nd zbwSTb7qASt({&qS;!|B5Y#*I&Oi}Pv7{7L^e}Z}%M8UX-1DueR+aWtbA)f7@RdFj= zKo5j)J^hgI6Jkv|PzrfSd4{|TMsH8jvlM;j#?h{AW2iGm6vhuG>lRGT#Pk*$(gQI( zrrfl%Mg_df@4UUp3 zDFm1uTV$bUq+3^+>N34eZS8Y$D~jDy_+~v`hueEp3SvTRAx6t~ez4k{#Y_)*3r%|e zVQP&wA^Ytr^v^DNsWQwF()sJgT9M&?0K%peK(sPsmBV?@j=GNG@iknAra>}Qu9Gi6 zCC7s7=GLJE>(9IoEmE}4=i0SgP+l1p4g2uM_I zds(NeW__l;HXFZ;uRg7MK@kzL>i(5#Bx%&=2w(gbpD234uh?~E$}Lsy7zPQERDD(= zrW?{TeRN{}B71S7(rf1kSWhL*&dwGF19itttdD>i>Q2)d>Z-1i-Yx0C*W{XzTNofz z4khf^;G=oo`3or1NEhR3Nn=$|$9c_)Ad&gABAnsZns@Jd5nar_BSg|oLYNfXu?Li{ zlZlr3Q9latzhtku%x|5O@c<{%bJ~r%HCLti75%mkJjb^H07!vma4^NU)hGGMhJXgb zY4LaJkywZLVI{+=S5J)znmpaBi7nV%v8p<(3XQPrPnXe$iAVVGATG}(L5ZZdZl~V< z=?hYvk8hV1Hky{r0`EkEy)3Cyz_lpTFXF5 zEh&CK)@{xZWv);7m?z=XEvt#F+2u-ImLH0r%{yg6CsZiToOMYKH~ItSZf4oC`(P(y zb4>Oh{aXncZjIhk3b)@#tbJ?qy_e{Gggi`tnpZnjuR*R6ROWjjA;=SZk^aZ%Rl{@J zV4`D{PXnLUySu@_*B5I*%+G~GxgI~6^U!!n_@t5_0C?ihx5AQEu#Q<>YZncq**5hF z8nYQ9Ryt_BdCL6JtM6U=m>6sXmSwu)!^_70nSH@t$#qWNF0tTaU1<)QiXGNC#f_(Y z^g^3n8HdF^rzf+MB@On#RvvZAHAkUkopZMd&!j=s;?5#(X&EOZ z+ACq;)X$+S2y@5F6V7beIQvIk0a}-O#Z@Fi*Mhy3A-M*Hb7>#q4XnFES0cU-TvA@b zf3>^mMCx+umir}XAjcFmyTe@CXyv5NEp?R)g@?+fNReKL`}!AD;m+~OOl zX2l_h2$hw8XGR}6h=Ni06H=3HteZ6~HUt!wge8IXl9AtP&t(Ie0Z|nGC)d#;;mZDI zA+TA~<9U*7o0gW@;*BzocMCpMtXbQDk7D=lPUahr0kLX?*WHMa*!!nDX;mF1ciGt9 z2ALP?@Wiudo8Xdg2eCum*q09b2eV> zJ|8RRk)aA6M=)K);^(;tKF)Md!<)7C9GnYfWMGL$6f==+wvOw9eoA{gF|zT%C~&R0 zIV@7v?Y&*N0mv3ei|wX?QY4~E*wxgy*w>%AW_sOvhrWH?hThR!vrs4CPWZ`Eh9;_^ zZgq4DboP%vsGfWn8JwXlrNlA3SS0D@S|q(3!5=dW{d$}G~xID2K=XA!^Q2Y>9rd5jNQJS+tQH~VMr zT`-_1!R3IF1kzXBnE->tRD*5}zEoy!T zV{FhGe|r*?TZskArUbsQ;*?_96^A_CVMpo9Lv0Cpp+e zzWKeN?enJdv>hWxmEybWN_DHHB=pAJtY;ec;6rD^_p*=^j%anRY)QU;nekFiV4Fxxf_VXDF`U@^EuQ4`~bqO5V&bV6yxsta?=s`^OU<@ zSIabR9Kimz-HHTwFe~FL?*7->^P}j};f+ovqR=0*d%%SwdF67#nMwHl83e%>K_4Pc z4PFWlp^`kz;o|R^Vpb(ur1yzFhZ!Y29}#@>529?{?;ciO&!RS1Px;)Jxu73fmXE(kls6Rxs7t z)oasrmYRd!Gy4j*a3#Zh#bV{>e^2LDA0-hv?*8eiE4g%K@6w{ze%s;`zGVq%IZ-;2 zpTO$W63F>D`+X2i;*hJPdm~FMHmrp0{_w`(j4wEpKrEHlpHaI*I1X=qC%OKmdMmBt zUS{bRnYHrsWr!;#Yt|4gJaHowDwAPUb2X8E%*YjQZ|*d=1bX`wYysHx zauO)Ul^9rS9n+X-H!U60-!b@N+8m5K>B<#>bpM8YVmYL?rRRMO0DSy~R zX_Mtc%q|%$^gd!TuBhV`mt@#NG3$~ojgrq#;yCk6>w+m-vNW1BOo2%34cFfQbo|Bg z1@)I)5}Ofgu5w&RhcOUby5&vwB+6wx+SpdZzXse8CeGkf%x=`GVlwXYahBvHknb~W4|)t zF>&UmA<8_sFFKY#=Q?z2|E{cwau;Lx1zx#HE6k?|oByYUbq%(cR6tBu7#1o=KYu?B~ps)=N-Y@yQVsT>eDvDmR zcd{N9OQWi@i(CUpCbGTINQ192eVX3b5BH4^Z`o+J&E!8z$ulSfKbFofn4>Rn%lrPI z=$c=M6`GB!-~3S$5(INS>%TWqvBD3^uLq-?7P~3^HRmX~KSS|>7nk}o3~P8$t2Ni> zb*F{e$SR)V2d~v$=lm|L&JLUmkRFrAbQ<{@U$Qi56&7wn1m#Ova-9^_?h$XXBDVb{>%?4;horHSlsD`puv1iH19 zpj(@F-o2b#9%!mFHn!${-K>G!i58I8P)Lvw8}Jp{{7*IS!XmA?EO;xE4v=RNg5xD^ zJ;}m~-*oN1__CLJ?wf6}SpYBBDSPt}4FUDfUZ6^xcYK0J*AHw~6?f3tm- zGB&Mi)%9FhSkJtyc&VszpwV|-Vk3H2Xx+msD+HU}XfXFesCT7Neyp*l@n;o>#3t1n z+E|DM!)^MOL#YVsMB-42ZvJhh-S=MFSUJax(wlGQ<+*ovcQ=Co&r>QXg}H$(5?@>W{(N1%a0DLigcub*&E7PYWv3`;L{XTlF${yt*eM70Jx$&(09D_g8=gv$Y z1w_dOw$HPTqn7poxK0|}!Ks9ZsOY#i=A5r`VuV}ff||_tnaJ!%YM%UmD{GF?7Yt_E zDlb|L4glFd{LZUi<#V*XB4lj%4mC${Vo1vyQ~40}&-z(I)41ACLJ*~a5kQjKo7gZ- zPtP2b(pN46p;2`GmUv?+hXrjce5(w#T5PzQ(qnk)%*Q_2U$XCwMS(aB9Wao zjO;rhku|(cYO>BmX%mv62vZEQhr!sU-IgW$GL~X!tl5_tGrwnizJL7gUzB_AdCqyB z*V)gNEqGX-ga}Z%l+Zj{W$$IXtG78aJX{$tXIH$iWRG&o8VhAaW?Tq`Z$~tK`21n= zLhYTI_$a-5*Qx_YyFY!>ckv!M=%l7}TSmWYj_md4pqAI52SZs16Y{}xoIcQ|5GinP zk-j<#0`W)WezH!8v47Hrd@jgAP*L-rKkuVYU=i0GUmtZn=U0D4t zrTbQUd%OC=>wYIeV~|GZ+Ip}XwzAxWX;XgB1?7;t86H`!TGW`zO7S;GM;DK!?#_u} z>L&NS8T*_oTAQ7HDLgCEj_Mj|JI66_zJVULvFMUf_n*P$VyDjik`$SH_T7ge_e|5fg*e- zuZ(j-Y3-}p85-es)q&$Fz4ct-aXF3W{nx5fUAxbj>y4(hQDOQbDaSE-)R>hfz0}3A zYxWeBrD4KbQ}Y(V@FL3N#RbZ*=i!^P1n(?cZ+q{aSQY_~%Ca&n_`=2Pk9OgtqZ&Wz z({^JQ3RRmA%zjMJ^zuC{?xXtvn{%-%D*H!YYJu0ISa=ExkADzluCIiBB4~~|rL?f@ zlyk2(WOaYS{33MzBn-lOadUIm{R#AgW@9$}e*uVVOzHwRa7#uzZ})ukZd z1RC3fX=G1?Ad5NzL8JZW&%b71+@;FZn7Mw+c0>4iO--RqNkEaicaRKYu4$!%Ec9m}TSCb z0!*r(JI$6>dJ{>0ECxDD^lxa!pu34sihnq6K;n$Cg>m-{5D^@a#22o#(BI3kSZ(NO z2|Z76*PQVyJYh32kETd4pFVCTG=xJ`_MvzxP4^vD21hO{N1F0 z4^MS^+BFR1$-}o|k*%Zjx5@s`@fxC{qG9tNM~4ZKhCKyPoN_Ia(OBYAm;3$u_pSh; z-N@@D-t{(w->e2-d1*_$`~U1-VvQ~_Sl&F`Z!NZ#36DMeu>GaQ0)B3OUgS+$np2@( zEX?TF1L9?Df~L_>H7nYyHgcwFv2!$Nco}TG#y$W!I!@|m`8Fxw+lv1bMeXSWsJ5}1 z#4p_pmY!g?-yG||Oifd|fK6V6-s#^VOiD|N@}k4{`l-=VYHJ=F4n&O18& z1jAmP)6%p2Th*w@2s}|3?=UlK1&h*7WKG&!7$&%qA^U`fgE5-MZkLmxj(IweL{vLIFeNgb#PRe|EOd zfjPRZ;bSo~U!s4(W9GvOyt*6y%*hA14JJKtzjT1sK*J>O+E?)0ba1BKIRZY@>{Yn` z4rKJ(fEdxKECR`2WP$WefqEs8A{7)BT2y~)H3?*|&vD>$gYH@fc|us*AuW51UYNt! zcaKNLD^{CBK9ZBIg2S!?)bU2SMfP1|YJshgh=`57x3_h?cib;4-BJP}bSQ~UANScO z`xOjFren-f?Avpq-Ivy@-z6>3Y=vJGq;SHY9L)lInlv~*K3@D-8g_;*is6I&t}^K< zn(_4wHg?q=PcD`6h+}8(|4>*czi7>Y9vu1}N=8u~#@;O9zEfp+=#rtCKWAYCt;|;x zG0Vaq20A*Uvr9`sP6*8`x9&gPH+xdVimqBvBD*e!8+>t12y?UPL|JIj0+y9dJG6zC zOx4zujBF4Yn+!!e*w!luHEfFMWPM-+LHSQ-LY*9(BJ$ZjT0R?EY3wWP`&a-nLqIRq zuj=_zHqAYkt{cxRpn`{pzYNKNjKXUrclD?vBlaIkOG`t!pw6B@U*6Zt5m7PGarV4}jVMGJ;TJE(+ZwkJjhlvJSSnYFY}M&iWznDNJS81A%Om=&4X!?7!?6 z)b^pUBD-r|YOQrh9JK`p_wTuDFurU@-fO+3D-+xO%!1I zh2`9v7zX*t{E%4AcS1-C-Pzo!Pr66R_`$XU(Hr_F7=+RNT9bSf%QQ^ggM5@i<- zl}aWmDYPY2occ6wEmmdfYl}v4>UZH~2HM;d2^MVxn{o!f>wWk1{a+#4N5?@vG<=#> zW>ZMz8Kn{?5vwe>#s|~)av1W@t6Xb46l!nA4`(<_3>Gc@^;gn~&HjbKS<~7z|!UVMInyynX~-gFX)W_}Av#-)$9>!k^rZ}#-%_GUDBvpB+>*4N9X5ift{M$< zlYyo;lVuec))UUgU9(E>f-o(tZFoEwdwpcToj-0#!HiEZ`GQPjATWfp+O?)b4?w{H`55{L}6$ zvT|!LXE3ky6_lknK4I9tQ1>&WY@@u*89gZ&<#mQl?QCHmR~!Y zryvWl{?@t(t1%_9mb$1Ci&NuYbUw;ouxz=2V6Sc--LZc&h7VTH_q&?(beY;BW^r{N z)xd=OXrUt0Af&;z=c`-7<*OEgC`tsEf$85pduQ*;r~(ZDETLmvb7pSW_- zZFmqGHsL-t&#NFxy|#VFut#f-C=qHmE1Ed86Zdnc7=Dw-<5*CXrqA12J^hWKBlr+k z2Lu+A1mP7&?B_Ht+nPMIbx9V^v@EEoxMO5hM<E~AcCGNIydOpq4zrmRvX ze32uRh>{v5W{gdYJAEj2S?n?+2xlczjDD1(qKd83Mf*I&R5QojRNRd9N8xu^F5^$n zYky5)Et-CfYuGXv_~~}cVkhFV@Caj$NSDvwKk-=#)_Kkz@?&bO_Y&^cPC>mtokH#R zSI|e&`BSs5QFiNQTGmQZD*{U2-cA1#Xxg6goH!YH_3x$!<-!^kw?dPgUr*Cl3M98K z``fm3J%vl3J`*qrzB7ooJlN!ADG$4>!+z=|khp+d(yNK|Ly$~U-5t~IalI5BMn$rl z5poG-SpAkad@61s39$f9rq|VH$iryF!t>W6rKGe?_s@ZckFc&QGL#}4(&mJ#5aCYpKUd=_~+RiZs4UFO!usNx_iV`S0Dn2XqeOwiSpyC!M^D&IIljct5e1<=z z(YD%7CI0P$7l>*d`8?#=#p8XOYkvaueVtQSTTY=qIT;_kYjgM=sWDor<+;Zb{Vu|sv`e}si+t;sI$hYb{Ivw5Cpd8- zeuqjOU>fbG584m>IDL+}kYqc`Ef_k@!F&vNq?NIjsT%H5Y^y)^WEHOh>;a^%wD*4| zq+;K=hZ6sN`7zJ&Vy0B`?18lNM3Qddhoet4r5PXLo$@{mT&eB3&+d`~*t9BY^%8~R zSU54pC^Kfry%FcT&k584gqHePp$-)1<44`7(5GA{{IwkAHA(>NB#*2m_l_MxJ{P)| zX8eo*j~Vao-o>iW%Qor3i>P|(+by5Ev!0tw?OR8uYT0$o8MMTe4MZ34yx*{#!x!J9 zcKZ{-BzFAHX(bTq{7?w$RQwQ}3t9z5;u9lA&?m-w~A!b83C6NCtwUoWs=&5&N(ps|b{{vvXnK)v-2jGK-tNAkDHm#?cwfoBfv=yQ|BM}V_wpdsuDtx>2l9Rhm4Nz-E7{g> zR%I%p}qxM^$u@759<3NU5T4V6?8DZ{xFDaU!(F`c-o$wUO=S5^SM@H>{s*wzXBx zjAk{1ouItBINIACl+|2!Pm#5Ov9IHAAR#>~3B+N5Y`ihFaUX}ENDID&gY?3T?|OcA zbnGsF)^jJTE+WIpdky$Hgx?(WjeZ0TAk2wle=5r4AiK*yo-s9?NC=(PD2f5-4v zXN0suhy42HV*e`&CADtyiiW@Xx6!np`2(~I1|AF9ETeuS4lOxo1HDu`E;f91N>!i7 z_Gj$37I=zHh)7<*(pZBF?(xcQablDHccu&yhudZ$@dIPUg1`be6HbM*2M!f`Ue|EW zd|Bt7(3{e6i^0;VJ^kBvT{Fso?}XYJ@uJ(rq+LT(If0NUgO(UC#UcJ5dXFCvGy3dU z>;HY>-rZS+L+|n|Q(x)l<{;sA5XJh^hm+7ZuVB|PLe`WE&Be(n@on;Eor8SEJ)4gB z8Ql3x!PU7ff1ZE!ll|(}qTH(R&c038BxVY`%OOg9IWw>4+WYZKiJ?RJ`|EXJAhW%E zgy5%V<_5i{r!Kb=pF%lYzJZYOpV4~pr9toViUDiALzzXGmg1y}$xt!~?OX^XDfpGV zZp41+uMWD5zmA)nY~vN?^t)s8Pb`Fnb=~+gj_)w$!%w*p{H;vO+!m1)V5szoeW~=v zysQ%&oVLc>)rZikAMM&qV359l1@~sM9yPmOUK&TUQVaa`xJtfyUGPJm-eQU;yz3`T z{>{_I!|t#WqM@GOc0CrWS1>RtMoP`}ps+pY%R~+mY*g$GKAO4@X!(u1QOrts>kI1J6r+)6;5)CyNPf@93iTL#XUsKhv5j_bZ|aBIaX;8j;& zwy0&pDt!UYI56Ty(cQ(c(>?k$si?tFHWwm14dr!sd%DwVWsJo)< z65CG;l;f8euu>GWsS*o$i+HuNou#Cnhh`A+hc@JGZ+lY&=!jQ4=fhq7P+j;}M|-Qt8-A>r>>lv$q5|v-%RD^wPvOAw*OY&&_UsQ$Wd!ai zx;KT$ZK29Zmm}MSreC&^hmVd@-P&`|pRznBg!k+FB@)@Yo`KBALJ`15T%CH4&MFa~ z)Y2jvey!t&wCAwapFqp`+4HdL70Da1slo$|N!V&qcuXvZP5HwcU4y$=u6;35Iz>4F z?yCYeJK-f$X;*l0|B2*!GG8L;`5{Yot>Fww@-X>!#B~s6#gPhXoF5+B zKRa|_&a%|g4Ghv)I#|e-FM*AR+pzJT&u>b4vR-rCV1|4lUS4)`(yz-%mY%`_$UUQl zG#ncen{v^7LmxvmHS+|%tP3c;hD4EgFS2gMOm=_VvOG7yUtPR$!Jw04L8;&^v0LqO zyR^k3;p$3O>Rj#j;Pdhdj64Qd*xw6c9`8;k9#!O%yaT8@*N@C_k zB2~$SBKrF?O~)BdHl0*BDs(b; z)d3xm>|kn4EquB97zF9Z022>m-I_&pOsjjf$c9!{DI91S-6-1gDS6Yx3kEccuIc%` z)+gaM$X>gpn<5ZKM15Y!(Z6jULxQdBlq|i9$t)f%l=;}u(k&%16Xd_3;;E@Ao~6V( za;cSY8~xTxy}wGlX(p_U{H)zH$CDO{HEu0_9DO2rr1NF{t-){qYzeVMs$PCZ{KypL zCm_nVHguYKH|h4XBhaH;jJFSzH4^BiUhO+I!Vh0>hi_+3bIBB_ZpBmU!T8xS@~+&6 zd>L1hfxGq}$|v;b+>bBA=hTmb)m<9=u0igkiHB3}KZ{}9%4k4T>B=J~CzHN@Lpj>} z-k5W{f+eXHOZe!<_?4Ov?v&gZG$3>{Fl-Eo11gH-aVJ2}UC-iqjCHy1(r&dKVq_|Q z(JpYaP$lCU?eTvGxcMsv$5`BtaQ8bm*d5N_pqXrvvpCDP1{u<~Rjy#itagfl!)JlI z2UIs@X$n`nNwnAr+G(1a*APEfrVLE8@7pm0*kf$Z657^N22?yh5y!EioPHX4*j>AG zZu-9eEt!Wsba|XKXBqIdMFe}wcXt|aJ6|h&JsHGh^BOnDL)KeH=@IYN^onPm);>!U z{scBqms_Bb{$Ns!`>}oUf;T6|y(z)(J5I_NTMV}?p|X^(#!nudfqdL3r57bw`5u^q zUV8NYL0l-=9F<4IO~MxxlK0%lk1hx#!!$Rh8)c&;H&J?TsKLprzf|gP!Z302!odLk z^BfnulAajg81#VM07b`r6YnnB-aUlol-=E65~;M*>PzmVON!25Pr=dU|?{d*HVl3hW^!OWqv9>u_Qw07=vqdMt+%HMK zH_Ou{%6bX{EB@(?6SUMNU8voA^?7R+-E5<5tMj}ruan6zx{L)be9(2QDO~FoqSJ-lnWX(fKpN)RxY*Y#B zKd-PiYs-}1py&PDXbDpZD4e<-c5yMgw7F>ZS=|+*vV2MUm*~5wgwVn8FD!q{CimHL zCv#85#Loq}Dc?9af^@)L8l>xVt_C$J927eBX#56q2ThSNdaK3tZB3Jb#$Y;03xJXo z>R`>=#+Lw^lJmW2QG-9XaOWueS=?#YmGX=93QR#VJ=C8?3`5OdK?qG!>jk>jC?(cE5q~`vr(_KYUMLlg~gTM17*O` zWuUW4CvSX_`XjHaSRF}O#f@;hST6Mx{FcA!c>+>p1xRo>dYJ*;oCpOxwcF@!sd4TW zP*cJqn@3mT-ime_2Z#banRt{Gb3*mDxJ=_nfQ{FblbdEzz0BZI zW!>|%_{P>NRyuYi!k6rJ5Q+WpADDBMri7-AHb)9t=j}W#s==l&ZCArRriUk-m?(n()&fGjm@Vr1*J8wChP4s)VE3R*CBtX1g47X=GVJ1%*wDuM?b{&dzRGb{Z;dN>ko>q5<}pvL`!#hVuER0mC59 ziP0MI@QwVC&8s9+-%l-MT!$-RB|+!B31k zlsoQA@;a_vBsLxn{d0-4;8b%?WNe#_VJ08l7|QvZ(&8jLpy>T|5`k=x>#0**JPv#C z9=;?WYTmmN?mlv5{fGTXY(8e6oXfMakXP^nF74}A)Np@Cc=UH(3UYLbjI|4f&Y+Sq z8cKS3^;E8UfgE(58>S+Pdy@W=)BSVAV5EVs5^7KllgyOd=91ogq=Ed=tr32;pODW{ z;HW{6F3?rUSsGI0)b2j&QmBf~ZtFsX!6MM&q^cU{xXJTsmJKw^($dOH?~=Bm zXDI4pk%tWlCm|p9J=Vy$2f4jOF6OI-n?CxdDET>K#&`jQU-T4^GtWU58ted{*JU^A z*6?K-?9pL!@^5lVlYVpHs72(+PjH+Jj#qrQ`5CKEJ&hnnZl@`d`FpHS09{avxO%U3 zw87j->p1c`I(9$zd|SYgwTRPU(=pSqCqgAqFE`jUEf!urV(wz1|@drVGR;g7PVe<|(mz8Dt9xaG;N z))KjPOk=M4`{?Tsm8(B3S;b|$R~Fa-=${K`#Mg5Zo5wB?>(|`&hp`u9!hmYseos;` zZ<`vP3_Jvhdx7vBq(2>cj@agUK~A9qm%Ta8%o>o|&08aZ}7@HvX&* zLX-Ln^Q5ABZoRg;Fh$V@Mn61W{#^Z~#ga$_yV{S@Q>qrdKbMP?5^m8}mLcXzO!VDo zl?7YN%yNl{an#8nomOfQjZ|Ii$9{&VCa#*Z39JjDZ^J)Vi=eN`Y+Ub(fnnq4X^~_l zy4=$68*&TdFkR52xOY?<*|dtN)|JO2nQwW3x9fKb+lD1yVS!$JtLB5ScOB)!clUsZ zQ1=w5%6@oY`r8)?ZISb~ptgd5U1rc->3`I4orb)EGDsd(Uj7rjub$Vz0Er(R((I$A zk)f&PN5dGR>L3Q!aXk9G_kR_Lx{Ve=k`VHF7aQG`fko@ca9L=oeqyAd7*Mz5(1$>v zw}e8~6vx0ehy$Z(Dm)-{B_o zqUpS0wlW)_*E1fu`ivP$SQ_t8_|zKuV3 z3rznr`6m(G{SM~d2cr+XPlEc2R3R(@;3tHh|F^PIl7T^PG= z5#;l7`P>uEsIC)7|FcgL;2c)-p?3d<(Pv|@k+$8hOp_oV-92=8#lZ(gSUAZE;51tCGN%KV)z@8<^+mq&n6{S(axyX?b#MamiN%CopBlj2WIcdQvtKhQm#h zlUNMya3=@y+l5*rR7naxJ)kPIclrYD%Bz+9DO)q;#*4dRBnjkZ)SH*0Nt56Zc^cVh z5Z$`2vslu;!e~u?HZ0k;xb^xjO{T)*LHvRI%I~{vgOl#6Cp}c3G(ZiyRl75#yYq~R zAC-51jEZ~45wGdV87Z(Y(@)Uz;b<&jAFXLsuR1An*VuNp$ER+9RuL6U|l!Y5wUg%jW6vxC(+W$h{DQ5g2ZzH5v2PIR&J!R~|` zg#K^(*{uKaTMj*+xpo+8*JtKr|KT~YPTO_r$$E4`2;&~f^h1)uH^#0<>q3}qNAu}8 zs^Qo3^3V$*GrqM8xAXOv*SjZ%vh?#+1-t$hpv(5hIu#0_o%RBH5gIJ{SEGypwheI; zjmNCYbuSfRY6qGlr*45n%Bh}?8dQ!s0juTnSKomv^8D&io3Ni)0*73HKVgA(N%*~- z7&;782ZB^jO9xN_e@up`)yYM^my3JHB$0D&0nr|BmOHZeEbOhCSVNQPUfFIPqM#xZ}(%%8SCM|!!RG!Mq7Y0wGQUn zb0EJ&w{uD}ZyscjC$M#H9ob54Ag%f|n4xdmis`1>i~Q^S>85xi0jz2i2{IgO_^@_o z$R=iQ!=yf~**RTGFc*Yy$`6fl0wM0n!kk@8OnYr3Q!mV)6QR4nSFZL(9OHMWyjpUFfkEq0eBrPAB{l*2z>ChfF+}{{84b5vzYCu{mjPC|XR)l3B z>JB!Kt|d>E$sB>O8hgm?xvCv%-`NUI-}F+`Xqr#|E6uQ}`3TF!68oHujv8mS%kTMx zjrjK#^=p3DxixDCzR_XZQu(j=#d;1&PUDI9i?vsc0XFj8sF0%WpSG@@N%86_Asp+kx>7w2Bm7FoG5|47OyH5&{ey)`pcl| zh75A08^hZd6GxvFNf*y1*X?Q$%te!HcYqUV_037%e<21^Q7}NAX8GgAF=W}aO0=Uj zyyOICIuJDU)YH3E9z~(*pW|UC7!EsMAR(63u~yOCWR2~4$D@b{NJL1 z2v!5C&%Lq}m8D^J)(;m7x57^!LVNv5=LFeV&>Z0cOmp&RV{dr_BR9y1=?0Ci^F z1N!TbdqW!;-dz~kaDgW7GvYMtoN&k0*BOL0;PDjm{X`nI_p!m@_G8sTj_;ya^ zh4C;@aP^9^m`eR?g1FZxb9e3A=&2kBZS`D`RDI>l>kw>m!zKYId?*~ZR3 z&ku-+QZjDrFajSF2ixCqaa3-NJ`SGrnTS^r)j?G06p}nRrq&KHpY&QU@!-*+PdV8}JIDkSoD*4vkcPKA*^Zv|qiy2obVCPUd_IlBqj8w+~( z2A5+t7RDRLJjUnxDi%u%IJMLngv*kSG}Mu`>>)%ju)rX!)keoLvUk+0hVV-WQcb$` zu*#dpb;OC?bYO0`RVq`&ggV8rJB3hmSoYWG;j)u#22?rGrV~f!V;j72e#S>H?_0LK zBo#ev%{fvvEWyJkZPf!a#6{T&ce9+At%G&k}PdTIK)+7 zBp(nB;vK;1LN4e7JAHpow=l zs4Ge?z@t|q)!+I&mw>pULXs{AdcT8$dF4KF|DUuX04LhTGbfIETQ1kWcLdNNv$?4xZ zi7qxbMEhdPZZwiUeyv(}8^MNhw@RA7A7)WK!i4*-FGcgf$xlr%S*=*!1nQlzppu^T zk~$crQ8@5D@&UTw$Ey9guMH?MP}6RqZ6;|DG}y|9(|gS8@?`(m?g+c>{$vxI%XszK zTAOlh@vhqdwjupFr|U9n^iGkW2TvJAQx_0dk+R_HO>aLd1iOh&Jn)NI^g9A`ulU1( z`DCwkRrmH^eF`1!wyDRj%678I>ZH#)E2GnLp?FQGIK)jqy9{s`@^NIUrBw6gF@h99 zSqdtW@Bm&e^Qoj4*LEKFA3iVZ%7^+8*=u#rpwqU(Gm8=FZcsyXp*LYFrDwI7w?<3f@9}oZ-W)= zn-uGBbF1xyd(D=H2t*Tg40NdSylHJN&;5=FS`P3Su`q!PD+x+sLsR2{Q7pVPh02t! zC~#g}j6@)JO`ZBgH*qk(<2LWb)~@%mMbkI8-JE^98k0WN53A4+#&L*C5`X&)yZ5w$ z0xzx9l3xr`P|=cPX)A^$H>UK=WGB3u-yUdq^$4k+BZMej-CS(u) zMKxUiBQCRi2rmlL?T%%lvX4%G>lon_2w7aaUM$cAo7mbt!uxb#qAh%E6>c&n{?*;}#hhfJY$g%#y&*Ojj- zV;IinUJI|^N=uOg!;*{{iJE<8W%lh;5eBxfK9NF_MKq#w;Bda7sV8R+7qGUH-fdm^ z$*+=l$gWlCI9=5_%GjyFY!OV@L~1Mbe9l4XEBGxagWUS*u-|RGrq~%V--{sh1^Jk| ziZuOfW5%9=g$2x}o(8GKLZ~ts9{n2|P(ZOP#ceiN8fao5BjG)%|P7KE+u7hX+&NMcH1YxuJg@ZPEb#*vi=4kQceDB-lmxEr9F@mKYh12MKi3o zk_SE6I`r=$sp-d?WwHEMSNCFa(EkvEwJZ!+;o0bd*-c~A?JJaSGE>G^o7 zCfE&zJymS+zOh@)M)>Ezou_)@{{MGR+_iD+&)IdkWPA00SI64g))Hixo4nne8SBi_ zvG{1`klkn-KIx2i(SQ5DdnyZ@b5S<4PNze8_XpP3_x8BGUPjGuw_Q4bo^n52A5tZj zul~PX`sR8a>30T#U0vNv0G*nuY2P;5;6`wvde%}Wb#GzG5)J%1lm(92JrB*t^an5! z&v<{$yx3W9Un+rCNZv)3q|}9H4QFc0RKyB09!R|N7R%?|!;$i9pqHi{B`X0F$t$xN zhdaoJ?$wWlZxk-PVI%@~W7C%m>YCq@f@4Q=~n84SzygVkV1PUr~n`flBbvaDO7dO(oD>X zNs@<+!%6Wt7gz01$5W2YizYHlcbo1o`%^&oz~%=S)Mc{=Ol{{f8d`NW3VMgKabjaB z4gU%&|8qmRo+#V8uc4gi&oo{f>kK-k^QIQ>yLQa31D7}w4neA-V2v#o31W#Z3oeLi zJ*A#LB~X;m>ZWmWv_ZDL9d5E(o@pci6#?tl)aP6%^tE#YHG=SFv*6Il&1uO{d-JxjdIuwx!Fo@T4|iU? zy({dwqu%P+V1__E;$nkA%@_8pq~L3Tn4h(#AhdS8_68$aMAe1|22Jd3ObL7CX6Zo= z2zzGsHLRww;Tkh{PfX@(100ng5Q%K8AD)zevU=z59ATF z=l|&XA2UHo>rcQSABC$C>M)}N%$s9IMtOsD&y1n-<_#pYvCza$yo2|6`j2J0Ae}GUK3~n z*6F06Ku*7NR$j2_WqMmnaLo%23mshzsP|iN+%O^lG-t{-RrLD>yIZEP0a-peqoCj$ z6<8wMR9@by5kGu~Fl{#hGvWnH@Vgmj>Fk^?k{8N_2Gd7;0;6>IUh&}3qkW*YsnuyD z8A}Rw0o@WU7JyXt2n3)=`RYP|27&Mc6?Zf!zhu1r9d&c5DRLHqwEqJgQ7(1C*}kjL zRLT+P09dIXlc%O4O|~VGnCndet)>Yxp|v5~XZHtq*1|5S_Tm5DVu9!&{zJ1^3MvveV-_TFx7 zyr`aH=c;E~BCO1A_V3@WVLHBu1>Co{%DGz5uw(CSXvpjyvK(7oYYQUIt&rQ8cZ*N1 zY}a?V)#Sg*>umNP-hbXMLo`E3z(E-Fwi(GV30DKXS(=*Z_Aqh(yW11;prhA} zlm{k7miZ5q&OOdHOy7v~EVgx4%%oUpngzQ7nXtRmh8Sl>E@Mir6NW37^yCW{OB8vF-AXsA zs%n&A=!d{##CjEKYN~3gywA)W%N>OTJwle~I+676{F*y$N}!D=*o__1kNw%%Nf}up z4j}btSR4+QtI*_KY#S6*3L5Lq-`#ftqFe;8Ye$q6nx?B2R9EMPu0m(DP|NSf7!$0b zdcmg_+W>w(iO?+-i_$JQs7tFZ8Lugk`c5wg9l-lvEyc&j{fHEVBpjF#E2FCQm>3}e z0Si!9;x3pYMdB6osI08a5lcAH_h}eEJ3rt1gID-Z<538GbQLsu#*6l7Es6DiJRFyr3hV9E847t|!k=xi)_<{^(bFyik$m{pmfhkEw`R-=5i+zw3W?R#mi z`)ik=v4C2)T^L?}Vu!B))BbMU0)4dWUlR=1=^SEzso92imlj<#%wADDwaqhJT!zbU zRm8^jH$@sl5+VTKuUk=wWoyWnToUh&8NgWw_r9q9`cHplFW^Xk=fWFab4u)JZV{^T@AGD(O{xaUy zLu0^6yz$)*Up@EBQ0!MzR#ZW{dY~#1c2z~C7P*w!$oOsO(ulayWd63x+FbV?m<;GQ z`U#ED&|QR}U(?ewcPT5kM75%>k4G^YqUhWFE)8L}nKR!H(@c`m+k8EJ9JH!~QiZLB zp&Sn6H)!@|+jRTzM~^xtaogM5xn}V;Qy1b_wj}}QTn9?sdP<3m+zhRIvoA%pR5dl< zKfwFuT*Wq5r~Gkl{cYI^|*_UFaa((Avz(CS4zZgPWy0qgPE zzOdVyfxIQcaa1dq5eyvn+4p&M&gMGH598@uuR2^Vh&gLLd-@o_;@nh=b?yD&tK*TI z8SDcG{l5NveKqzmzKZDPGU@^v+U4sFlkkr=0eETS^?wKLF4JN>l0`b%wWIQNV4WD9 z$8uGb{dTliKWmk$*v;Oh-K$lz^7d2$JHDY{LB||`gpiPsl{s>Ae?mN*1hx^sx0h=8 zW}qoolM~}b?@np?mZL>mu>DHomyhxc3BTSMTLmhbPdWeq%f&VP6D%RZO7uq(*LpMFv92IbEb{wX0`?6b48!*)JM z1H=t43V)L#OmO!l|0JWJ#=Ird3xL6%ern&c_;U21Iv`B}WER*CJx zf}ej7n42K#7o;?HZHO#9^@n#xEP-*q1k5tvXsqkKS@LP?L~795N_atywX1KUYp%wl z+;7YNm3R)Pb)RyD?I6jNCv7M6ifO^odNRS7V!!$eY_JD(vmlfq?h}yow zujFbmk2LR&5!Uc82A#O7NCsVO^M#Zqj|@qhK@c0>ilIGD+SiXX6wrU$Rc= zj;9BYNeX=Dw7dfzgCA(NsekS#7jzBJ$g*7yFPD=rJ)y0ZJ2m+?!7eSaQ$KqJM!SS@ zX={072Orb;;S=I{xl?zQr-1>@0>2nttNi2dQ=J6LN0++03&ZCX&U|y;HxV?la`4!A zR605R?vWj*Ppk60yM7eM+g;}sZBBn%qdHUExsfZT{w4k-4tcYpt#Rn$|G;AB&4Gs9 zDzi*YNn?cnw#&dykjM9@F;0wtqP07?6&IV`rRtYidOrpVgXs5d!g`(bSe?mK*(ct` zVc}tGYdw1d8-mq2^q&DM&%!=3GBRxYJAcT^6jz3Jq~_xE4cyH-y}(i7RaI5>r-5Vj z9};xwmmiNsIp=l0#d!4gN+cZpx}4}(GUwM+6%a)_It1G5We zK8}Xb2@SIp%7+?lQRoY)Ce+#xJSC_e(4D1W{p2gxkC!)D{Rd_-_OAW0A3xTlvxwg^ zg0L@ZwA%6kvNwRL@8OS;bs__jTtGWA`_h?^b}6UN{iEw(Mf=rBmG4Qqd%&o11OfzY$piNTk|qSijr*QZZr z=-#z%+ z3qyD1cBjIsDm9(utx3%?6frD3d@}Gb z`Y6cJVBf_lhuvnLSd9-agw@WzJA}5dFLCYTFx2gShaCGw;O0+hqBV`mVS|hC!14+6 zoH1jbZlGVkjY>eWO&J%^vHn3p1FM~SrIZ%q7x8gt@d{`?LfCr20n}Dd!}`WZ?SH?c z_sj1t`#xA>+78zP#;Z)h#1i5hSh%?H0qpOsWLVeHQLw`Gpi-X*MilRXl2nOUR$zp> zG)6csozVS%?Ol5`Ro}PY9+CQr_?8eyp17^JJ;^sG45}SJI1|#+g%Y`K9}ruPGt`zOlD|T37*fv zGCvjA0}*#vK2d(6X6aDJbf(j}ew9hTfatS_yTZ$56*Gd4^T!G*$l zt+bTK^UB8wlk9=My4I1hEI@ei@b%qtWwPu7EB;<_YCs1;qj2XskV$vVzB^3u*t2|~ zxxPNx_*sAl-E%w6t8r$vw<`uTVy=>4Dj)R&l8A;@X*yg};CF82jdty7D-Ceay%J7S zy1(u~5i_8j`Vhz2x4{`z*HGndr7uyN8yp;LT#uh~6a|QsX8}XI*Hvp8FwTzl3n`IO zI{f)qf%hRAz;X;WYEfGXvK)HODQ*#n(Cw{FYS}+hn!Ab;lnO9O7Ln%DE3!A&+JdM3 z=#ft8_;?Mc)l$pz?Af~`8?i^=<|>^5qtP3#;MiJj4^rLuO@hk$e7YB)PL!_8R$S04 z0&J4@VBBz~oW94NTOQ`8zRu-yQ`7ywS_=nX@OfY6T8^<=TjgQ2kVs%O>uBfMgSvTD ze!7diyP(<0mXb3=a!rt0*yrW{jeAak-g_{J{Z_3ccqndd<@t!+ulMn{Z8KxPoTwel zg#ot~p0EY~_N$c;W&ulU{@vkWZ(dJ-TK>64v!PFAR>1me2ped6q}C#IiHKA$_wqs` zcJqGDbahf6fLlf0{YmLRqVm>Jwz__x-O2dTv0MD9cQthGBV#aqKLQuhEMbLw6l>h<)na{&#Oupgnw)E!6Dl)gFU~oC+m_SQ z;yn444S5#c$ZHwxT`D$x0mdHhz3M zEwS~P1Ms8q)2DPJ`|KOf?qQNQ5R;FFmOOh_5ZICe5FS~H^l>pHQ$a-6Eu(V-cfbKI z$j@gh$=aM-TP6)3;tRifOXaPUn8!18IN=@7X=e9>JIH?3 z703n*cdxTNdU1|?fUN*?Em%*fl+x0J2+IYin*>A}qkwPEcP1Dlr%;K%8;{lY!DkRa zW5>r;-ZH-9Z^bb2-gAlToSvr>!94Mx@&2@ub6Jwys5@Ja{4Tj9S#C~AHnUQWQ3$oNzeMuM59^0je1UfxxZd8$0?N|x2`s7XtSn1d z59H((nkE8e{3m{BwmtW^?ERS3(o+-b-}OVht;pX}ADpc0*203KqSanQ;}q-WRpxIl zKJWDiKI~+9)6!R8B^pQq!_4>Zvr?Fyae+90|EYwt=(c=Y zxNkm$@EneP7H(Azli~q~-sR*D(_q*PKd|hV5jNfwnhkw+KSFuT7omR_WVPx|rYEPTsca5q4|a+0IF{X% z(Rj>n>ZhRs)qi)hOV2g>qviothAo{}7tdi&)cVhT{A+LP5oK7{`Z`&uQ>C0taErc| znY!y9lU!LR}H@mJ$ zYRN6jEe2J&qr@vyHl(NRGpE8%igdM}k(fC$!4@%AN789p@*)e#k66BEi=W4eRghRyHOe z)Di#-fiYj-SK&Qg&c%5j^c@fE4K<#jC@3_sx>HrvR9Q`*Wv5jHF{Lp`NnjDaH*enL z%*HyiYxNiPXXTbRL-Zah-Eo|V)Z1wEcZh9NT%7U@WXPS!)`!W~z0PdhNh@@=R!j4B z2`dTY@;5K~sqMB=QNoA>vM@&1)7N*`Hk`AM;N{}|gCk{8v9;9{u7t#3Cp-Va>N)5V z6@_CFTP}AJYCN3a#+J?T=VIpbx0%J%@PW<`mvHC|Z6#p3M-Ly~LRB=*1jFecG|iKE zS7<&;d|}^tP*n{8b(p&(7-P)!&C8b;gJ;IJ@sjY;9B{RECCcEVR3qJo{TVNJ<85ti zzRoM5SnQ@SG?sk?U}ex|3((gxS!qput;m&kLHVqV3=1m-1EwO9A30c0pshBQN+``x zIgQqjVtMi|3taeWY8!kz8FV_*LQrf)MCER8?_ehs>ogR`5V?9X&5};t*9eMuE1e zAujGChkOJ8lsG?Heb~7z68*i-cvC~e?w=LJr%!huELvW7A!u$s8WUNooXYz8HKB|@ zwVyW_wv}^#-bK6`a14zyL5@&nXL54#bDZ5W z&SYW75plkoO7*g^@NjoeE-CUlu*hSycVC|cSUU&v3fj*OYV!Cb{9#qgL~c(f4#Fqh zWkeg5z-Y%wqF27|Bql+7=f=7m9YY`WI$!R@U0qFWySn0Bp8~6nKov2RTonb3l$j6% z8tBzVXMUc2?nX3>A16|b3^rtp@#dNu8x=_DYa}}r3>ax^M2^MQtL>~YX*I`{GZb0b zdap)XxsGz{wl*YBzX~A50;by!22IVzLb$8%4=GN~o{2FLLRU^!vAIKHrx$qa{sI0Z zGZx0rzLr-k^lIe z@kALA{N{Jdwymn%%Bl7e?Vp8)^N_Jox_}aAvog+oNj}e$ZPRwXtqIf(m);*3sw1c9 zn2U3HBXI_K&IkYci%B-d}$;sX+|b(sABW%{nP^^7zAwa%FLG@qP+UJOgt^ zQt~JHf-S@EKMr(3(yn|_X$Az0Yh&B1_f7fe>FK2sGu0g%wtS}kn}rnf?fs|_#O+T* z*4^*N&(EJia0@v_xd#0CR9w|G#U?-o*@ApsBoyo?_BCUkNB%WuSL{S(8C4K{Z}RxT zqZ`6*Npm5>5r)+RbN5AXlDBV}G{=uTZxe`N1B zI@Nt`QRzfDpm366qF}nWV#un-7E%| z*u((h(jb7t+=M$l-o4`y5wb!Q5TN1`N&OC+WJ^6i^L;~_+eF5W*ZDma;}RZYtwLvY*P$dASg%# z>*NE8_zdlOJoE|A(k#U#B`)r69V~?Ok~LsJm8_7UONQL5iJfep8yPIEqzt@ks?qiR zlh0W-Ly?znV!tYSpsLEULIcS_Ydk|WzSz%VqzvCXLNR%$;gI|8mEE}3k`UbExSy%0 znws7Qo#dQ#BrPOktn^OzN45u#7?zafsl;ZE=! z8ajyrFuYbf)1%Oovmh-1=NbwgQ{(k!&(jpBi;WO`p#`Kp6z^l{fJ_oogxx_8rygY6zpDOa=I9 zg^I8kMmklu3+!dqIkA%UI~qi{EvUf1x)Ej#@HLG^JFPrD&mU+rHuiN^hshE`)Uqz< zm^C{>)_IFh*~C*5gHxu3-k>2qp^*RGV7wuN6b4<=8PaeKy?reEW8$W?RlUE=L-u}6 z?Qcn7b!6+=-Ir9WCDLON!*(Esy;uO-Fg%Vwl2imv3Hv#_7qYn$;^H235XQObtim+? z;Tn*#BZ%+_`sMbMt$Dmn;VFKvnL_~}aNKo-y-G*4 zQ;ZF~nAoi8&{kdT3C>_R&9sPrxL_LZyK7Pt{zUGJyh9FQPs5Gz7MuUxt6chd6@X;HKw z(hOj~1B*BW$l~-9o{uHD(^6o(7$?yIg9w{Sbd~OT?kVXnNs`PH%Ma$!OV6Bij1pax zeid0U8K!ZrRbw;@Omj z4(ClM>-G)};lnauJ8dCYfx#Auff3QP4WMv}bh!0m0a}lkuYV6Az9p|v*ldvfc1#E< z%^`$O0X9uhn5e8ot7wK097w?!9M~TQqXQ$@a zmQ$G-*ToUkb`p77qK8>dauThuf*Wxa;01a6$|= z+$Wu}I#W~+KnpRR&%b(ggP~5c2QpNWLWoXxrf3CG7Xj9;57un#Q8Lm?ku@R&$OXVJ zvnqbIrI5ygrZYoXiKJ4KG~ND3v!nN{m`O@ZNc{;E<_rIT4w>PKcX+wZkWLgU-MHa? zB1wTH6}>AxO4JSJ4_sc7bNRoM7elV9a^vsTf$IWM;Pq$wprHhs(nFWGhY-+>}Z__d8`gF-fC4%D0u`m9lw+!HL+l7 z%e`Qz;tpNI=28rp{bOR;PNhb}w&*M)>4B*)0SWrH0tH|h*L|~xk&A(4llrh{wK!i* zVtE~_yNF{y{Thh_<8cCH8Zp}L&A`-{5aQQD6tN5`l}&Pk7@=CN#@dEl4|CGTwsYrk zCgT(-Ei7SakXopinh!0P>@5$rX_W4YOF*##_SO`Ym$?Dz*Fa~t-QiyqDAiyan;7;Q}fc z8AXm9^89I;q}sfK{apx^NF93zSlk%HZBRr6dIR&XMz$M(8U^glX0L1!5fQ(WbVt#& zH1fJSAWUFy)@oQZd-6J&iGoT zKqpq@T``GwNTeU(Nlx*qh=ZSB>N1c&pBPnB zgtT5M1Zn+lAO8_gv>lMWzi=mPlfa${+**osKBEw}7I|Kr?h@~>R?x>aREb6#FbuzK ziWvJi3^5jd|49uLYVu#MdP0r=<5H^s=582fx@37cnC9W+dTlgb`2D{e3Q0NbKPe|e zjs8~{`%h{}4*WwIAiDB@PX+!tQ~#VP#DRaTJ4ILiCk(~*&&Y28KPjiEx?1SJW^X57 zcCUm-Xf7vn!%CC{7-o58%r(5lKC0@5G-BwY`9#Sv*QvkA;m!Tuj>PA@EB#y?Iq8+=L=2Vq84*qO{=dlY#(z@e5lb-R%CB02_~BJ3%IUgh{nj7v@_o zP!y506SFxHf7=%-&BK_=w6SBvnZ%La=NAJB%P1@iITaWdW^+RD1%)fRN1!_>H+EB^ z>F6!>2#n$Eame}nTAtx2t!!+y13(<;FjKCoiRr1#>6B4-yY&#t$PuiY|MX(ZMF5y= zTNo%nbr|2am?~<#uEiT||FSaUUQmy3U^FN*#Aa7@)ZU*z0}>&5HP3DKor=IaaiMh8 z#h4vYxilbR7NNNaGy zBSk=dZbVPb$VA&?=Jlog*X?|`oOLyXf2xPzC#58!sUG+cb1tXhFc!UI(cZTR^u5 zi=+sQ7Lms&pNV7lS_o8{{NzUQXRCRllN^UHbJpMj!ei;KSNPknyt$$xtgf)YrhinJ zCMAY(lOxYzFJt%`3UW4I<|x09#(IxXrmaW7<;EiaZdS*vd!z++?ZI}P7PL`dn8Pe( zFgrV_qO|_g_bc$R5oRO-M}DrilFh9! za(lr49 z)P}bhuXa!5{A*vHHk`pEBzLUXQLH#~s%I|x&WwX@7nLwWG%EL~&U?rn*}3(AjLYuP>^HF*9l3fIHp4}zO@nFn zZ<139*(c}nst?x+2s^IMYIw{&Wz32GI9mG&aZPzwaWVEXQA4KNVbd@K)5-HQZLWJp zVY{fYfto*|ULPh-U7TN2YBqYSM5*o%mCT{&+%pGoR{2(b7>JpY#qsFeJ#oV6-B5Hc zdA)N%bf$`VuAfu|{V93oC&p1|*imgPYhy9C`Bm660rLdMs=i$1L+BU;MvJtQy4JG( zh1%%0*Yk61Zk{5%?K$*Yr=x46XJDkQuc57Nq^+%ehIRTM9ysCS e>T&ekfAE08uLlZUma8J>Sp2!uH0O^)7ykttxlJJe literal 0 HcmV?d00001 diff --git a/resources/linux/application-ubz.svg b/resources/linux/application-ubz.svg new file mode 100644 index 00000000..0320a2c8 --- /dev/null +++ b/resources/linux/application-ubz.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 333b2eb3223ceb4c321602a30e0ba5627fd22825 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Thu, 24 Feb 2022 15:17:07 +0100 Subject: [PATCH 073/130] prevent the same error to popup again and again if something went wrong while trying to open the virtual keyboard --- src/board/UBBoardController.cpp | 6 ++++-- src/frameworks/UBPlatformUtils.cpp | 4 +++- src/frameworks/UBPlatformUtils.h | 2 ++ src/frameworks/UBPlatformUtils_mac.mm | 7 +++++++ 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/board/UBBoardController.cpp b/src/board/UBBoardController.cpp index 3d1e4fa3..25b7ab8c 100644 --- a/src/board/UBBoardController.cpp +++ b/src/board/UBBoardController.cpp @@ -875,7 +875,6 @@ void UBBoardController::showKeyboard(bool show) UBPlatformUtils::showOSK(show); else mPaletteManager->showVirtualKeyboard(show); - } @@ -2171,7 +2170,10 @@ void UBBoardController::stylusToolChanged(int tool) if(eTool != UBStylusTool::Selector && eTool != UBStylusTool::Text) { if(mPaletteManager->mKeyboardPalette->m_isVisible) - UBApplication::mainWindow->actionVirtualKeyboard->activate(QAction::Trigger); + { + if (!UBPlatformUtils::errorOpeningVirtualKeyboard) + UBApplication::mainWindow->actionVirtualKeyboard->activate(QAction::Trigger); + } } } diff --git a/src/frameworks/UBPlatformUtils.cpp b/src/frameworks/UBPlatformUtils.cpp index b5335f8f..74a17b95 100644 --- a/src/frameworks/UBPlatformUtils.cpp +++ b/src/frameworks/UBPlatformUtils.cpp @@ -66,7 +66,9 @@ UBKeyboardLocale::~UBKeyboardLocale() int UBPlatformUtils::nKeyboardLayouts; UBKeyboardLocale** UBPlatformUtils::keyboardLayouts; - +#ifdef Q_OS_OSX +bool UBPlatformUtils::errorOpeningVirtualKeyboard = false; +#endif UBKeyboardLocale** UBPlatformUtils::getKeyboardLayouts(int& nCount) { nCount = nKeyboardLayouts; diff --git a/src/frameworks/UBPlatformUtils.h b/src/frameworks/UBPlatformUtils.h index 8e6b91ed..6d8ad70c 100644 --- a/src/frameworks/UBPlatformUtils.h +++ b/src/frameworks/UBPlatformUtils.h @@ -213,6 +213,8 @@ public: #ifdef Q_OS_OSX static void SetMacLocaleByIdentifier(const QString& id); static void toggleFinder(const bool on); + + static bool errorOpeningVirtualKeyboard; #endif }; diff --git a/src/frameworks/UBPlatformUtils_mac.mm b/src/frameworks/UBPlatformUtils_mac.mm index 34fe2d72..0b208c06 100644 --- a/src/frameworks/UBPlatformUtils_mac.mm +++ b/src/frameworks/UBPlatformUtils_mac.mm @@ -684,6 +684,7 @@ void UBPlatformUtils::showOSK(bool show) tell application \"System Events\"\n\ tell application process \"TextInputMenuAgent\"\n\ tell menu 1 of menu bar item 1 of menu bar 2\n\ + delay 0.2\n\ click menu item 2\n\ end tell\n\ end tell\n\ @@ -696,6 +697,8 @@ void UBPlatformUtils::showOSK(bool show) if(errorInfo!=nil) { + errorOpeningVirtualKeyboard = true; + NSAlert *alert = [[NSAlert alloc] init]; if (alert != nil) @@ -711,5 +714,9 @@ void UBPlatformUtils::showOSK(bool show) UBApplication::mainWindow->actionVirtualKeyboard->setChecked(true); } } + else + { + errorOpeningVirtualKeyboard = false; + } } } From b921d083d1c063fe303c393bbc870e1bf946ccef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Thu, 24 Feb 2022 15:43:33 +0100 Subject: [PATCH 074/130] updated german translations --- resources/i18n/OpenBoard_de.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/resources/i18n/OpenBoard_de.ts b/resources/i18n/OpenBoard_de.ts index aaa9a3ba..28662ec0 100644 --- a/resources/i18n/OpenBoard_de.ts +++ b/resources/i18n/OpenBoard_de.ts @@ -898,11 +898,11 @@ Loading scene (%1/%2) - + Laden der Szene (%1/%2) Moving cached scenes (%1/%2) - + Verschieben von zwischengespeicherten Szenen (%1/%2) @@ -1252,7 +1252,7 @@ Refreshing Document Thumbnails View (%1/%2) - + Aktualisieren der Vorschauen im Dokumentenmodus (%1/%2) @@ -1306,7 +1306,7 @@ Generating thumbnails for board (%1/%2) - + Erstellen von Voransichten des Tabellenmodus (%1/%2) @@ -2046,7 +2046,7 @@ Möchten Sie diese Fehler für diesen Computer ignorieren? Renaming pages (%1/%2) - + Aktualisieren der aktuellen Seitennamen (%1/%2) @@ -2218,7 +2218,7 @@ Miniaturansicht der Seite %1 wird geladen Loading thumbnail (%1/%2) - + Laden der Vorschau (%1/%2) From c3430d397520c8fc81b75c3ce9f555886b8577a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Fri, 25 Feb 2022 11:17:17 +0100 Subject: [PATCH 075/130] updated version to 1.6.2rc-0225 --- OpenBoard.pro | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenBoard.pro b/OpenBoard.pro index 85233990..bb13a4b9 100644 --- a/OpenBoard.pro +++ b/OpenBoard.pro @@ -10,8 +10,8 @@ CONFIG += debug_and_release \ VERSION_MAJ = 1 VERSION_MIN = 6 VERSION_PATCH = 2 -VERSION_TYPE = a # a = alpha, b = beta, rc = release candidate, r = release, other => error -VERSION_BUILD = 1118 +VERSION_TYPE = rc # a = alpha, b = beta, rc = release candidate, r = release, other => error +VERSION_BUILD = 0225 VERSION = "$${VERSION_MAJ}.$${VERSION_MIN}.$${VERSION_PATCH}-$${VERSION_TYPE}.$${VERSION_BUILD}" From a277bf78a3bada21e1c2b50b37169e584159884c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Fri, 25 Feb 2022 14:20:18 +0100 Subject: [PATCH 076/130] forgot to check OS --- src/board/UBBoardController.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/board/UBBoardController.cpp b/src/board/UBBoardController.cpp index 25b7ab8c..f6d01637 100644 --- a/src/board/UBBoardController.cpp +++ b/src/board/UBBoardController.cpp @@ -2171,8 +2171,12 @@ void UBBoardController::stylusToolChanged(int tool) { if(mPaletteManager->mKeyboardPalette->m_isVisible) { +#ifdef Q_OS_OSX if (!UBPlatformUtils::errorOpeningVirtualKeyboard) UBApplication::mainWindow->actionVirtualKeyboard->activate(QAction::Trigger); +#else + UBApplication::mainWindow->actionVirtualKeyboard->activate(QAction::Trigger); +#endif } } } From e7b707d5913377aea8e93f3b90d7393ec62f5747 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Fri, 25 Feb 2022 15:30:39 +0100 Subject: [PATCH 077/130] update document thummbnail when a new page is inserted --- src/board/UBBoardController.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/board/UBBoardController.cpp b/src/board/UBBoardController.cpp index f6d01637..79bbd3ce 100644 --- a/src/board/UBBoardController.cpp +++ b/src/board/UBBoardController.cpp @@ -524,6 +524,7 @@ void UBBoardController::addScene() if (UBApplication::documentController->selectedDocument() == selectedDocument()) { UBApplication::documentController->insertThumbPage(mActiveSceneIndex+1); + emit UBApplication::documentController->documentThumbnailsUpdated(UBApplication::documentController); } selectedDocument()->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(QDateTime::currentDateTime())); From 02aca3cb9cc449fa7ef77a7c9303712c0ddcd25a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Fri, 25 Feb 2022 17:20:24 +0100 Subject: [PATCH 078/130] added delay to prevent failure (sometimes the scripts fails making an action in time so the next fails too etc...) of virtual keyboard opening in macOS 11 --- src/frameworks/UBPlatformUtils_mac.mm | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/frameworks/UBPlatformUtils_mac.mm b/src/frameworks/UBPlatformUtils_mac.mm index 0b208c06..6e2acd70 100644 --- a/src/frameworks/UBPlatformUtils_mac.mm +++ b/src/frameworks/UBPlatformUtils_mac.mm @@ -661,10 +661,15 @@ void UBPlatformUtils::showOSK(bool show) { NSString *source = @"tell application \"System Events\"\n\ + delay 0.1\n\ if application process \"TextInputMenuAgent\" exists then\n\ + delay 0.1\n\ tell application process \"TextInputMenuAgent\"\n\ + delay 0.1\n\ tell menu bar item 1 of menu bar 2\n\ + delay 0.1\n\ ignoring application responses\n\ + delay 0.1\n\ click\n\ end ignoring\n\ end tell\n\ @@ -681,10 +686,13 @@ void UBPlatformUtils::showOSK(bool show) } source = [source stringByAppendingString:@" running then\n\ + delay 0.1\n\ tell application \"System Events\"\n\ + delay 0.1\n\ tell application process \"TextInputMenuAgent\"\n\ + delay 0.1\n\ tell menu 1 of menu bar item 1 of menu bar 2\n\ - delay 0.2\n\ + delay 0.1\n\ click menu item 2\n\ end tell\n\ end tell\n\ From 8d3c421e484e3bb51401b94c8248f879fd209603 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Mon, 28 Feb 2022 13:47:25 +0100 Subject: [PATCH 079/130] delay is needed after click not throughout the whole script --- src/frameworks/UBPlatformUtils_mac.mm | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/frameworks/UBPlatformUtils_mac.mm b/src/frameworks/UBPlatformUtils_mac.mm index 6e2acd70..7eae208e 100644 --- a/src/frameworks/UBPlatformUtils_mac.mm +++ b/src/frameworks/UBPlatformUtils_mac.mm @@ -661,16 +661,12 @@ void UBPlatformUtils::showOSK(bool show) { NSString *source = @"tell application \"System Events\"\n\ - delay 0.1\n\ if application process \"TextInputMenuAgent\" exists then\n\ - delay 0.1\n\ tell application process \"TextInputMenuAgent\"\n\ - delay 0.1\n\ tell menu bar item 1 of menu bar 2\n\ - delay 0.1\n\ ignoring application responses\n\ - delay 0.1\n\ click\n\ + delay 0.5\n\ end ignoring\n\ end tell\n\ end tell\n\ @@ -686,13 +682,9 @@ void UBPlatformUtils::showOSK(bool show) } source = [source stringByAppendingString:@" running then\n\ - delay 0.1\n\ tell application \"System Events\"\n\ - delay 0.1\n\ tell application process \"TextInputMenuAgent\"\n\ - delay 0.1\n\ tell menu 1 of menu bar item 1 of menu bar 2\n\ - delay 0.1\n\ click menu item 2\n\ end tell\n\ end tell\n\ From 8fe3dce0d2ee52678f98ecdbc556053a6d13dea1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Tue, 1 Mar 2022 15:12:08 +0100 Subject: [PATCH 080/130] updated italian translations --- resources/i18n/OpenBoard_it.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/resources/i18n/OpenBoard_it.ts b/resources/i18n/OpenBoard_it.ts index 9f6be1ba..50a151a3 100644 --- a/resources/i18n/OpenBoard_it.ts +++ b/resources/i18n/OpenBoard_it.ts @@ -898,11 +898,11 @@ Loading scene (%1/%2) - + Caricamento della scena (%1/%2) Moving cached scenes (%1/%2) - + Spostamento delle scene in cache (%1/%2) @@ -1248,7 +1248,7 @@ Refreshing Document Thumbnails View (%1/%2) - + Aggiornamento delle anteprime in modalità Documenti (%1/%2) @@ -1302,7 +1302,7 @@ Generating thumbnails for board (%1/%2) - + Creazione delle anteprime in modalità Lavagna (%1/%2) @@ -2038,7 +2038,7 @@ Vuoi ignorare gli errori per questo host? Renaming pages (%1/%2) - + Aggiornamento dei nomi delle pagine in corso (%1/%2) @@ -2209,7 +2209,7 @@ Vuoi ignorare gli errori per questo host? Loading thumbnail (%1/%2) - + Caricamento dell'anteprima (%1/%2) From e963d58a0cd4a9c229d0c7c08bfac4868fc0a949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Tue, 1 Mar 2022 15:29:43 +0100 Subject: [PATCH 081/130] updated version to 1.6.2rc-0302 --- OpenBoard.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenBoard.pro b/OpenBoard.pro index bb13a4b9..82f63778 100644 --- a/OpenBoard.pro +++ b/OpenBoard.pro @@ -11,7 +11,7 @@ VERSION_MAJ = 1 VERSION_MIN = 6 VERSION_PATCH = 2 VERSION_TYPE = rc # a = alpha, b = beta, rc = release candidate, r = release, other => error -VERSION_BUILD = 0225 +VERSION_BUILD = 0302 VERSION = "$${VERSION_MAJ}.$${VERSION_MIN}.$${VERSION_PATCH}-$${VERSION_TYPE}.$${VERSION_BUILD}" From a35e4b343c3f803ee11d1c2ff9c3fa6576e4c5b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Tue, 1 Mar 2022 15:40:16 +0100 Subject: [PATCH 082/130] fixed unsupported character in hu translation --- resources/i18n/OpenBoard_hu.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/resources/i18n/OpenBoard_hu.ts b/resources/i18n/OpenBoard_hu.ts index 6a04afa0..fd17cfc2 100644 --- a/resources/i18n/OpenBoard_hu.ts +++ b/resources/i18n/OpenBoard_hu.ts @@ -886,7 +886,7 @@ Are you sure you want to remove 1 page from the selected document '%0'? - Biztosan szeretné eltávolítani 1 oldalt a kijelölt „%0”-dokumentumból? + Biztosan szeretné eltávolítani 1 oldalt a kijelölt '%0'-dokumentumból? Element ID = @@ -1121,7 +1121,7 @@ Are you sure you want to remove the document '%1'? - Biztosan szeretné eltávolítani a(z) „%1” dokumentumot? + Biztosan szeretné eltávolítani a(z) '%1' dokumentumot? Empty Trash @@ -1145,7 +1145,7 @@ Are you sure you want to remove the folder '%1' and all its content? - Biztosan szeretné eltávolítani a(z) „%1” mappát és az összes tartalmát? + Biztosan szeretné eltávolítani a(z) '%1' mappát és az összes tartalmát? No document selected! @@ -1197,7 +1197,7 @@ Are you sure you want to remove %n page(s) from the selected document '%1'? - Biztosan szeretné eltávolítani %n oldalt a kiválasztott „%1” dokumentumból? + Biztosan szeretné eltávolítani %n oldalt a kiválasztott '%1' dokumentumból? Folder does not contain any image files @@ -1209,7 +1209,7 @@ The document '%1' has been generated with a newer version of OpenBoard (%2). By opening it, you may lose some information. Do you want to proceed? - A(z) „%1” dokumentum az OpenBoard újabb verziójával (%2) készült. Megnyitásával néhány információ elveszhet. Szeretné folytatni? + A(z) '%1' dokumentum az OpenBoard újabb verziójával (%2) készült. Megnyitásával néhány információ elveszhet. Szeretné folytatni? Are you sure you want to remove all selected documents? @@ -1946,7 +1946,7 @@ Figyelmen kívül hagyja ezeket a hibákat ennél a hosztnál? has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. - elveszítette a(z) „%1” dokumentum repository hozzáférését. Sajnálatosan az alkalmazásnak le kellett állnia, hogy elkerülje az adatsérülést. A legutóbbi változtatások elveszhettek. + elveszítette a(z) '%1' dokumentum repository hozzáférését. Sajnálatosan az alkalmazásnak le kellett állnia, hogy elkerülje az adatsérülést. A legutóbbi változtatások elveszhettek. Moving page to trash folder... @@ -1954,7 +1954,7 @@ Figyelmen kívül hagyja ezeket a hibákat ennél a hosztnál? OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. - Az OpenBoard elvesztette a hozzáférést a(z) „%1” dokumentumtárához. Sajnos az alkalmazást le kell állítani az adatvesztés elkerülése érdekében. A legújabb változások is elveszhetnek. + Az OpenBoard elvesztette a hozzáférést a(z) '%1' dokumentumtárához. Sajnos az alkalmazást le kell állítani az adatvesztés elkerülése érdekében. A legújabb változások is elveszhetnek. Renaming pages (%1/%2) From 9e61b66fc1941e2e11acc0c33389309489c798a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Wed, 2 Mar 2022 12:16:20 +0100 Subject: [PATCH 083/130] Revert "fixed unsupported character in hu translation" This reverts commit a35e4b343c3f803ee11d1c2ff9c3fa6576e4c5b0. --- resources/i18n/OpenBoard_hu.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/resources/i18n/OpenBoard_hu.ts b/resources/i18n/OpenBoard_hu.ts index fd17cfc2..6a04afa0 100644 --- a/resources/i18n/OpenBoard_hu.ts +++ b/resources/i18n/OpenBoard_hu.ts @@ -886,7 +886,7 @@ Are you sure you want to remove 1 page from the selected document '%0'? - Biztosan szeretné eltávolítani 1 oldalt a kijelölt '%0'-dokumentumból? + Biztosan szeretné eltávolítani 1 oldalt a kijelölt „%0”-dokumentumból? Element ID = @@ -1121,7 +1121,7 @@ Are you sure you want to remove the document '%1'? - Biztosan szeretné eltávolítani a(z) '%1' dokumentumot? + Biztosan szeretné eltávolítani a(z) „%1” dokumentumot? Empty Trash @@ -1145,7 +1145,7 @@ Are you sure you want to remove the folder '%1' and all its content? - Biztosan szeretné eltávolítani a(z) '%1' mappát és az összes tartalmát? + Biztosan szeretné eltávolítani a(z) „%1” mappát és az összes tartalmát? No document selected! @@ -1197,7 +1197,7 @@ Are you sure you want to remove %n page(s) from the selected document '%1'? - Biztosan szeretné eltávolítani %n oldalt a kiválasztott '%1' dokumentumból? + Biztosan szeretné eltávolítani %n oldalt a kiválasztott „%1” dokumentumból? Folder does not contain any image files @@ -1209,7 +1209,7 @@ The document '%1' has been generated with a newer version of OpenBoard (%2). By opening it, you may lose some information. Do you want to proceed? - A(z) '%1' dokumentum az OpenBoard újabb verziójával (%2) készült. Megnyitásával néhány információ elveszhet. Szeretné folytatni? + A(z) „%1” dokumentum az OpenBoard újabb verziójával (%2) készült. Megnyitásával néhány információ elveszhet. Szeretné folytatni? Are you sure you want to remove all selected documents? @@ -1946,7 +1946,7 @@ Figyelmen kívül hagyja ezeket a hibákat ennél a hosztnál? has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. - elveszítette a(z) '%1' dokumentum repository hozzáférését. Sajnálatosan az alkalmazásnak le kellett állnia, hogy elkerülje az adatsérülést. A legutóbbi változtatások elveszhettek. + elveszítette a(z) „%1” dokumentum repository hozzáférését. Sajnálatosan az alkalmazásnak le kellett állnia, hogy elkerülje az adatsérülést. A legutóbbi változtatások elveszhettek. Moving page to trash folder... @@ -1954,7 +1954,7 @@ Figyelmen kívül hagyja ezeket a hibákat ennél a hosztnál? OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. - Az OpenBoard elvesztette a hozzáférést a(z) '%1' dokumentumtárához. Sajnos az alkalmazást le kell állítani az adatvesztés elkerülése érdekében. A legújabb változások is elveszhetnek. + Az OpenBoard elvesztette a hozzáférést a(z) „%1” dokumentumtárához. Sajnos az alkalmazást le kell állítani az adatvesztés elkerülése érdekében. A legújabb változások is elveszhetnek. Renaming pages (%1/%2) From 61b7b76e3017d927ff92a53a2f847b32272c5811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Wed, 2 Mar 2022 12:28:20 +0100 Subject: [PATCH 084/130] remove numerus attribute as plural takes the same form in hungarian --- resources/i18n/OpenBoard_hu.ts | 48 ++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/resources/i18n/OpenBoard_hu.ts b/resources/i18n/OpenBoard_hu.ts index 6a04afa0..0e96122d 100644 --- a/resources/i18n/OpenBoard_hu.ts +++ b/resources/i18n/OpenBoard_hu.ts @@ -1121,15 +1121,15 @@ Are you sure you want to remove the document '%1'? - Biztosan szeretné eltávolítani a(z) „%1” dokumentumot? + Biztosan szeretné eltávolítani a(z) „%1” dokumentumot? Empty Trash - Törölt elemek kiürítése + Törölt elemek kiürítése Are you sure you want to empty trash? - Biztosan szeretné kiüríteni a törölt elemeket? + Biztosan szeretné kiüríteni a törölt elemeket? Emptying trash @@ -1195,9 +1195,9 @@ Selection does not contain any image files! A kiválasztásban nincs egyetlen kép sem! - + Are you sure you want to remove %n page(s) from the selected document '%1'? - Biztosan szeretné eltávolítani %n oldalt a kiválasztott „%1” dokumentumból? + Biztosan szeretné eltávolítani %n oldalt a kiválasztott „%1” dokumentumból? Folder does not contain any image files @@ -1213,7 +1213,7 @@ Are you sure you want to remove all selected documents? - Biztosan szeretné eltávolítani minden kijelölt dokumentumot? + Biztosan szeretné eltávolítani minden kijelölt dokumentumot? Remove multiple documents @@ -1222,7 +1222,9 @@ duplicated %1 page duplicated %1 pages - %1 oldal másolata + + %1 oldal másolata + Remove Item @@ -1330,7 +1332,9 @@ Ennek a névnek a megtartása helyettesíti a dokumentumot. %1 pages copied - %1 oldal másolva + + %1 oldal másolva + @@ -1341,7 +1345,9 @@ Ennek a névnek a megtartása helyettesíti a dokumentumot. %1 pages copied - %1 oldal másolva + + %1 oldal másolva + Remove Item @@ -1360,13 +1366,11 @@ Ennek a névnek a megtartása helyettesíti a dokumentumot. Copying page %1/%2 - Oldal másolása: %1/%2 + Oldal másolása: %1/%2 - + %1 pages copied - - %1 oldal másolva - + %1 oldal másolva @@ -1613,7 +1617,7 @@ Ennek a névnek a megtartása helyettesíti a dokumentumot. Animations - Animációk + Animációk Interactivities @@ -1950,7 +1954,7 @@ Figyelmen kívül hagyja ezeket a hibákat ennél a hosztnál? Moving page to trash folder... - Oldal áthelyezése a törölt elemek mappába… + Oldal áthelyezése a törölt elemek mappába… OpenBoard has lost access to the document repository '%1'. Unfortunately the application must shut down to avoid data corruption. Latest changes may be lost as well. @@ -2066,30 +2070,30 @@ Figyelmen kívül hagyja ezeket a hibákat ennél a hosztnál? Username: - Felhasználónév: + Felhasználónév: Password: - Jelszó: + Jelszó: UBPublicationDlg Publish document on the web - Dokumentum közzététele az interneten + Dokumentum közzététele az interneten Title: - Cím: + Cím: Description: - Leírás: + Leírás: Publish - Közzététel + Közzététel From a73298f978d802ae46eebccdca163fd38131ad79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Thu, 3 Mar 2022 11:59:56 +0100 Subject: [PATCH 085/130] fixed apple script to take into account the case where multiple layouts have been added for visual keyboard --- src/frameworks/UBPlatformUtils_mac.mm | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/frameworks/UBPlatformUtils_mac.mm b/src/frameworks/UBPlatformUtils_mac.mm index 7eae208e..3138861f 100644 --- a/src/frameworks/UBPlatformUtils_mac.mm +++ b/src/frameworks/UBPlatformUtils_mac.mm @@ -685,7 +685,16 @@ void UBPlatformUtils::showOSK(bool show) tell application \"System Events\"\n\ tell application process \"TextInputMenuAgent\"\n\ tell menu 1 of menu bar item 1 of menu bar 2\n\ - click menu item 2\n\ + set nbItems to count menu items\n\ + if (nbItems = 4)\n\ + -- only one language so items are\n\ + -- 1. emojis&symbols n-2. keyboard n-1. separator n.preferences\n\ + click menu item (nbItems-2)\n\ + else\n\ + -- items are ... n-4. access keyboard n-3. separator n-2 display names n-1. separator n. preferences\n\ + -- target is in fourth position from bottom\n\ + click menu item (nbItems - 4)\n\ + end if\n\ end tell\n\ end tell\n\ end tell\n\ From beb4361a72fdcc9dbbbe3fbc49371c9955298383 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Thu, 3 Mar 2022 12:05:06 +0100 Subject: [PATCH 086/130] changed version to 1.6.2rc-0303 --- OpenBoard.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenBoard.pro b/OpenBoard.pro index 82f63778..e0c661ac 100644 --- a/OpenBoard.pro +++ b/OpenBoard.pro @@ -11,7 +11,7 @@ VERSION_MAJ = 1 VERSION_MIN = 6 VERSION_PATCH = 2 VERSION_TYPE = rc # a = alpha, b = beta, rc = release candidate, r = release, other => error -VERSION_BUILD = 0302 +VERSION_BUILD = 0303 VERSION = "$${VERSION_MAJ}.$${VERSION_MIN}.$${VERSION_PATCH}-$${VERSION_TYPE}.$${VERSION_BUILD}" From 758bd16da7829c2bc4c660f6d5bd5b3477ab47d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Fri, 4 Mar 2022 11:13:56 +0100 Subject: [PATCH 087/130] fixed missing calls to reloadThumbnails in some cases --- src/board/UBBoardController.cpp | 7 +++++-- src/document/UBDocumentController.cpp | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/board/UBBoardController.cpp b/src/board/UBBoardController.cpp index 79bbd3ce..306a2028 100644 --- a/src/board/UBBoardController.cpp +++ b/src/board/UBBoardController.cpp @@ -1428,12 +1428,15 @@ UBItem *UBBoardController::downloadFinished(bool pSuccess, QUrl sourceUrl, QUrl QStringList fileNames; fileNames << pdfFile.fileName(); result = UBDocumentManager::documentManager()->addFilesToDocument(selectedDocument(), fileNames); - emit documentThumbnailsUpdated(this); pdfFile.close(); } } - if (result){ + if (result) + { + if (UBApplication::documentController->selectedDocument() == selectedDocument()) + UBApplication::documentController->reloadThumbnails(); + selectedDocument()->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(QDateTime::currentDateTime())); } } diff --git a/src/document/UBDocumentController.cpp b/src/document/UBDocumentController.cpp index 693fbd85..94008f34 100644 --- a/src/document/UBDocumentController.cpp +++ b/src/document/UBDocumentController.cpp @@ -3049,6 +3049,7 @@ bool UBDocumentController::addFileToDocument(UBDocumentProxy* document) { document->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(QDateTime::currentDateTime())); UBMetadataDcSubsetAdaptor::persist(document); + reloadThumbnails(); } else { From 064ba615526553522ebfc426270a0c154cdbeb66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Fri, 4 Mar 2022 11:23:42 +0100 Subject: [PATCH 088/130] use simplified call to documentThumbnailsUpdated (with the good source) --- src/board/UBBoardController.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/board/UBBoardController.cpp b/src/board/UBBoardController.cpp index 306a2028..b3b26c8e 100644 --- a/src/board/UBBoardController.cpp +++ b/src/board/UBBoardController.cpp @@ -524,7 +524,7 @@ void UBBoardController::addScene() if (UBApplication::documentController->selectedDocument() == selectedDocument()) { UBApplication::documentController->insertThumbPage(mActiveSceneIndex+1); - emit UBApplication::documentController->documentThumbnailsUpdated(UBApplication::documentController); + emit UBApplication::documentController->reloadThumbnails(); } selectedDocument()->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(QDateTime::currentDateTime())); From 9169146e272023cad7e5accdd555de7d243d1d4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Fri, 4 Mar 2022 11:30:46 +0100 Subject: [PATCH 089/130] don't call showKeyboard if opening is in error (osx only) --- src/board/UBBoardController.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/board/UBBoardController.cpp b/src/board/UBBoardController.cpp index b3b26c8e..ae0d80fe 100644 --- a/src/board/UBBoardController.cpp +++ b/src/board/UBBoardController.cpp @@ -1934,7 +1934,12 @@ void UBBoardController::closing() mIsClosing = true; lastWindowClosed(); ClearUndoStack(); - showKeyboard(false); +#ifdef Q_OS_OSX + if (!UBPlatformUtils::errorOpeningVirtualKeyboard) + showKeyboard(false); +#else + showKeyboard(false); +#endif } void UBBoardController::lastWindowClosed() From bbf134d1fe22c7e395e06c3bb72d117611bdf824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Fri, 4 Mar 2022 13:55:21 +0100 Subject: [PATCH 090/130] fixed a bug where grouped items with file dependencies would not be properly copied when copying a page from a document to another --- src/domain/UBGraphicsScene.cpp | 92 ++++++++++++++++++++-------------- src/domain/UBGraphicsScene.h | 1 + 2 files changed, 56 insertions(+), 37 deletions(-) diff --git a/src/domain/UBGraphicsScene.cpp b/src/domain/UBGraphicsScene.cpp index 816ef1ee..5cb2808b 100644 --- a/src/domain/UBGraphicsScene.cpp +++ b/src/domain/UBGraphicsScene.cpp @@ -2473,9 +2473,53 @@ void UBGraphicsScene::setRenderingQuality(UBItem::RenderingQuality pRenderingQua } } +QList UBGraphicsScene::relativeDependenciesOfItem(QGraphicsItem* item) const +{ + 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){ + relativePaths << QUrl(UBPersistenceManager::imageDirectory + "/" + pixmapItem->uuid().toString() + ".png"); + return relativePaths; + } + + UBGraphicsSvgItem* svgItem = dynamic_cast(item); + if(svgItem){ + relativePaths << QUrl(UBPersistenceManager::imageDirectory + "/" + svgItem->uuid().toString() + ".svg"); + return relativePaths; + } + + return relativePaths; +} + QList UBGraphicsScene::relativeDependencies() const { - QList relativePathes; + QList relativePaths; QListIterator itItems(mFastAccessItems); @@ -2483,45 +2527,19 @@ QList UBGraphicsScene::relativeDependencies() const { QGraphicsItem* item = itItems.next(); - UBGraphicsVideoItem *videoItem = qgraphicsitem_cast (item); - if (videoItem){ - QString completeFileName = QFileInfo(videoItem->mediaFileUrl().toLocalFile()).fileName(); - QString path = UBPersistenceManager::videoDirectory + "/"; - relativePathes << QUrl(path + completeFileName); - continue; - } - - UBGraphicsAudioItem *audioItem = qgraphicsitem_cast (item); - if (audioItem){ - QString completeFileName = QFileInfo(audioItem->mediaFileUrl().toLocalFile()).fileName(); - QString path = UBPersistenceManager::audioDirectory + "/"; - relativePathes << QUrl(path + completeFileName); - continue; - } - - UBGraphicsWidgetItem* widget = qgraphicsitem_cast(item); - if(widget){ - QString widgetPath = UBPersistenceManager::widgetDirectory + "/" + widget->uuid().toString() + ".wgt"; - QString screenshotPath = UBPersistenceManager::widgetDirectory + "/" + widget->uuid().toString().remove("{").remove("}") + ".png"; - relativePathes << QUrl(widgetPath); - relativePathes << QUrl(screenshotPath); - continue; - } - - UBGraphicsPixmapItem* pixmapItem = qgraphicsitem_cast(item); - if(pixmapItem){ - relativePathes << QUrl(UBPersistenceManager::imageDirectory + "/" + pixmapItem->uuid().toString() + ".png"); - continue; - } - - UBGraphicsSvgItem* svgItem = qgraphicsitem_cast(item); - if(svgItem){ - relativePathes << QUrl(UBPersistenceManager::imageDirectory + "/" + svgItem->uuid().toString() + ".svg"); - continue; + UBGraphicsGroupContainerItem* groupItem = dynamic_cast(item); + if(groupItem) + { + for (auto child : groupItem->childItems()) + { + relativePaths << relativeDependenciesOfItem(child); + } } + else + relativePaths << relativeDependenciesOfItem(item); } - return relativePathes; + return relativePaths; } QSize UBGraphicsScene::nominalSize() diff --git a/src/domain/UBGraphicsScene.h b/src/domain/UBGraphicsScene.h index 6a22f14f..82e3a982 100644 --- a/src/domain/UBGraphicsScene.h +++ b/src/domain/UBGraphicsScene.h @@ -303,6 +303,7 @@ class UBGraphicsScene: public UBCoreGraphicsScene, public UBItem virtual void setRenderingQuality(UBItem::RenderingQuality pRenderingQuality, UBItem::CacheBehavior cacheBehavior); + QList relativeDependenciesOfItem(QGraphicsItem* item) const; QList relativeDependencies() const; QSize nominalSize(); From 173fe34355cd3cdb4607faeb1587a53663517f2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Fri, 4 Mar 2022 15:23:29 +0100 Subject: [PATCH 091/130] prevent a crash when trying to open an ubx file by double click or 'open with' actions --- src/core/UBApplication.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/core/UBApplication.cpp b/src/core/UBApplication.cpp index b5cdfcc4..d1ef381c 100644 --- a/src/core/UBApplication.cpp +++ b/src/core/UBApplication.cpp @@ -347,7 +347,7 @@ int UBApplication::exec(const QString& pFileToImport) applicationController->initScreenLayout(bUseMultiScreen); boardController->setupLayout(); - if (pFileToImport.length() > 0) + if (pFileToImport.length() > 0 && !pFileToImport.endsWith("ubx")) UBApplication::applicationController->importFile(pFileToImport); if (UBSettings::settings()->appStartMode->get().toInt()) @@ -575,7 +575,10 @@ bool UBApplication::eventFilter(QObject *obj, QEvent *event) UBPlatformUtils::setFrontProcess(); - applicationController->importFile(fileToOpenEvent->file()); + if (!fileToOpenEvent->file().endsWith("ubx")) + applicationController->importFile(fileToOpenEvent->file()); + else + applicationController->showMessage(tr("Cannot open your UBX file directly. Please import it in Documents mode instead"), false); } if (event->type() == QEvent::TabletLeaveProximity) From 443c03cbb605bccb80d47363a2e18391e4f8a7e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Fri, 4 Mar 2022 15:37:48 +0100 Subject: [PATCH 092/130] fixed naming of 'delete folder' actions --- src/document/UBDocumentController.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/document/UBDocumentController.cpp b/src/document/UBDocumentController.cpp index 94008f34..24371875 100644 --- a/src/document/UBDocumentController.cpp +++ b/src/document/UBDocumentController.cpp @@ -3513,7 +3513,7 @@ void UBDocumentController::updateActions() if (mSelectionType == Folder) { mMainWindow->actionDelete->setIcon(QIcon(":/images/trash-folder.png")); - mMainWindow->actionDelete->setText(tr("Empty")); + mMainWindow->actionDelete->setText(tr("Delete")); } else if (mSelectionType == Document) { @@ -3556,7 +3556,7 @@ void UBDocumentController::updateActions() else { mMainWindow->actionDelete->setIcon(QIcon(":/images/trash-folder.png")); - mMainWindow->actionDelete->setText(tr("Empty")); + mMainWindow->actionDelete->setText(tr("Delete")); } break; case EmptyTrash : From 7f815798f1c394876cedf0b2d90d9adf9dee1ad4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Fri, 4 Mar 2022 16:16:33 +0100 Subject: [PATCH 093/130] display version number in getInfo instead of app name --- release_scripts/osx/release.macx.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release_scripts/osx/release.macx.sh b/release_scripts/osx/release.macx.sh index 7bea5a01..456b3e90 100755 --- a/release_scripts/osx/release.macx.sh +++ b/release_scripts/osx/release.macx.sh @@ -192,7 +192,7 @@ rm -f "$APP/Contents/Resources/empty.lproj" # set various version infomration in Info.plist $PLISTBUDDY -c "Set :CFBundleVersion $VERSION" "$INFO_PLIST" $PLISTBUDDY -c "Set :CFBundleShortVersionString $VERSION" "$INFO_PLIST" -$PLISTBUDDY -c "Set :CFBundleGetInfoString $APPLICATION_NAME" "$INFO_PLIST" +$PLISTBUDDY -c "Set :CFBundleGetInfoString $VERSION" "$INFO_PLIST" # bundle Qt Frameworks into the app bundle notify "Bulding frameworks ..." From 836f0f27056aa38202c3380366a062c3d4d2ff80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Mon, 7 Mar 2022 16:17:06 +0100 Subject: [PATCH 094/130] improved thumbnails reloading (fixes somes cases and is more respectful of reponsibility) --- src/core/UBDocumentManager.cpp | 2 -- src/document/UBDocumentController.cpp | 18 +++++++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/core/UBDocumentManager.cpp b/src/core/UBDocumentManager.cpp index 40ab420c..f451e519 100644 --- a/src/core/UBDocumentManager.cpp +++ b/src/core/UBDocumentManager.cpp @@ -286,8 +286,6 @@ int UBDocumentManager::addFilesToDocument(UBDocumentProxy* document, QStringList UBGraphicsScene* scene = UBPersistenceManager::persistenceManager()->createDocumentSceneAt(document, pageIndex); importAdaptor->placeImportedItemToScene(scene, page); UBPersistenceManager::persistenceManager()->persistDocumentScene(document, scene, pageIndex); - if (UBApplication::documentController->selectedDocument() == UBApplication::boardController->selectedDocument()) - UBApplication::boardController->insertThumbPage(pageIndex); } UBPersistenceManager::persistenceManager()->persistDocumentMetadata(document); diff --git a/src/document/UBDocumentController.cpp b/src/document/UBDocumentController.cpp index 24371875..d69e12bf 100644 --- a/src/document/UBDocumentController.cpp +++ b/src/document/UBDocumentController.cpp @@ -2393,8 +2393,11 @@ void UBDocumentController::duplicateSelectedItem() int sceneCount = selectedSceneIndexes.count(); showMessage(tr("duplicated %1 page","duplicated %1 pages",sceneCount).arg(sceneCount), false); - mBoardController->setActiveDocumentScene(selectedThumbnail); - mBoardController->reloadThumbnails(); + if (selectedDocument() == mBoardController->selectedDocument()) + { + mBoardController->setActiveDocumentScene(selectedThumbnail); + mBoardController->reloadThumbnails(); + } } } else @@ -2999,6 +3002,8 @@ void UBDocumentController::addFolderOfImages() document->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(QDateTime::currentDateTime())); UBMetadataDcSubsetAdaptor::persist(document); reloadThumbnails(); + if (selectedDocument() == UBApplication::boardController->selectedDocument()) + UBApplication::boardController->reloadThumbnails(); } } } @@ -3012,11 +3017,6 @@ void UBDocumentController::addFileToDocument() if (document) { addFileToDocument(document); - reloadThumbnails(); - if (UBApplication::boardController->selectedDocument() == selectedDocument()) - { - UBApplication::boardController->reloadThumbnails(); - } } } @@ -3050,6 +3050,8 @@ bool UBDocumentController::addFileToDocument(UBDocumentProxy* document) document->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(QDateTime::currentDateTime())); UBMetadataDcSubsetAdaptor::persist(document); reloadThumbnails(); + if (selectedDocument() == UBApplication::boardController->selectedDocument()) + UBApplication::boardController->reloadThumbnails(); } else { @@ -3328,6 +3330,8 @@ void UBDocumentController::addImages() document->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(QDateTime::currentDateTime())); UBMetadataDcSubsetAdaptor::persist(document); reloadThumbnails(); + if (selectedDocument() == UBApplication::boardController->selectedDocument()) + UBApplication::boardController->reloadThumbnails(); } } } From cb7538aacd24cf7f51ae5d3e7a9c6cbf332bffec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Mon, 7 Mar 2022 16:18:10 +0100 Subject: [PATCH 095/130] fixed a case where import would fail because of an attempt to copy an already existing file while overwrite is false --- src/frameworks/UBFileSystemUtils.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/frameworks/UBFileSystemUtils.cpp b/src/frameworks/UBFileSystemUtils.cpp index 6c5ae6d6..42915957 100644 --- a/src/frameworks/UBFileSystemUtils.cpp +++ b/src/frameworks/UBFileSystemUtils.cpp @@ -105,6 +105,11 @@ bool UBFileSystemUtils::copyFile(const QString &source, const QString &destinati if (QFileInfo(normalizedDestination).isFile() && overwrite) { QFile::remove(normalizedDestination); } + else + { + if (!overwrite) + return true; // don't try to copy an existing file if overwrite is false + } } else { normalizedDestination = normalizedDestination.replace(QString("\\"), QString("/")); int pos = normalizedDestination.lastIndexOf("/"); @@ -300,7 +305,7 @@ bool UBFileSystemUtils::copyDir(const QString& pSourceDirPath, const QString& pT { if (dirContent.isDir()) { - successSoFar = copyDir(pSourceDirPath + "/" + dirContent.fileName(), pTargetDirPath + "/" + dirContent.fileName()); + successSoFar = copyDir(pSourceDirPath + "/" + dirContent.fileName(), pTargetDirPath + "/" + dirContent.fileName(), overwite); } else { From 6994400e716fdb266f91d34f76fc103120f6e2ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Tue, 9 Nov 2021 18:05:42 +0100 Subject: [PATCH 096/130] use progressdialog and QtConcurrent in order to create documentProxies structure (improving performances in a network-storage context) --- OpenBoard.pro | 1 + src/core/UBPersistenceManager.cpp | 84 ++++++++++++++++++++----------- src/core/UBPersistenceManager.h | 5 +- 3 files changed, 59 insertions(+), 31 deletions(-) diff --git a/OpenBoard.pro b/OpenBoard.pro index e0c661ac..8973cae4 100644 --- a/OpenBoard.pro +++ b/OpenBoard.pro @@ -40,6 +40,7 @@ QT += webkitwidgets QT += multimediawidgets QT += printsupport QT += core +QT += concurrent INCLUDEPATH += src diff --git a/src/core/UBPersistenceManager.cpp b/src/core/UBPersistenceManager.cpp index b2e26cca..37c22054 100644 --- a/src/core/UBPersistenceManager.cpp +++ b/src/core/UBPersistenceManager.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include "frameworks/UBPlatformUtils.h" #include "frameworks/UBFileSystemUtils.h" @@ -98,7 +99,6 @@ UBPersistenceManager::UBPersistenceManager(QObject *pParent) mDocumentTreeStructureModel = new UBDocumentTreeModel(this); createDocumentProxiesStructure(); - emit proxyListChanged(); } @@ -131,7 +131,27 @@ void UBPersistenceManager::createDocumentProxiesStructure(bool interactive) rootDir.mkpath(rootDir.path()); QFileInfoList contentList = rootDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Time | QDir::Reversed); - createDocumentProxiesStructure(contentList, interactive); + + mProgress.setWindowFlags(Qt::Window | Qt::WindowTitleHint | Qt::CustomizeWindowHint); + mProgress.setLabelText(QString("retrieving all your documents (found %1)").arg(contentList.size())); + mProgress.setCancelButton(nullptr); + + // Create a QFutureWatcher and connect signals and slots. + QFutureWatcher futureWatcher; + QObject::connect(&futureWatcher, &QFutureWatcher::finished, &mProgress, &QProgressDialog::reset); + QObject::connect(&futureWatcher, &QFutureWatcher::progressRangeChanged, &mProgress, &QProgressDialog::setRange); + QObject::connect(&futureWatcher, &QFutureWatcher::progressValueChanged, &mProgress, &QProgressDialog::setValue); + + // Start the computation. + futureWatcher.setFuture(QtConcurrent::map(contentList, [=] (QFileInfo& contentInfo) + { + createDocumentProxyStructure(contentInfo); + })); + + // Display the dialog and start the event loop. + mProgress.exec(); + + futureWatcher.waitForFinished(); if (QFileInfo(mFoldersXmlStorageName).exists()) { QDomDocument xmlDom; @@ -158,44 +178,48 @@ void UBPersistenceManager::createDocumentProxiesStructure(bool interactive) } } -void UBPersistenceManager::createDocumentProxiesStructure(const QFileInfoList &contentInfo, bool interactive) +void UBPersistenceManager::createDocumentProxiesStructure(const QFileInfoList &contentInfoList, bool interactive) { - foreach(QFileInfo path, contentInfo) + foreach(QFileInfo contentInfo, contentInfoList) { - QString fullPath = path.absoluteFilePath(); + createDocumentProxyStructure(contentInfo, interactive); + } +} - QDir dir(fullPath); +void UBPersistenceManager::createDocumentProxyStructure(const QFileInfo &contentInfo, bool interactive) +{ + QString fullPath = contentInfo.absoluteFilePath(); + QDir dir(fullPath); - if (dir.entryList(QDir::Files | QDir::NoDotAndDotDot).size() > 0) - { - QMap metadatas = UBMetadataDcSubsetAdaptor::load(fullPath); - QString docGroupName = metadatas.value(UBSettings::documentGroupName, QString()).toString(); - QString docName = metadatas.value(UBSettings::documentName, QString()).toString(); + if (dir.entryList(QDir::Files | QDir::NoDotAndDotDot).size() > 0) + { + QMap metadatas = UBMetadataDcSubsetAdaptor::load(fullPath); + QString docGroupName = metadatas.value(UBSettings::documentGroupName, QString()).toString(); + QString docName = metadatas.value(UBSettings::documentName, QString()).toString(); - if (docName.isEmpty()) { - qDebug() << "Group name and document name are empty in UBPersistenceManager::createDocumentProxiesStructure()"; - continue; - } + if (docName.isEmpty()) { + qDebug() << "Group name and document name are empty in UBPersistenceManager::createDocumentProxiesStructure()"; + return; + } - QModelIndex parentIndex = mDocumentTreeStructureModel->goTo(docGroupName); - if (!parentIndex.isValid()) { - return; - } + QModelIndex parentIndex = mDocumentTreeStructureModel->goTo(docGroupName); + if (!parentIndex.isValid()) { + return; + } - UBDocumentProxy* docProxy = new UBDocumentProxy(fullPath); // managed in UBDocumentTreeNode - foreach(QString key, metadatas.keys()) { - docProxy->setMetaData(key, metadatas.value(key)); - } + UBDocumentProxy* docProxy = new UBDocumentProxy(fullPath); // managed in UBDocumentTreeNode + foreach(QString key, metadatas.keys()) { + docProxy->setMetaData(key, metadatas.value(key)); + } - docProxy->setPageCount(sceneCount(docProxy)); + docProxy->setPageCount(sceneCount(docProxy)); - if (!interactive) - mDocumentTreeStructureModel->addDocument(docProxy, parentIndex); - else - processInteractiveReplacementDialog(docProxy); - } + if (!interactive) + mDocumentTreeStructureModel->addDocument(docProxy, parentIndex); + else + processInteractiveReplacementDialog(docProxy); } -} +}; QDialog::DialogCode UBPersistenceManager::processInteractiveReplacementDialog(UBDocumentProxy *pProxy) { diff --git a/src/core/UBPersistenceManager.h b/src/core/UBPersistenceManager.h index 8a1462ce..9d27dc80 100644 --- a/src/core/UBPersistenceManager.h +++ b/src/core/UBPersistenceManager.h @@ -131,7 +131,8 @@ class UBPersistenceManager : public QObject bool addDirectoryContentToDocument(const QString& documentRootFolder, UBDocumentProxy* pDocument); void createDocumentProxiesStructure(bool interactive = false); - void createDocumentProxiesStructure(const QFileInfoList &contentInfo, bool interactive = false); + void createDocumentProxiesStructure(const QFileInfoList &contentInfoList, bool interactive = false); + void createDocumentProxyStructure(const QFileInfo& contentInfo, bool interactive = false); QDialog::DialogCode processInteractiveReplacementDialog(UBDocumentProxy *pProxy); QStringList documentSubDirectories() @@ -187,6 +188,8 @@ private: bool mHasPurgedDocuments; QString mDocumentRepositoryPath; QString mFoldersXmlStorageName; + QProgressDialog mProgress; + QFutureWatcher futureWatcher; private slots: void documentRepositoryChanged(const QString& path); From e5b7251fd1c6f1561311344d077c38b785b56507 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Thu, 11 Nov 2021 08:24:23 +0100 Subject: [PATCH 097/130] add QtConcurrent lib to packaging --- release_scripts/linux/package.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/release_scripts/linux/package.sh b/release_scripts/linux/package.sh index ce2e8f70..25d07230 100755 --- a/release_scripts/linux/package.sh +++ b/release_scripts/linux/package.sh @@ -215,6 +215,7 @@ if $BUNDLE_QT; then notifyProgress "Copying and stripping Qt libraries" mkdir -p $QT_LIBRARY_DEST_PATH + copyQtLibrary libQt5Concurrent copyQtLibrary libQt5Core copyQtLibrary libQt5DBus copyQtLibrary libQt5Gui From b943a0d79d4a9f994069b9d89b6dc54cab95e10f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Wed, 9 Mar 2022 11:24:24 +0100 Subject: [PATCH 098/130] cherry-picked 670d772 + b944207c --- src/core/UBPersistenceManager.cpp | 89 ++++++++++++++++----------- src/core/UBPersistenceManager.h | 2 +- src/document/UBDocumentController.cpp | 2 + src/gui/UBBoardThumbnailsView.cpp | 4 +- 4 files changed, 58 insertions(+), 39 deletions(-) diff --git a/src/core/UBPersistenceManager.cpp b/src/core/UBPersistenceManager.cpp index 37c22054..e826a7ba 100644 --- a/src/core/UBPersistenceManager.cpp +++ b/src/core/UBPersistenceManager.cpp @@ -123,36 +123,65 @@ UBPersistenceManager::~UBPersistenceManager() { } -void UBPersistenceManager::createDocumentProxiesStructure(bool interactive) +void UBPersistenceManager::createDocumentProxiesStructure(const QFileInfoList &contentInfoList, bool interactive) { - mDocumentRepositoryPath = UBSettings::userDocumentDirectory(); - - QDir rootDir(mDocumentRepositoryPath); - rootDir.mkpath(rootDir.path()); - - QFileInfoList contentList = rootDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Time | QDir::Reversed); - - mProgress.setWindowFlags(Qt::Window | Qt::WindowTitleHint | Qt::CustomizeWindowHint); - mProgress.setLabelText(QString("retrieving all your documents (found %1)").arg(contentList.size())); - mProgress.setCancelButton(nullptr); - // Create a QFutureWatcher and connect signals and slots. - QFutureWatcher futureWatcher; + QFutureWatcher futureWatcher; QObject::connect(&futureWatcher, &QFutureWatcher::finished, &mProgress, &QProgressDialog::reset); QObject::connect(&futureWatcher, &QFutureWatcher::progressRangeChanged, &mProgress, &QProgressDialog::setRange); QObject::connect(&futureWatcher, &QFutureWatcher::progressValueChanged, &mProgress, &QProgressDialog::setValue); // Start the computation. - futureWatcher.setFuture(QtConcurrent::map(contentList, [=] (QFileInfo& contentInfo) - { - createDocumentProxyStructure(contentInfo); - })); + std::function createDocumentProxyLambda = [=](QFileInfo contentInfo) { + return createDocumentProxyStructure(contentInfo); + }; + + QFuture proxiesFuture = QtConcurrent::mapped(contentInfoList, createDocumentProxyLambda); + futureWatcher.setFuture(proxiesFuture); // Display the dialog and start the event loop. mProgress.exec(); futureWatcher.waitForFinished(); + QList proxies = futureWatcher.future().results(); + + for (auto&& proxy : qAsConst(proxies)) + { + if (proxy) + { + QString docGroupName = proxy->metaData(UBSettings::documentGroupName).toString(); + QModelIndex parentIndex = mDocumentTreeStructureModel->goTo(docGroupName); + if (parentIndex.isValid()) + { + if (!interactive) + mDocumentTreeStructureModel->addDocument(proxy, parentIndex); + else + processInteractiveReplacementDialog(proxy); + } + else + { + qDebug() << "something went wrong"; + } + } + } +} + +void UBPersistenceManager::createDocumentProxiesStructure(bool interactive) +{ + mDocumentRepositoryPath = UBSettings::userDocumentDirectory(); + + QDir rootDir(mDocumentRepositoryPath); + rootDir.mkpath(rootDir.path()); + + QFileInfoList contentInfoList = rootDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Time | QDir::Reversed); + + mProgress.setWindowFlags(Qt::Window | Qt::WindowTitleHint | Qt::CustomizeWindowHint); + mProgress.setLabelText(QString("retrieving all your documents (found %1)").arg(contentInfoList.size())); + mProgress.setCancelButton(nullptr); + + createDocumentProxiesStructure(contentInfoList, interactive); + if (QFileInfo(mFoldersXmlStorageName).exists()) { QDomDocument xmlDom; QFile inFile(mFoldersXmlStorageName); @@ -178,15 +207,7 @@ void UBPersistenceManager::createDocumentProxiesStructure(bool interactive) } } -void UBPersistenceManager::createDocumentProxiesStructure(const QFileInfoList &contentInfoList, bool interactive) -{ - foreach(QFileInfo contentInfo, contentInfoList) - { - createDocumentProxyStructure(contentInfo, interactive); - } -} - -void UBPersistenceManager::createDocumentProxyStructure(const QFileInfo &contentInfo, bool interactive) +UBDocumentProxy* UBPersistenceManager::createDocumentProxyStructure(QFileInfo& contentInfo) { QString fullPath = contentInfo.absoluteFilePath(); QDir dir(fullPath); @@ -199,12 +220,7 @@ void UBPersistenceManager::createDocumentProxyStructure(const QFileInfo &content if (docName.isEmpty()) { qDebug() << "Group name and document name are empty in UBPersistenceManager::createDocumentProxiesStructure()"; - return; - } - - QModelIndex parentIndex = mDocumentTreeStructureModel->goTo(docGroupName); - if (!parentIndex.isValid()) { - return; + return nullptr; } UBDocumentProxy* docProxy = new UBDocumentProxy(fullPath); // managed in UBDocumentTreeNode @@ -214,11 +230,12 @@ void UBPersistenceManager::createDocumentProxyStructure(const QFileInfo &content docProxy->setPageCount(sceneCount(docProxy)); - if (!interactive) - mDocumentTreeStructureModel->addDocument(docProxy, parentIndex); - else - processInteractiveReplacementDialog(docProxy); + docProxy->moveToThread(UBApplication::instance()->thread()); + + return docProxy; } + + return nullptr; }; QDialog::DialogCode UBPersistenceManager::processInteractiveReplacementDialog(UBDocumentProxy *pProxy) diff --git a/src/core/UBPersistenceManager.h b/src/core/UBPersistenceManager.h index 9d27dc80..18353b0b 100644 --- a/src/core/UBPersistenceManager.h +++ b/src/core/UBPersistenceManager.h @@ -132,7 +132,7 @@ class UBPersistenceManager : public QObject void createDocumentProxiesStructure(bool interactive = false); void createDocumentProxiesStructure(const QFileInfoList &contentInfoList, bool interactive = false); - void createDocumentProxyStructure(const QFileInfo& contentInfo, bool interactive = false); + UBDocumentProxy* createDocumentProxyStructure(QFileInfo &contentInfo); QDialog::DialogCode processInteractiveReplacementDialog(UBDocumentProxy *pProxy); QStringList documentSubDirectories() diff --git a/src/document/UBDocumentController.cpp b/src/document/UBDocumentController.cpp index d69e12bf..e76c7ecd 100644 --- a/src/document/UBDocumentController.cpp +++ b/src/document/UBDocumentController.cpp @@ -3781,6 +3781,8 @@ void UBDocumentController:: refreshDocumentThumbnailsView(UBDocumentContainer* s , QList() , QStringList() , UBApplication::mimeTypeUniboardPage); + + QApplication::restoreOverrideCursor(); return; } diff --git a/src/gui/UBBoardThumbnailsView.cpp b/src/gui/UBBoardThumbnailsView.cpp index fe10ecca..a6f9ba4e 100644 --- a/src/gui/UBBoardThumbnailsView.cpp +++ b/src/gui/UBBoardThumbnailsView.cpp @@ -307,7 +307,7 @@ void UBBoardThumbnailsView::dragMoveEvent(QDragMoveEvent *event) { y = item->pos().y() - UBSettings::thumbnailSpacing / 2; if (mDropBar->y() != y) - mDropBar->setRect(QRectF(item->pos().x(), y, mThumbnailWidth-verticalScrollBar()->width(), 3)); + mDropBar->setRect(QRectF(item->pos().x(), y, (item->boundingRect().width()-verticalScrollBar()->width())*scale, 3)); } } else @@ -316,7 +316,7 @@ void UBBoardThumbnailsView::dragMoveEvent(QDragMoveEvent *event) { y = item->pos().y() + item->boundingRect().height() * scale + UBSettings::thumbnailSpacing / 2; if (mDropBar->y() != y) - mDropBar->setRect(QRectF(item->pos().x(), y, mThumbnailWidth-verticalScrollBar()->width(), 3)); + mDropBar->setRect(QRectF(item->pos().x(), y, (item->boundingRect().width()-verticalScrollBar()->width())*scale, 3)); } } } From 1374fb36987b620a309a41c4aa63879bc3509ae4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Thu, 10 Mar 2022 12:54:17 +0100 Subject: [PATCH 099/130] don't call play/pause on osx to display the first frame (the video won't pause) --- src/domain/UBGraphicsMediaItemDelegate.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/domain/UBGraphicsMediaItemDelegate.cpp b/src/domain/UBGraphicsMediaItemDelegate.cpp index 4ab1a298..cea439ce 100644 --- a/src/domain/UBGraphicsMediaItemDelegate.cpp +++ b/src/domain/UBGraphicsMediaItemDelegate.cpp @@ -242,13 +242,15 @@ void UBGraphicsMediaItemDelegate::mediaStatusChanged(QMediaPlayer::MediaStatus s if (status == QMediaPlayer::LoadedMedia) mMediaControl->totalTimeChanged(delegated()->mediaDuration()); - // At the beginning of the video, play/pause to load and display the first frame + // At the beginning of the video, play/pause to load and display the first frame (not working on OSX) +#ifndef Q_OS_OSX if ((status == QMediaPlayer::LoadedMedia || status == QMediaPlayer::BufferedMedia) && delegated()->mediaPosition() == delegated()->initialPos() && !delegated()->isStopped()) { delegated()->play(); delegated()->pause(); } +#endif // At the end of the video, make sure the progress bar doesn't autohide if (status == QMediaPlayer::EndOfMedia) From ed73c7b15edcde2184d067100666ec351e42f0bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Thu, 10 Mar 2022 14:00:30 +0100 Subject: [PATCH 100/130] changed version to 1.6.2rc-0310 --- OpenBoard.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenBoard.pro b/OpenBoard.pro index 8973cae4..3cd4c238 100644 --- a/OpenBoard.pro +++ b/OpenBoard.pro @@ -11,7 +11,7 @@ VERSION_MAJ = 1 VERSION_MIN = 6 VERSION_PATCH = 2 VERSION_TYPE = rc # a = alpha, b = beta, rc = release candidate, r = release, other => error -VERSION_BUILD = 0303 +VERSION_BUILD = 0311 VERSION = "$${VERSION_MAJ}.$${VERSION_MIN}.$${VERSION_PATCH}-$${VERSION_TYPE}.$${VERSION_BUILD}" From 37cfe3ea302fa75d78ba1be84905c744f4159e3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Wed, 23 Mar 2022 10:05:11 +0100 Subject: [PATCH 101/130] fixed an issue on OSX where files could no longer be imported by double-clicking or using 'open with' --- src/core/UBApplication.cpp | 27 ++++++++++++++++++++++++--- src/core/UBApplication.h | 2 ++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/core/UBApplication.cpp b/src/core/UBApplication.cpp index d1ef381c..479f07a1 100644 --- a/src/core/UBApplication.cpp +++ b/src/core/UBApplication.cpp @@ -81,6 +81,8 @@ const QString UBApplication::mimeTypeUniboardPage = QString("application/vnd.mne const QString UBApplication::mimeTypeUniboardPageItem = QString("application/vnd.mnemis-uniboard-page-item"); const QString UBApplication::mimeTypeUniboardPageThumbnail = QString("application/vnd.mnemis-uniboard-thumbnail"); +QString UBApplication::fileToOpen = ""; + #if defined(Q_OS_OSX) || defined(Q_OS_LINUX) bool bIsMinimized = false; #endif @@ -309,6 +311,14 @@ int UBApplication::exec(const QString& pFileToImport) boardController->paletteManager()->rightPalette()); + if (!UBApplication::fileToOpen.isEmpty()) + { + if (!UBApplication::fileToOpen.endsWith("ubx")) + applicationController->importFile(UBApplication::fileToOpen); + else + applicationController->showMessage(tr("Cannot open your UBX file directly. Please import it in Documents mode instead"), false); + } + connect(applicationController, SIGNAL(mainModeChanged(UBApplicationController::MainMode)), boardController->paletteManager(), SLOT(slot_changeMainMode(UBApplicationController::MainMode))); @@ -575,10 +585,21 @@ bool UBApplication::eventFilter(QObject *obj, QEvent *event) UBPlatformUtils::setFrontProcess(); - if (!fileToOpenEvent->file().endsWith("ubx")) - applicationController->importFile(fileToOpenEvent->file()); + if (applicationController) + { + if (!fileToOpenEvent->file().endsWith("ubx")) + applicationController->importFile(fileToOpenEvent->file()); + else + applicationController->showMessage(tr("Cannot open your UBX file directly. Please import it in Documents mode instead"), false); + } else - applicationController->showMessage(tr("Cannot open your UBX file directly. Please import it in Documents mode instead"), false); + { + //startup : progressdialog.exec() is called and fileOpenEvent is consumed too early + // we store the file and will import it when the documents tree is ready + + UBApplication::fileToOpen = fileToOpenEvent->file(); + return true; + } } if (event->type() == QEvent::TabletLeaveProximity) diff --git a/src/core/UBApplication.h b/src/core/UBApplication.h index 5f9442d7..5473804c 100644 --- a/src/core/UBApplication.h +++ b/src/core/UBApplication.h @@ -86,6 +86,8 @@ class UBApplication : public SingleApplication static const QString mimeTypeUniboardPageItem; static const QString mimeTypeUniboardPageThumbnail; + static QString fileToOpen; + static void showMessage(const QString& message, bool showSpinningWheel = false); static void setDisabled(bool disable); From 05d23fb9bdbbdd1ad3b07333d422397e7043e05f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Wed, 23 Mar 2022 11:03:17 +0100 Subject: [PATCH 102/130] updated translations --- resources/i18n/OpenBoard_ar.ts | 4 ++++ resources/i18n/OpenBoard_bg.ts | 4 ++++ resources/i18n/OpenBoard_ca.ts | 4 ++++ resources/i18n/OpenBoard_cs.ts | 4 ++++ resources/i18n/OpenBoard_da.ts | 4 ++++ resources/i18n/OpenBoard_de.ts | 4 ++++ resources/i18n/OpenBoard_el.ts | 4 ++++ resources/i18n/OpenBoard_en.ts | 4 ++++ resources/i18n/OpenBoard_en_UK.ts | 4 ++++ resources/i18n/OpenBoard_es.ts | 4 ++++ resources/i18n/OpenBoard_fr.ts | 4 ++++ resources/i18n/OpenBoard_fr_CH.ts | 4 ++++ resources/i18n/OpenBoard_gl.ts | 4 ++++ resources/i18n/OpenBoard_hu.ts | 4 ++++ resources/i18n/OpenBoard_it.ts | 4 ++++ resources/i18n/OpenBoard_iw.ts | 4 ++++ resources/i18n/OpenBoard_ja.ts | 4 ++++ resources/i18n/OpenBoard_ko.ts | 4 ++++ resources/i18n/OpenBoard_mg.ts | 4 ++++ resources/i18n/OpenBoard_nb.ts | 4 ++++ resources/i18n/OpenBoard_nl.ts | 4 ++++ resources/i18n/OpenBoard_pl.ts | 4 ++++ resources/i18n/OpenBoard_pt.ts | 4 ++++ resources/i18n/OpenBoard_ro.ts | 4 ++++ resources/i18n/OpenBoard_ru.ts | 4 ++++ resources/i18n/OpenBoard_sk.ts | 4 ++++ resources/i18n/OpenBoard_sv.ts | 4 ++++ resources/i18n/OpenBoard_tr.ts | 4 ++++ resources/i18n/OpenBoard_uk.ts | 4 ++++ resources/i18n/OpenBoard_zh.ts | 4 ++++ resources/i18n/OpenBoard_zh_CN.ts | 4 ++++ resources/i18n/OpenBoard_zh_TW.ts | 4 ++++ 32 files changed, 128 insertions(+) diff --git a/resources/i18n/OpenBoard_ar.ts b/resources/i18n/OpenBoard_ar.ts index 60221728..e5459ac7 100644 --- a/resources/i18n/OpenBoard_ar.ts +++ b/resources/i18n/OpenBoard_ar.ts @@ -915,6 +915,10 @@ Podcast بودكاست + + Cannot open your UBX file directly. Please import it in Documents mode instead + + UBApplicationController diff --git a/resources/i18n/OpenBoard_bg.ts b/resources/i18n/OpenBoard_bg.ts index 1bbf4db8..a03c28fe 100644 --- a/resources/i18n/OpenBoard_bg.ts +++ b/resources/i18n/OpenBoard_bg.ts @@ -915,6 +915,10 @@ Podcast Подкаст + + Cannot open your UBX file directly. Please import it in Documents mode instead + + UBApplicationController diff --git a/resources/i18n/OpenBoard_ca.ts b/resources/i18n/OpenBoard_ca.ts index ccd562d0..98115d42 100644 --- a/resources/i18n/OpenBoard_ca.ts +++ b/resources/i18n/OpenBoard_ca.ts @@ -915,6 +915,10 @@ Podcast Podcast + + Cannot open your UBX file directly. Please import it in Documents mode instead + + UBApplicationController diff --git a/resources/i18n/OpenBoard_cs.ts b/resources/i18n/OpenBoard_cs.ts index 682ec6c4..9516eb32 100644 --- a/resources/i18n/OpenBoard_cs.ts +++ b/resources/i18n/OpenBoard_cs.ts @@ -916,6 +916,10 @@ Page Size Velikost stránky + + Cannot open your UBX file directly. Please import it in Documents mode instead + + UBApplicationController diff --git a/resources/i18n/OpenBoard_da.ts b/resources/i18n/OpenBoard_da.ts index 1b072aee..cf1655a8 100644 --- a/resources/i18n/OpenBoard_da.ts +++ b/resources/i18n/OpenBoard_da.ts @@ -915,6 +915,10 @@ Podcast Podcast + + Cannot open your UBX file directly. Please import it in Documents mode instead + + UBApplicationController diff --git a/resources/i18n/OpenBoard_de.ts b/resources/i18n/OpenBoard_de.ts index 28662ec0..521a4e14 100644 --- a/resources/i18n/OpenBoard_de.ts +++ b/resources/i18n/OpenBoard_de.ts @@ -915,6 +915,10 @@ Podcast Podcast + + Cannot open your UBX file directly. Please import it in Documents mode instead + + UBApplicationController diff --git a/resources/i18n/OpenBoard_el.ts b/resources/i18n/OpenBoard_el.ts index f7b515ab..f9057609 100644 --- a/resources/i18n/OpenBoard_el.ts +++ b/resources/i18n/OpenBoard_el.ts @@ -915,6 +915,10 @@ Podcast Βίντεο + + Cannot open your UBX file directly. Please import it in Documents mode instead + + UBApplicationController diff --git a/resources/i18n/OpenBoard_en.ts b/resources/i18n/OpenBoard_en.ts index 792ae6d4..99d431b2 100644 --- a/resources/i18n/OpenBoard_en.ts +++ b/resources/i18n/OpenBoard_en.ts @@ -907,6 +907,10 @@ Podcast + + Cannot open your UBX file directly. Please import it in Documents mode instead + + UBApplicationController diff --git a/resources/i18n/OpenBoard_en_UK.ts b/resources/i18n/OpenBoard_en_UK.ts index 792ae6d4..99d431b2 100644 --- a/resources/i18n/OpenBoard_en_UK.ts +++ b/resources/i18n/OpenBoard_en_UK.ts @@ -907,6 +907,10 @@ Podcast + + Cannot open your UBX file directly. Please import it in Documents mode instead + + UBApplicationController diff --git a/resources/i18n/OpenBoard_es.ts b/resources/i18n/OpenBoard_es.ts index bf30a45d..c80f0755 100644 --- a/resources/i18n/OpenBoard_es.ts +++ b/resources/i18n/OpenBoard_es.ts @@ -915,6 +915,10 @@ Podcast Podcast + + Cannot open your UBX file directly. Please import it in Documents mode instead + + UBApplicationController diff --git a/resources/i18n/OpenBoard_fr.ts b/resources/i18n/OpenBoard_fr.ts index 9bd5c135..ae910ba1 100644 --- a/resources/i18n/OpenBoard_fr.ts +++ b/resources/i18n/OpenBoard_fr.ts @@ -916,6 +916,10 @@ Podcast Podcast + + Cannot open your UBX file directly. Please import it in Documents mode instead + Impossible d'ouvrir un fichier UBX directement. Veuillez l'importer depuis le mode Documents + UBApplicationController diff --git a/resources/i18n/OpenBoard_fr_CH.ts b/resources/i18n/OpenBoard_fr_CH.ts index da3a92c2..7bb7dafc 100644 --- a/resources/i18n/OpenBoard_fr_CH.ts +++ b/resources/i18n/OpenBoard_fr_CH.ts @@ -916,6 +916,10 @@ Podcast Podcast + + Cannot open your UBX file directly. Please import it in Documents mode instead + Impossible d'ouvrir un fichier UBX directement. Veuillez l'importer depuis le mode Documents + UBApplicationController diff --git a/resources/i18n/OpenBoard_gl.ts b/resources/i18n/OpenBoard_gl.ts index b84aa5a2..b7378865 100644 --- a/resources/i18n/OpenBoard_gl.ts +++ b/resources/i18n/OpenBoard_gl.ts @@ -915,6 +915,10 @@ Podcast Podcast + + Cannot open your UBX file directly. Please import it in Documents mode instead + + UBApplicationController diff --git a/resources/i18n/OpenBoard_hu.ts b/resources/i18n/OpenBoard_hu.ts index 0e96122d..43a3a97a 100644 --- a/resources/i18n/OpenBoard_hu.ts +++ b/resources/i18n/OpenBoard_hu.ts @@ -915,6 +915,10 @@ Podcast Podcast + + Cannot open your UBX file directly. Please import it in Documents mode instead + + UBApplicationController diff --git a/resources/i18n/OpenBoard_it.ts b/resources/i18n/OpenBoard_it.ts index 50a151a3..0c012a5f 100644 --- a/resources/i18n/OpenBoard_it.ts +++ b/resources/i18n/OpenBoard_it.ts @@ -915,6 +915,10 @@ Podcast PodCast + + Cannot open your UBX file directly. Please import it in Documents mode instead + + UBApplicationController diff --git a/resources/i18n/OpenBoard_iw.ts b/resources/i18n/OpenBoard_iw.ts index 2e769753..43c543e4 100644 --- a/resources/i18n/OpenBoard_iw.ts +++ b/resources/i18n/OpenBoard_iw.ts @@ -916,6 +916,10 @@ Podcast פודקסט + + Cannot open your UBX file directly. Please import it in Documents mode instead + + UBApplicationController diff --git a/resources/i18n/OpenBoard_ja.ts b/resources/i18n/OpenBoard_ja.ts index 31cc8803..7e5b1721 100644 --- a/resources/i18n/OpenBoard_ja.ts +++ b/resources/i18n/OpenBoard_ja.ts @@ -915,6 +915,10 @@ Podcast ポッドキャスト + + Cannot open your UBX file directly. Please import it in Documents mode instead + + UBApplicationController diff --git a/resources/i18n/OpenBoard_ko.ts b/resources/i18n/OpenBoard_ko.ts index c61d339c..d59d7947 100644 --- a/resources/i18n/OpenBoard_ko.ts +++ b/resources/i18n/OpenBoard_ko.ts @@ -915,6 +915,10 @@ Podcast 팟캐스트 + + Cannot open your UBX file directly. Please import it in Documents mode instead + + UBApplicationController diff --git a/resources/i18n/OpenBoard_mg.ts b/resources/i18n/OpenBoard_mg.ts index c2eb0752..a5c59cc0 100644 --- a/resources/i18n/OpenBoard_mg.ts +++ b/resources/i18n/OpenBoard_mg.ts @@ -915,6 +915,10 @@ Podcast Podcast + + Cannot open your UBX file directly. Please import it in Documents mode instead + + UBApplicationController diff --git a/resources/i18n/OpenBoard_nb.ts b/resources/i18n/OpenBoard_nb.ts index 0a574139..82d376aa 100644 --- a/resources/i18n/OpenBoard_nb.ts +++ b/resources/i18n/OpenBoard_nb.ts @@ -915,6 +915,10 @@ Podcast Podcast + + Cannot open your UBX file directly. Please import it in Documents mode instead + + UBApplicationController diff --git a/resources/i18n/OpenBoard_nl.ts b/resources/i18n/OpenBoard_nl.ts index ba7eb1a8..9fded612 100644 --- a/resources/i18n/OpenBoard_nl.ts +++ b/resources/i18n/OpenBoard_nl.ts @@ -915,6 +915,10 @@ Podcast Podcast + + Cannot open your UBX file directly. Please import it in Documents mode instead + + UBApplicationController diff --git a/resources/i18n/OpenBoard_pl.ts b/resources/i18n/OpenBoard_pl.ts index 677ffc59..2af7cb25 100644 --- a/resources/i18n/OpenBoard_pl.ts +++ b/resources/i18n/OpenBoard_pl.ts @@ -918,6 +918,10 @@ Podcast Podkast + + Cannot open your UBX file directly. Please import it in Documents mode instead + + UBApplicationController diff --git a/resources/i18n/OpenBoard_pt.ts b/resources/i18n/OpenBoard_pt.ts index 28c49903..5aec6470 100644 --- a/resources/i18n/OpenBoard_pt.ts +++ b/resources/i18n/OpenBoard_pt.ts @@ -917,6 +917,10 @@ Podcast Podcast + + Cannot open your UBX file directly. Please import it in Documents mode instead + + UBApplicationController diff --git a/resources/i18n/OpenBoard_ro.ts b/resources/i18n/OpenBoard_ro.ts index afeb969e..d0176e37 100644 --- a/resources/i18n/OpenBoard_ro.ts +++ b/resources/i18n/OpenBoard_ro.ts @@ -915,6 +915,10 @@ Podcast Podcast + + Cannot open your UBX file directly. Please import it in Documents mode instead + + UBApplicationController diff --git a/resources/i18n/OpenBoard_ru.ts b/resources/i18n/OpenBoard_ru.ts index a230bbfc..dee5d9f1 100644 --- a/resources/i18n/OpenBoard_ru.ts +++ b/resources/i18n/OpenBoard_ru.ts @@ -915,6 +915,10 @@ Podcast Подкаст + + Cannot open your UBX file directly. Please import it in Documents mode instead + + UBApplicationController diff --git a/resources/i18n/OpenBoard_sk.ts b/resources/i18n/OpenBoard_sk.ts index 3c5c9cdb..6739abcb 100644 --- a/resources/i18n/OpenBoard_sk.ts +++ b/resources/i18n/OpenBoard_sk.ts @@ -919,6 +919,10 @@ Page Size Veľkosť stránky + + Cannot open your UBX file directly. Please import it in Documents mode instead + + UBApplicationController diff --git a/resources/i18n/OpenBoard_sv.ts b/resources/i18n/OpenBoard_sv.ts index 222ab123..435c3751 100644 --- a/resources/i18n/OpenBoard_sv.ts +++ b/resources/i18n/OpenBoard_sv.ts @@ -915,6 +915,10 @@ Podcast Podcast + + Cannot open your UBX file directly. Please import it in Documents mode instead + + UBApplicationController diff --git a/resources/i18n/OpenBoard_tr.ts b/resources/i18n/OpenBoard_tr.ts index e1e2d349..6d372408 100644 --- a/resources/i18n/OpenBoard_tr.ts +++ b/resources/i18n/OpenBoard_tr.ts @@ -915,6 +915,10 @@ Podcast Ekran Kaydı + + Cannot open your UBX file directly. Please import it in Documents mode instead + + UBApplicationController diff --git a/resources/i18n/OpenBoard_uk.ts b/resources/i18n/OpenBoard_uk.ts index d1eb6c08..02a9c16d 100644 --- a/resources/i18n/OpenBoard_uk.ts +++ b/resources/i18n/OpenBoard_uk.ts @@ -915,6 +915,10 @@ Podcast Подкаст + + Cannot open your UBX file directly. Please import it in Documents mode instead + + UBApplicationController diff --git a/resources/i18n/OpenBoard_zh.ts b/resources/i18n/OpenBoard_zh.ts index f222bad2..838b6327 100644 --- a/resources/i18n/OpenBoard_zh.ts +++ b/resources/i18n/OpenBoard_zh.ts @@ -915,6 +915,10 @@ Podcast 播客 + + Cannot open your UBX file directly. Please import it in Documents mode instead + + UBApplicationController diff --git a/resources/i18n/OpenBoard_zh_CN.ts b/resources/i18n/OpenBoard_zh_CN.ts index f222bad2..838b6327 100644 --- a/resources/i18n/OpenBoard_zh_CN.ts +++ b/resources/i18n/OpenBoard_zh_CN.ts @@ -915,6 +915,10 @@ Podcast 播客 + + Cannot open your UBX file directly. Please import it in Documents mode instead + + UBApplicationController diff --git a/resources/i18n/OpenBoard_zh_TW.ts b/resources/i18n/OpenBoard_zh_TW.ts index a55735fa..19235899 100644 --- a/resources/i18n/OpenBoard_zh_TW.ts +++ b/resources/i18n/OpenBoard_zh_TW.ts @@ -915,6 +915,10 @@ Podcast Podcast + + Cannot open your UBX file directly. Please import it in Documents mode instead + + UBApplicationController From bcdeb3e03f74c98de9e45e1a44cee683c065e51f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Wed, 23 Mar 2022 11:16:34 +0100 Subject: [PATCH 103/130] updated version to 1.6.2rc-0323 --- OpenBoard.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenBoard.pro b/OpenBoard.pro index 3cd4c238..3c5d2dc7 100644 --- a/OpenBoard.pro +++ b/OpenBoard.pro @@ -11,7 +11,7 @@ VERSION_MAJ = 1 VERSION_MIN = 6 VERSION_PATCH = 2 VERSION_TYPE = rc # a = alpha, b = beta, rc = release candidate, r = release, other => error -VERSION_BUILD = 0311 +VERSION_BUILD = 0323 VERSION = "$${VERSION_MAJ}.$${VERSION_MIN}.$${VERSION_PATCH}-$${VERSION_TYPE}.$${VERSION_BUILD}" From 095d4f748b06b8422cef62f1ca88d9a59b66a089 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Wed, 23 Mar 2022 12:05:02 +0100 Subject: [PATCH 104/130] added message if trying to open ubx directly (linux + windows) --- src/core/UBApplication.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/core/UBApplication.cpp b/src/core/UBApplication.cpp index 479f07a1..b7d3e0c0 100644 --- a/src/core/UBApplication.cpp +++ b/src/core/UBApplication.cpp @@ -357,8 +357,13 @@ int UBApplication::exec(const QString& pFileToImport) applicationController->initScreenLayout(bUseMultiScreen); boardController->setupLayout(); - if (pFileToImport.length() > 0 && !pFileToImport.endsWith("ubx")) - UBApplication::applicationController->importFile(pFileToImport); + if (pFileToImport.length() > 0) + { + if (!pFileToImport.endsWith("ubx")) + applicationController->importFile(pFileToImport); + else + applicationController->showMessage(tr("Cannot open your UBX file directly. Please import it in Documents mode instead"), false); + } if (UBSettings::settings()->appStartMode->get().toInt()) applicationController->showDesktop(); From 4218f70158262e0d23a91e3626a4758b42d7fbf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Fri, 25 Mar 2022 08:12:49 +0100 Subject: [PATCH 105/130] updated german translations --- resources/i18n/OpenBoard_de.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/i18n/OpenBoard_de.ts b/resources/i18n/OpenBoard_de.ts index 521a4e14..169e01b7 100644 --- a/resources/i18n/OpenBoard_de.ts +++ b/resources/i18n/OpenBoard_de.ts @@ -917,7 +917,7 @@ Cannot open your UBX file directly. Please import it in Documents mode instead - + Ihre UBX-Datei kann nicht direkt geöffnet werden. Bitte importieren Sie die Datei im Dokumentenmodus From 490b9dee3e78bb45486cb92f75887296ac5fd5f3 Mon Sep 17 00:00:00 2001 From: letsfindaway Date: Sat, 20 Mar 2021 14:11:23 +0100 Subject: [PATCH 106/130] add option to export background grid to pdf --- resources/etc/OpenBoard.config | 1 + src/adaptors/UBExportPDF.cpp | 10 +++++++++- src/core/UBSettings.cpp | 1 + src/core/UBSettings.h | 1 + 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/resources/etc/OpenBoard.config b/resources/etc/OpenBoard.config index 463ece5b..b94e808b 100644 --- a/resources/etc/OpenBoard.config +++ b/resources/etc/OpenBoard.config @@ -128,6 +128,7 @@ Margin=20 PageFormat=A4 Resolution=300 ZoomBehavior=4 +ExportBackgroundGrid=false [Podcast] AudioRecordingDevice=Default diff --git a/src/adaptors/UBExportPDF.cpp b/src/adaptors/UBExportPDF.cpp index 177f716d..3f97f273 100644 --- a/src/adaptors/UBExportPDF.cpp +++ b/src/adaptors/UBExportPDF.cpp @@ -106,7 +106,15 @@ bool UBExportPDF::persistsDocument(UBDocumentProxy* pDocumentProxy, const QStrin // set background to white, no crossing for PDF output bool isDark = scene->isDarkBackground(); UBPageBackground pageBackground = scene->pageBackground(); - scene->setBackground(false, UBPageBackground::plain); + + if (UBSettings::settings()->exportBackgroundGrid->get().toBool()) + { + scene->setBackground(false, pageBackground); + } + else + { + scene->setBackground(false, UBPageBackground::plain); + } // pageSize is the output PDF page size; it is set to equal the scene's boundary size; if the contents // of the scene overflow from the boundaries, they will be scaled down. diff --git a/src/core/UBSettings.cpp b/src/core/UBSettings.cpp index f659452f..d5b5ed6a 100644 --- a/src/core/UBSettings.cpp +++ b/src/core/UBSettings.cpp @@ -409,6 +409,7 @@ void UBSettings::init() pdfZoomBehavior = new UBSetting(this, "PDF", "ZoomBehavior", "4"); enableQualityLossToIncreaseZoomPerfs = new UBSetting(this, "PDF", "enableQualityLossToIncreaseZoomPerfs", true); + exportBackgroundGrid = new UBSetting(this, "PDF", "ExportBackgroundGrid", false); podcastFramesPerSecond = new UBSetting(this, "Podcast", "FramesPerSecond", 10); podcastVideoSize = new UBSetting(this, "Podcast", "VideoSize", "Medium"); diff --git a/src/core/UBSettings.h b/src/core/UBSettings.h index 17d4ae79..90e77eef 100644 --- a/src/core/UBSettings.h +++ b/src/core/UBSettings.h @@ -361,6 +361,7 @@ class UBSettings : public QObject UBSetting* pdfZoomBehavior; UBSetting* enableQualityLossToIncreaseZoomPerfs; + UBSetting* exportBackgroundGrid; UBSetting* podcastFramesPerSecond; UBSetting* podcastVideoSize; From 421a407202d2bf6acb5eaafd64534f7674292449 Mon Sep 17 00:00:00 2001 From: letsfindaway Date: Sat, 20 Mar 2021 14:31:37 +0100 Subject: [PATCH 107/130] add option to keep background color in PDF export --- resources/etc/OpenBoard.config | 3 ++- src/adaptors/UBExportPDF.cpp | 6 ++++-- src/core/UBSettings.cpp | 1 + src/core/UBSettings.h | 1 + 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/resources/etc/OpenBoard.config b/resources/etc/OpenBoard.config index b94e808b..77eea8d1 100644 --- a/resources/etc/OpenBoard.config +++ b/resources/etc/OpenBoard.config @@ -124,11 +124,12 @@ RefreshRateInFramePerSecond=2 [PDF] enableQualityLossToIncreaseZoomPerfs=true +ExportBackgroundGrid=false +ExportBackgroundColor=false Margin=20 PageFormat=A4 Resolution=300 ZoomBehavior=4 -ExportBackgroundGrid=false [Podcast] AudioRecordingDevice=Default diff --git a/src/adaptors/UBExportPDF.cpp b/src/adaptors/UBExportPDF.cpp index 3f97f273..683716bb 100644 --- a/src/adaptors/UBExportPDF.cpp +++ b/src/adaptors/UBExportPDF.cpp @@ -107,13 +107,15 @@ bool UBExportPDF::persistsDocument(UBDocumentProxy* pDocumentProxy, const QStrin bool isDark = scene->isDarkBackground(); UBPageBackground pageBackground = scene->pageBackground(); + bool exportDark = isDark && UBSettings::settings()->exportBackgroundColor->get().toBool(); + if (UBSettings::settings()->exportBackgroundGrid->get().toBool()) { - scene->setBackground(false, pageBackground); + scene->setBackground(exportDark, pageBackground); } else { - scene->setBackground(false, UBPageBackground::plain); + scene->setBackground(exportDark, UBPageBackground::plain); } // pageSize is the output PDF page size; it is set to equal the scene's boundary size; if the contents diff --git a/src/core/UBSettings.cpp b/src/core/UBSettings.cpp index d5b5ed6a..c98f1d70 100644 --- a/src/core/UBSettings.cpp +++ b/src/core/UBSettings.cpp @@ -410,6 +410,7 @@ void UBSettings::init() pdfZoomBehavior = new UBSetting(this, "PDF", "ZoomBehavior", "4"); enableQualityLossToIncreaseZoomPerfs = new UBSetting(this, "PDF", "enableQualityLossToIncreaseZoomPerfs", true); exportBackgroundGrid = new UBSetting(this, "PDF", "ExportBackgroundGrid", false); + exportBackgroundColor = new UBSetting(this, "PDF", "ExportBackgroundColor", false); podcastFramesPerSecond = new UBSetting(this, "Podcast", "FramesPerSecond", 10); podcastVideoSize = new UBSetting(this, "Podcast", "VideoSize", "Medium"); diff --git a/src/core/UBSettings.h b/src/core/UBSettings.h index 90e77eef..6067db5b 100644 --- a/src/core/UBSettings.h +++ b/src/core/UBSettings.h @@ -362,6 +362,7 @@ class UBSettings : public QObject UBSetting* pdfZoomBehavior; UBSetting* enableQualityLossToIncreaseZoomPerfs; UBSetting* exportBackgroundGrid; + UBSetting* exportBackgroundColor; UBSetting* podcastFramesPerSecond; UBSetting* podcastVideoSize; From 2aee390105b4d2e23f52df7bbd88247ce35109b6 Mon Sep 17 00:00:00 2001 From: letsfindaway Date: Thu, 10 Mar 2022 18:07:02 +0100 Subject: [PATCH 108/130] fix: compile with poppler >= 22.03 - use c++17 (even required for poppler 22.01) - handle API change --- src/pdf/XPDFRenderer.cpp | 2 ++ src/pdf/pdf.pri | 1 + 2 files changed, 3 insertions(+) diff --git a/src/pdf/XPDFRenderer.cpp b/src/pdf/XPDFRenderer.cpp index a81616b8..56826565 100644 --- a/src/pdf/XPDFRenderer.cpp +++ b/src/pdf/XPDFRenderer.cpp @@ -95,6 +95,8 @@ XPDFRenderer::XPDFRenderer(const QString &filename, bool importingFile) } #ifdef USE_XPDF mDocument = new PDFDoc(new GString(filename.toLocal8Bit()), 0, 0, 0); // the filename GString is deleted on PDFDoc desctruction +#elif POPPLER_VERSION_MAJOR > 22 || (POPPLER_VERSION_MAJOR == 22 && POPPLER_VERSION_MINOR >= 3) + mDocument = new PDFDoc(std::make_unique(filename.toLocal8Bit())); #else mDocument = new PDFDoc(new GooString(filename.toLocal8Bit()), 0, 0, 0); // the filename GString is deleted on PDFDoc desctruction #endif diff --git a/src/pdf/pdf.pri b/src/pdf/pdf.pri index 9be1b7fe..aeb09b5b 100644 --- a/src/pdf/pdf.pri +++ b/src/pdf/pdf.pri @@ -1,3 +1,4 @@ +CONFIG += c++17 HEADERS += src/pdf/GraphicsPDFItem.h \ src/pdf/PDFRenderer.h \ From ee3b9aec7db8d3b0a2419d096f048cf0ad1033cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Tue, 29 Mar 2022 15:28:32 +0200 Subject: [PATCH 109/130] improved selection behavior in document mode and updating of buttons/actions + reduced the amount of some calls --- src/board/UBBoardController.cpp | 2 +- src/core/UBApplicationController.cpp | 1 - src/core/UBPersistenceManager.cpp | 8 ++-- src/core/UBPersistenceManager.h | 2 +- src/document/UBDocumentController.cpp | 59 ++++++++++++++++----------- src/gui/UBThumbnailWidget.cpp | 3 -- 6 files changed, 42 insertions(+), 33 deletions(-) diff --git a/src/board/UBBoardController.cpp b/src/board/UBBoardController.cpp index ae0d80fe..92e8c48b 100644 --- a/src/board/UBBoardController.cpp +++ b/src/board/UBBoardController.cpp @@ -524,7 +524,7 @@ void UBBoardController::addScene() if (UBApplication::documentController->selectedDocument() == selectedDocument()) { UBApplication::documentController->insertThumbPage(mActiveSceneIndex+1); - emit UBApplication::documentController->reloadThumbnails(); + UBApplication::documentController->reloadThumbnails(); } selectedDocument()->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(QDateTime::currentDateTime())); diff --git a/src/core/UBApplicationController.cpp b/src/core/UBApplicationController.cpp index 27c5d75b..cf03215b 100644 --- a/src/core/UBApplicationController.cpp +++ b/src/core/UBApplicationController.cpp @@ -441,7 +441,6 @@ void UBApplicationController::showDocument() if (UBApplication::documentController) { - emit UBApplication::documentController->reorderDocumentsRequested(); UBApplication::documentController->show(); } diff --git a/src/core/UBPersistenceManager.cpp b/src/core/UBPersistenceManager.cpp index e826a7ba..e163668e 100644 --- a/src/core/UBPersistenceManager.cpp +++ b/src/core/UBPersistenceManager.cpp @@ -686,8 +686,7 @@ void UBPersistenceManager::deleteDocumentScenes(UBDocumentProxy* proxy, const QL d.mkpath(d.absolutePath()); QFile::copy(source, target); } - - insertDocumentSceneAt(trashDocProxy, scene, trashDocProxy->pageCount()); + insertDocumentSceneAt(trashDocProxy, scene, trashDocProxy->pageCount(), true, true); } } @@ -890,7 +889,7 @@ UBGraphicsScene* UBPersistenceManager::createDocumentSceneAt(UBDocumentProxy* pr } -void UBPersistenceManager::insertDocumentSceneAt(UBDocumentProxy* proxy, UBGraphicsScene* scene, int index, bool persist) +void UBPersistenceManager::insertDocumentSceneAt(UBDocumentProxy* proxy, UBGraphicsScene* scene, int index, bool persist, bool deleting) { scene->setDocument(proxy); @@ -911,7 +910,8 @@ void UBPersistenceManager::insertDocumentSceneAt(UBDocumentProxy* proxy, UBGraph persistDocumentScene(proxy, scene, index); } - emit documentSceneCreated(proxy, index); + if (!deleting) + emit documentSceneCreated(proxy, index); } diff --git a/src/core/UBPersistenceManager.h b/src/core/UBPersistenceManager.h index 18353b0b..cdcba56f 100644 --- a/src/core/UBPersistenceManager.h +++ b/src/core/UBPersistenceManager.h @@ -107,7 +107,7 @@ class UBPersistenceManager : public QObject virtual UBGraphicsScene* createDocumentSceneAt(UBDocumentProxy* pDocumentProxy, int index, bool useUndoRedoStack = true); - virtual void insertDocumentSceneAt(UBDocumentProxy* pDocumentProxy, UBGraphicsScene* scene, int index, bool persist = true); + virtual void insertDocumentSceneAt(UBDocumentProxy* pDocumentProxy, UBGraphicsScene* scene, int index, bool persist = true, bool deleting = false); virtual void moveSceneToIndex(UBDocumentProxy* pDocumentProxy, int source, int target); diff --git a/src/document/UBDocumentController.cpp b/src/document/UBDocumentController.cpp index e76c7ecd..8578e04f 100644 --- a/src/document/UBDocumentController.cpp +++ b/src/document/UBDocumentController.cpp @@ -1341,6 +1341,7 @@ UBDocumentTreeView::UBDocumentTreeView(QWidget *parent) : QTreeView(parent) { setObjectName("UBDocumentTreeView"); setRootIsDecorated(true); + setSelectionBehavior(SelectRows); } void UBDocumentTreeView::setSelectedAndExpanded(const QModelIndex &pIndex, bool pExpand, bool pEdit) @@ -1358,9 +1359,9 @@ void UBDocumentTreeView::setSelectedAndExpanded(const QModelIndex &pIndex, bool ? QItemSelectionModel::Select : QItemSelectionModel::Deselect; - setCurrentIndex(pExpand - ? indexCurrentDoc - : QModelIndex()); + setCurrentIndex(indexCurrentDoc); + + selectionModel()->setCurrentIndex(proxy->mapFromSource(indexCurrentDoc), QItemSelectionModel::SelectCurrent); selectionModel()->select(proxy->mapFromSource(indexCurrentDoc), QItemSelectionModel::Rows | sel); @@ -1369,7 +1370,7 @@ void UBDocumentTreeView::setSelectedAndExpanded(const QModelIndex &pIndex, bool indexCurrentDoc = indexCurrentDoc.parent(); } - scrollTo(proxy->mapFromSource(pIndex), QAbstractItemView::PositionAtCenter); + scrollTo(proxy->mapFromSource(pIndex)); if (pEdit) edit(proxy->mapFromSource(pIndex)); @@ -1860,7 +1861,8 @@ void UBDocumentController::selectDocument(UBDocumentProxy* proxy, bool setAsCurr return; } - if (setAsCurrentDocument) { + if (setAsCurrentDocument) + { UBPersistenceManager::persistenceManager()->mDocumentTreeStructureModel->setCurrentDocument(proxy); QModelIndex indexCurrentDoc = UBPersistenceManager::persistenceManager()->mDocumentTreeStructureModel->indexForProxy(proxy); if (indexCurrentDoc.isValid()) @@ -1873,9 +1875,12 @@ void UBDocumentController::selectDocument(UBDocumentProxy* proxy, bool setAsCurr mBoardController->setActiveDocumentScene(proxy, 0, true, onImport); } } + else + { + qWarning() << "an issue occured while trying to select current index in document tree"; + } } - mSelectionType = Document; setDocument(proxy); } @@ -1953,7 +1958,7 @@ void UBDocumentController::TreeViewSelectionChanged(const QModelIndex ¤t, mCurrentIndexMoved = false; } - itemSelectionChanged(docModel->isCatalog(current_index) ? Folder : Document); + pageSelectionChanged(); } //N/C - NNE - 20140402 : workaround for using a proxy model @@ -2181,8 +2186,6 @@ void UBDocumentController::setupViews() connect(mDocumentUI->thumbnailWidget, SIGNAL(mouseDoubleClick(QGraphicsItem*,int)), this, SLOT(thumbnailPageDoubleClicked(QGraphicsItem*,int))); connect(mDocumentUI->thumbnailWidget, SIGNAL(mouseClick(QGraphicsItem*, int)), this, SLOT(pageClicked(QGraphicsItem*, int))); - connect(mDocumentUI->thumbnailWidget->scene(), SIGNAL(selectionChanged()), this, SLOT(pageSelectionChanged())); - connect(UBPersistenceManager::persistenceManager(), SIGNAL(documentSceneCreated(UBDocumentProxy*, int)), this, SLOT(documentSceneChanged(UBDocumentProxy*, int))); mDocumentUI->thumbnailWidget->setBackgroundBrush(UBSettings::documentViewLightColor); @@ -2248,6 +2251,8 @@ void UBDocumentController::sortDocuments(int kind, int order) mDocumentUI->documentTreeView->hideColumn(2); } } + + mDocumentUI->documentTreeView->setSelectedAndExpanded(firstSelectedTreeIndex(), true); } void UBDocumentController::onSortOrderChanged(bool order) @@ -2308,6 +2313,8 @@ void UBDocumentController::show() { selectDocument(mBoardController->selectedDocument()); + reorderDocuments(); + updateActions(); if(!mToolsPalette) @@ -2435,6 +2442,8 @@ void UBDocumentController::deleteSelectedItem() deleteSingleItem(indexes.at(0), docModel); } + pageSelectionChanged(); + updateActions(); } @@ -3134,8 +3143,6 @@ void UBDocumentController::pageSelectionChanged() itemSelectionChanged(Folder); else itemSelectionChanged(None); - - updateActions(); } void UBDocumentController::documentSceneChanged(UBDocumentProxy* proxy, int pSceneIndex) @@ -3401,16 +3408,6 @@ void UBDocumentController::focusChanged(QWidget *old, QWidget *current) else mSelectionType = None; } - else - { - if (old != mDocumentUI->thumbnailWidget && - old != mDocumentUI->documentTreeView && - old != mDocumentUI->documentZoomSlider) - { - if (current && (current->metaObject()->className() != QPushButton::staticMetaObject.className())) - mSelectionType = None; - } - } } void UBDocumentController::updateActions() @@ -3567,6 +3564,24 @@ void UBDocumentController::updateActions() mMainWindow->actionDelete->setIcon(QIcon(":/images/trash-empty.png")); mMainWindow->actionDelete->setText(tr("Empty")); break; + case NoDeletion : + default: + if (mSelectionType == Folder) + { + mMainWindow->actionDelete->setIcon(QIcon(":/images/trash-delete-folder.png")); + mMainWindow->actionDelete->setText(tr("Delete")); + } + else if (mSelectionType == Document) + { + mMainWindow->actionDelete->setIcon(QIcon(":/images/trash-delete-document.png")); + mMainWindow->actionDelete->setText(tr("Delete")); + } + else if (mSelectionType == Page) + { + mMainWindow->actionDelete->setIcon(QIcon(":/images/trash-document-page.png")); + mMainWindow->actionDelete->setText(tr("Delete")); + } + break; } mMainWindow->actionDocumentAdd->setEnabled((docSelected || pageSelected) && !trashSelected); @@ -3837,11 +3852,9 @@ void UBDocumentController:: refreshDocumentThumbnailsView(UBDocumentContainer* s if (selection) { - disconnect(mDocumentUI->thumbnailWidget->scene(), SIGNAL(selectionChanged()), this, SLOT(pageSelectionChanged())); UBSceneThumbnailPixmap *currentSceneThumbnailPixmap = dynamic_cast(selection); if (currentSceneThumbnailPixmap) mDocumentUI->thumbnailWidget->hightlightItem(currentSceneThumbnailPixmap->sceneIndex()); - connect(mDocumentUI->thumbnailWidget->scene(), SIGNAL(selectionChanged()), this, SLOT(pageSelectionChanged())); } QApplication::restoreOverrideCursor(); diff --git a/src/gui/UBThumbnailWidget.cpp b/src/gui/UBThumbnailWidget.cpp index 9a866847..abb217e2 100644 --- a/src/gui/UBThumbnailWidget.cpp +++ b/src/gui/UBThumbnailWidget.cpp @@ -67,13 +67,11 @@ UBThumbnailWidget::UBThumbnailWidget(QWidget* parent) setAlignment(Qt::AlignLeft | Qt::AlignTop); - connect(&mThumbnailsScene, SIGNAL(selectionChanged()), this, SLOT(sceneSelectionChanged())); } UBThumbnailWidget::~UBThumbnailWidget() { - disconnect(&mThumbnailsScene, SIGNAL(selectionChanged())); } @@ -108,7 +106,6 @@ void UBThumbnailWidget::setGraphicsItems(const QList& pGraphicsI { mThumbnailsScene.removeItem(it, true); } - // set lasso to 0 as it has been cleared as well mLassoRectItem = 0; From 67611680e4519b32c603367cfa7cf3706d03d388 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Tue, 29 Mar 2022 15:30:01 +0200 Subject: [PATCH 110/130] updated italian translations --- resources/i18n/OpenBoard_it.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/i18n/OpenBoard_it.ts b/resources/i18n/OpenBoard_it.ts index 0c012a5f..e946acf0 100644 --- a/resources/i18n/OpenBoard_it.ts +++ b/resources/i18n/OpenBoard_it.ts @@ -917,7 +917,7 @@ Cannot open your UBX file directly. Please import it in Documents mode instead - + Impossibile aprire direttamente un file UBX. Si prega di importarlo dalla modalità Documenti From bc3921ebdde124734a7dda9214385a9f9a737abd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Tue, 29 Mar 2022 17:29:46 +0200 Subject: [PATCH 111/130] fixed some issues introduced with last commit on selection --- src/document/UBDocumentController.cpp | 10 ++++++++-- src/document/UBDocumentController.h | 2 ++ src/gui/UBThumbnailWidget.cpp | 5 +++++ src/gui/UBThumbnailWidget.h | 1 + 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/document/UBDocumentController.cpp b/src/document/UBDocumentController.cpp index 8578e04f..90900113 100644 --- a/src/document/UBDocumentController.cpp +++ b/src/document/UBDocumentController.cpp @@ -1401,7 +1401,6 @@ void UBDocumentTreeView::hSliderRangeChanged(int min, int max) void UBDocumentTreeView::mousePressEvent(QMouseEvent *event) { QTreeView::mousePressEvent(event); - UBApplication::documentController->updateActions(); } void UBDocumentTreeView::dragEnterEvent(QDragEnterEvent *event) @@ -1942,9 +1941,11 @@ void UBDocumentController::TreeViewSelectionChanged(const QModelIndex ¤t, //We have just to pass a null proxy to disable the display of thumbnail UBDocumentProxy *currentDocumentProxy = 0; - if(current_index.isValid() && mDocumentUI->documentTreeView->selectionModel()->selectedRows(0).size() == 1){ + if(current_index.isValid() && mDocumentUI->documentTreeView->selectionModel()->selectedRows(0).size() == 1) + { currentDocumentProxy = docModel->proxyData(current_index); setDocument(currentDocumentProxy, false); + clearThumbnailsSelection(); } //N/C - NNE - 20140414 : END @@ -3907,3 +3908,8 @@ void UBDocumentController::expandAll() mDocumentUI->documentTreeView->setAnimated(true); } + +void UBDocumentController::clearThumbnailsSelection() +{ + mDocumentUI->thumbnailWidget->clearSelection(); +} diff --git a/src/document/UBDocumentController.h b/src/document/UBDocumentController.h index 3b634175..87e7e83a 100644 --- a/src/document/UBDocumentController.h +++ b/src/document/UBDocumentController.h @@ -458,6 +458,8 @@ class UBDocumentController : public UBDocumentContainer QModelIndex findNextSiblingNotSelected(const QModelIndex &index, QItemSelectionModel *selectionModel); bool parentIsSelected(const QModelIndex& child, QItemSelectionModel *selectionModel); + void clearThumbnailsSelection(); + signals: void exportDone(); void reorderDocumentsRequested(); diff --git a/src/gui/UBThumbnailWidget.cpp b/src/gui/UBThumbnailWidget.cpp index abb217e2..7d2304d0 100644 --- a/src/gui/UBThumbnailWidget.cpp +++ b/src/gui/UBThumbnailWidget.cpp @@ -232,6 +232,11 @@ QList UBThumbnailWidget::selectedItems() return sortedSelectedItems; } +void UBThumbnailWidget::clearSelection() +{ + mThumbnailsScene.clearSelection(); +} + void UBThumbnailWidget::mousePressEvent(QMouseEvent *event) { diff --git a/src/gui/UBThumbnailWidget.h b/src/gui/UBThumbnailWidget.h index 527b9cc2..9b1a3cbb 100644 --- a/src/gui/UBThumbnailWidget.h +++ b/src/gui/UBThumbnailWidget.h @@ -66,6 +66,7 @@ class UBThumbnailWidget : public QGraphicsView QList selectedItems(); void selectItemAt(int pIndex, bool extend = false); void unselectItemAt(int pIndex); + void clearSelection(); qreal thumbnailWidth() { From 569cbb7ab4a9a24a40cd8b80c639f8066ce275ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Wed, 30 Mar 2022 09:31:25 +0200 Subject: [PATCH 112/130] removed references to 'flash' in web capture feature --- resources/forms/mainWindow.ui | 6 +++--- resources/forms/trapFlash.ui | 2 +- src/gui/UBWebToolsPalette.cpp | 2 +- src/web/UBWebController.cpp | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/resources/forms/mainWindow.ui b/resources/forms/mainWindow.ui index 8b08f03c..d4ef4ff9 100644 --- a/resources/forms/mainWindow.ui +++ b/resources/forms/mainWindow.ui @@ -1497,16 +1497,16 @@ Configure Podcast Recording - + :/images/toolbar/addToolToLibrary.png:/images/toolbar/addToolToLibrary.png - Flash Trap + Capture Wep Content - Trap Flash Content + Capture Web Content diff --git a/resources/forms/trapFlash.ui b/resources/forms/trapFlash.ui index 2a7f4a69..00c36a00 100644 --- a/resources/forms/trapFlash.ui +++ b/resources/forms/trapFlash.ui @@ -19,7 +19,7 @@ - Select a flash to trap + Select a content to capture diff --git a/src/gui/UBWebToolsPalette.cpp b/src/gui/UBWebToolsPalette.cpp index dcca8fce..3149dc79 100644 --- a/src/gui/UBWebToolsPalette.cpp +++ b/src/gui/UBWebToolsPalette.cpp @@ -49,7 +49,7 @@ UBWebToolsPalette::UBWebToolsPalette(QWidget *parent) { QList actions; - actions << UBApplication::mainWindow->actionWebTrapFlash; + actions << UBApplication::mainWindow->actionCaptureWebContent; actions << UBApplication::mainWindow->actionWebCustomCapture; actions << UBApplication::mainWindow->actionWebWindowCapture; diff --git a/src/web/UBWebController.cpp b/src/web/UBWebController.cpp index c8d53216..eb6e65bb 100644 --- a/src/web/UBWebController.cpp +++ b/src/web/UBWebController.cpp @@ -304,7 +304,7 @@ void UBWebController::setupPalettes() UBApplication::boardController->paletteManager()->mKeyboardPalette, SLOT(onDeactivated())); #endif - connect(mMainWindow->actionWebTrapFlash, SIGNAL(triggered()), this, SLOT(trapFlash())); + connect(mMainWindow->actionCaptureWebContent, SIGNAL(triggered()), this, SLOT(trapFlash())); connect(mMainWindow->actionWebCustomCapture, SIGNAL(triggered()), this, SLOT(customCapture())); connect(mMainWindow->actionWebWindowCapture, SIGNAL(triggered()), this, SLOT(captureWindow())); connect(mMainWindow->actionWebOEmbed, SIGNAL(triggered()), this, SLOT(captureoEmbed())); From 03bd003c9a22c8d7833f8235a70f192fceb5964f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Thu, 31 Mar 2022 09:25:09 +0200 Subject: [PATCH 113/130] fixed some issues where actions would not fit with current selection + reduced amount of calls made to pageSelectionChanged() --- src/document/UBDocumentController.cpp | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/document/UBDocumentController.cpp b/src/document/UBDocumentController.cpp index 90900113..133f3975 100644 --- a/src/document/UBDocumentController.cpp +++ b/src/document/UBDocumentController.cpp @@ -1401,6 +1401,7 @@ void UBDocumentTreeView::hSliderRangeChanged(int min, int max) void UBDocumentTreeView::mousePressEvent(QMouseEvent *event) { QTreeView::mousePressEvent(event); + UBApplication::documentController->clearThumbnailsSelection(); } void UBDocumentTreeView::dragEnterEvent(QDragEnterEvent *event) @@ -1850,6 +1851,8 @@ void UBDocumentController::createNewDocument() if (document) pManager->mDocumentTreeStructureModel->markDocumentAsNew(document); + + pageSelectionChanged(); } void UBDocumentController::selectDocument(UBDocumentProxy* proxy, bool setAsCurrentDocument, const bool onImport, const bool editMode) @@ -1899,6 +1902,8 @@ void UBDocumentController::createNewDocumentGroup() QModelIndex newIndex = docModel->addCatalog(newFolderName, parentIndex); mDocumentUI->documentTreeView->setSelectedAndExpanded(newIndex, true, true); + + pageSelectionChanged(); } @@ -1945,7 +1950,6 @@ void UBDocumentController::TreeViewSelectionChanged(const QModelIndex ¤t, { currentDocumentProxy = docModel->proxyData(current_index); setDocument(currentDocumentProxy, false); - clearThumbnailsSelection(); } //N/C - NNE - 20140414 : END @@ -1958,8 +1962,6 @@ void UBDocumentController::TreeViewSelectionChanged(const QModelIndex ¤t, } mCurrentIndexMoved = false; } - - pageSelectionChanged(); } //N/C - NNE - 20140402 : workaround for using a proxy model @@ -2187,8 +2189,6 @@ void UBDocumentController::setupViews() connect(mDocumentUI->thumbnailWidget, SIGNAL(mouseDoubleClick(QGraphicsItem*,int)), this, SLOT(thumbnailPageDoubleClicked(QGraphicsItem*,int))); connect(mDocumentUI->thumbnailWidget, SIGNAL(mouseClick(QGraphicsItem*, int)), this, SLOT(pageClicked(QGraphicsItem*, int))); - connect(UBPersistenceManager::persistenceManager(), SIGNAL(documentSceneCreated(UBDocumentProxy*, int)), this, SLOT(documentSceneChanged(UBDocumentProxy*, int))); - mDocumentUI->thumbnailWidget->setBackgroundBrush(UBSettings::documentViewLightColor); #ifdef Q_WS_MACX @@ -2316,7 +2316,7 @@ void UBDocumentController::show() reorderDocuments(); - updateActions(); + pageSelectionChanged(); if(!mToolsPalette) setupPalettes(); @@ -2423,6 +2423,7 @@ void UBDocumentController::duplicateSelectedItem() } emit reorderDocumentsRequested(); + pageSelectionChanged(); } void UBDocumentController::deleteSelectedItem() @@ -3014,6 +3015,8 @@ void UBDocumentController::addFolderOfImages() reloadThumbnails(); if (selectedDocument() == UBApplication::boardController->selectedDocument()) UBApplication::boardController->reloadThumbnails(); + + pageSelectionChanged(); } } } @@ -3062,6 +3065,8 @@ bool UBDocumentController::addFileToDocument(UBDocumentProxy* document) reloadThumbnails(); if (selectedDocument() == UBApplication::boardController->selectedDocument()) UBApplication::boardController->reloadThumbnails(); + + pageSelectionChanged(); } else { @@ -3340,6 +3345,8 @@ void UBDocumentController::addImages() reloadThumbnails(); if (selectedDocument() == UBApplication::boardController->selectedDocument()) UBApplication::boardController->reloadThumbnails(); + + pageSelectionChanged(); } } } @@ -3873,6 +3880,8 @@ void UBDocumentController::createNewDocumentInUntitledFolder() if (document) pManager->mDocumentTreeStructureModel->markDocumentAsNew(document); + + pageSelectionChanged(); } void UBDocumentController::collapseAll() @@ -3912,4 +3921,5 @@ void UBDocumentController::expandAll() void UBDocumentController::clearThumbnailsSelection() { mDocumentUI->thumbnailWidget->clearSelection(); + pageSelectionChanged(); } From ee0ce302011e37cc29b25f2be13b89fea2d4b2e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Thu, 31 Mar 2022 09:29:29 +0200 Subject: [PATCH 114/130] fixed a crash caused by scene indexes deleted in ascending order --- src/document/UBDocumentController.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/document/UBDocumentController.cpp b/src/document/UBDocumentController.cpp index 133f3975..104c1dfd 100644 --- a/src/document/UBDocumentController.cpp +++ b/src/document/UBDocumentController.cpp @@ -3649,6 +3649,7 @@ void UBDocumentController::deletePages(QList itemsToDelete) UBDocumentContainer::deletePages(sceneIndexes); if (mBoardController->selectedDocument() == selectedDocument()) { + std::sort(sceneIndexes.begin(), sceneIndexes.end(), std::greater<>()); for (auto index : sceneIndexes) mBoardController->deleteThumbPage(index); } From b31610826228455dbd3680026146cd5dcca0dfa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Thu, 31 Mar 2022 09:46:57 +0200 Subject: [PATCH 115/130] fixed a typo (thank you @letsfindaway ;)) --- resources/forms/mainWindow.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/forms/mainWindow.ui b/resources/forms/mainWindow.ui index d4ef4ff9..48d40c93 100644 --- a/resources/forms/mainWindow.ui +++ b/resources/forms/mainWindow.ui @@ -1503,7 +1503,7 @@ :/images/toolbar/addToolToLibrary.png:/images/toolbar/addToolToLibrary.png - Capture Wep Content + Capture Web Content Capture Web Content From eedf2867a1d00c32ffbb4940a70a8f28220add34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Thu, 31 Mar 2022 13:23:28 +0200 Subject: [PATCH 116/130] fixed an issue where the ticker would not update after clicking stop + fixed an issue on linux where play button would have to be clicked several times to replay a video that has been watched entirely (the RessourceError message remains but the user just need to click play once now) --- src/domain/UBGraphicsMediaItem.cpp | 37 +++++++++++++++------- src/domain/UBGraphicsMediaItem.h | 3 ++ src/domain/UBGraphicsMediaItemDelegate.cpp | 19 +++++++++-- 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/src/domain/UBGraphicsMediaItem.cpp b/src/domain/UBGraphicsMediaItem.cpp index ddc61513..8ec7da2e 100644 --- a/src/domain/UBGraphicsMediaItem.cpp +++ b/src/domain/UBGraphicsMediaItem.cpp @@ -68,6 +68,7 @@ UBGraphicsMediaItem::UBGraphicsMediaItem(const QUrl& pMediaFileUrl, QGraphicsIte , mMuted(sIsMutedByDefault) , mMutedByUserAction(sIsMutedByDefault) , mStopped(false) + , mFirstLoad(true) , mMediaFileUrl(pMediaFileUrl) , mLinkedImage(NULL) , mInitialPos(0) @@ -135,8 +136,8 @@ UBGraphicsVideoItem::UBGraphicsVideoItem(const QUrl &pMediaFileUrl, QGraphicsIte * active scene has changed, or when the item is first created. * If and when Qt fix this issue, this should be changed back. * */ - //mMediaObject->setVideoOutput(mVideoItem); - mHasVideoOutput = false; + mMediaObject->setVideoOutput(mVideoItem); + mHasVideoOutput = true; mMediaObject->setNotifyInterval(50); @@ -175,12 +176,12 @@ QVariant UBGraphicsMediaItem::itemChange(GraphicsItemChange change, const QVaria || (change == QGraphicsItem::ItemVisibleChange)) { if (mMediaObject && (!isEnabled() || !isVisible() || !scene())) - mMediaObject->pause(); + pause(); } else if (change == QGraphicsItem::ItemSceneHasChanged) { if (!scene()) - mMediaObject->stop(); + stop(); else { QString absoluteMediaFilename; @@ -225,6 +226,16 @@ bool UBGraphicsMediaItem::isStopped() const return mStopped; } +bool UBGraphicsMediaItem::firstLoad() const +{ + return mFirstLoad; +} + +void UBGraphicsMediaItem::setFirstLoad(bool firstLoad) +{ + mFirstLoad = firstLoad; +} + qint64 UBGraphicsMediaItem::mediaDuration() const { return mMediaObject->duration(); @@ -333,7 +344,7 @@ UBGraphicsScene* UBGraphicsMediaItem::scene() void UBGraphicsMediaItem::activeSceneChanged() { if (UBApplication::boardController->activeScene() != scene()) - mMediaObject->pause(); + pause(); } @@ -374,17 +385,19 @@ void UBGraphicsMediaItem::togglePlayPause() } if (mMediaObject->state() == QMediaPlayer::StoppedState) - mMediaObject->play(); + { + play(); + } else if (mMediaObject->state() == QMediaPlayer::PlayingState) { if ((mMediaObject->duration() - mMediaObject->position()) <= 0) { - mMediaObject->stop(); - mMediaObject->play(); + stop(); + play(); } else { - mMediaObject->pause(); + pause(); if(scene()) scene()->setModified(true); } @@ -392,14 +405,14 @@ void UBGraphicsMediaItem::togglePlayPause() else if (mMediaObject->state() == QMediaPlayer::PausedState) { if ((mMediaObject->duration() - mMediaObject->position()) <= 0) - mMediaObject->stop(); + stop(); - mMediaObject->play(); + play(); } else if ( mMediaObject->mediaStatus() == QMediaPlayer::LoadingMedia) { mMediaObject->setMedia(mediaFileUrl()); - mMediaObject->play(); + play(); } } diff --git a/src/domain/UBGraphicsMediaItem.h b/src/domain/UBGraphicsMediaItem.h index 5f2ba757..5ec5eb08 100644 --- a/src/domain/UBGraphicsMediaItem.h +++ b/src/domain/UBGraphicsMediaItem.h @@ -88,6 +88,8 @@ public: bool isPlaying() const { return (mMediaObject->state() == QMediaPlayer::PlayingState); } bool isPaused() const { return (mMediaObject->state() == QMediaPlayer::PausedState); } bool isStopped() const; + bool firstLoad() const; + void setFirstLoad(bool firstLoad); QRectF boundingRect() const; @@ -140,6 +142,7 @@ protected: bool mMutedByUserAction; static bool sIsMutedByDefault; bool mStopped; + bool mFirstLoad; QUrl mMediaFileUrl; QString mMediaSource; diff --git a/src/domain/UBGraphicsMediaItemDelegate.cpp b/src/domain/UBGraphicsMediaItemDelegate.cpp index cea439ce..c510a59d 100644 --- a/src/domain/UBGraphicsMediaItemDelegate.cpp +++ b/src/domain/UBGraphicsMediaItemDelegate.cpp @@ -246,15 +246,22 @@ void UBGraphicsMediaItemDelegate::mediaStatusChanged(QMediaPlayer::MediaStatus s #ifndef Q_OS_OSX if ((status == QMediaPlayer::LoadedMedia || status == QMediaPlayer::BufferedMedia) && delegated()->mediaPosition() == delegated()->initialPos() - && !delegated()->isStopped()) { + && !delegated()->isStopped() + && delegated()->firstLoad() + ) + { delegated()->play(); delegated()->pause(); + delegated()->setFirstLoad(false); } #endif // At the end of the video, make sure the progress bar doesn't autohide if (status == QMediaPlayer::EndOfMedia) + { + delegated()->setFirstLoad(true); showToolBar(false); + } // in most cases, the only necessary action is to update the play/pause state @@ -267,6 +274,9 @@ void UBGraphicsMediaItemDelegate::mediaStateChanged(QMediaPlayer::State state) // Possible states are StoppedState, PlayingState and PausedState // updatePlayPauseState handles this functionality + if (state == QMediaPlayer::StoppedState) + delegated()->setMediaPos(0); + updatePlayPauseState(); } @@ -282,8 +292,11 @@ void UBGraphicsMediaItemDelegate::updatePlayPauseState() void UBGraphicsMediaItemDelegate::updateTicker(qint64 time) { - mMediaControl->totalTimeChanged(delegated()->mediaDuration()); - mMediaControl->updateTicker(time); + if (!delegated()->isStopped()) + { + mMediaControl->totalTimeChanged(delegated()->mediaDuration()); + mMediaControl->updateTicker(time); + } } From b401d4917605af80d421caaa8c6863ee4b498d52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Thu, 31 Mar 2022 14:42:20 +0200 Subject: [PATCH 117/130] fixed two issues where actions would not be updated correctly --- src/document/UBDocumentController.cpp | 4 +--- src/document/UBDocumentController.h | 5 +++-- src/gui/UBDocumentThumbnailWidget.cpp | 1 - src/gui/UBThumbnailWidget.cpp | 4 ++-- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/document/UBDocumentController.cpp b/src/document/UBDocumentController.cpp index 104c1dfd..55d82ce3 100644 --- a/src/document/UBDocumentController.cpp +++ b/src/document/UBDocumentController.cpp @@ -1592,7 +1592,7 @@ void UBDocumentTreeView::dropEvent(QDropEvent *event) QTreeView::dropEvent(event); - UBApplication::documentController->updateActions(); + UBApplication::documentController->pageSelectionChanged(); } void UBDocumentTreeView::paintEvent(QPaintEvent *event) @@ -2445,8 +2445,6 @@ void UBDocumentController::deleteSelectedItem() } pageSelectionChanged(); - - updateActions(); } void UBDocumentController::deleteMultipleItems(QModelIndexList indexes, UBDocumentTreeModel* docModel) diff --git a/src/document/UBDocumentController.h b/src/document/UBDocumentController.h index 87e7e83a..03c76e92 100644 --- a/src/document/UBDocumentController.h +++ b/src/document/UBDocumentController.h @@ -492,7 +492,6 @@ class UBDocumentController : public UBDocumentContainer void copy(); void paste(); void focusChanged(QWidget *old, QWidget *current); - void updateActions(); void updateExportSubActions(const QModelIndex &selectedIndex); void currentIndexMoved(const QModelIndex &newIndex, const QModelIndex &PreviousIndex); @@ -551,6 +550,8 @@ protected: void TreeViewSelectionChanged(const QModelIndex ¤t, const QModelIndex &previous); void TreeViewSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected); + void pageSelectionChanged(); + private slots: void documentZoomSliderValueChanged (int value); void itemSelectionChanged(LastSelectedElementType newSelection); @@ -558,7 +559,7 @@ protected: void exportDocumentSet(); void thumbnailViewResized(); - void pageSelectionChanged(); + void updateActions(); void documentSceneChanged(UBDocumentProxy* proxy, int pSceneIndex); diff --git a/src/gui/UBDocumentThumbnailWidget.cpp b/src/gui/UBDocumentThumbnailWidget.cpp index a948a9b1..aeb2f473 100644 --- a/src/gui/UBDocumentThumbnailWidget.cpp +++ b/src/gui/UBDocumentThumbnailWidget.cpp @@ -58,7 +58,6 @@ UBDocumentThumbnailWidget::~UBDocumentThumbnailWidget() // NOOP } - void UBDocumentThumbnailWidget::mouseMoveEvent(QMouseEvent *event) { if (!dragEnabled()) diff --git a/src/gui/UBThumbnailWidget.cpp b/src/gui/UBThumbnailWidget.cpp index 7d2304d0..779974bc 100644 --- a/src/gui/UBThumbnailWidget.cpp +++ b/src/gui/UBThumbnailWidget.cpp @@ -315,7 +315,6 @@ void UBThumbnailWidget::mousePressEvent(QMouseEvent *event) } mSelectionSpan = index2 - index1; selectItems(qMin(index1, index2), mSelectionSpan < 0 ? - mSelectionSpan + 1 : mSelectionSpan + 1); - return; } } } @@ -334,8 +333,9 @@ void UBThumbnailWidget::mousePressEvent(QMouseEvent *event) if (!mLastSelectedThumbnail && mGraphicItems.count() > 0) mLastSelectedThumbnail = dynamic_cast(mGraphicItems.at(0)); mSelectionSpan = 0; - return; } + + UBApplication::documentController->pageSelectionChanged(); } From 19fd765ac3f677bc61ff484ccb14cb11a86f198a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Fri, 1 Apr 2022 09:28:22 +0200 Subject: [PATCH 118/130] fixed an issue where a copy of an item registered as a tool would not be registered itself as is --- src/board/UBBoardController.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/board/UBBoardController.cpp b/src/board/UBBoardController.cpp index 92e8c48b..5768591e 100644 --- a/src/board/UBBoardController.cpp +++ b/src/board/UBBoardController.cpp @@ -732,11 +732,19 @@ UBGraphicsItem *UBBoardController::duplicateItem(UBItem *item) case UBMimeType::UNKNOWN: { + QGraphicsItem *copiedItem = dynamic_cast(item); QGraphicsItem *gitem = dynamic_cast(item->deepCopy()); if (gitem) { mActiveScene->addItem(gitem); + if (copiedItem) + { + if (mActiveScene->tools().contains(copiedItem)) + { + mActiveScene->registerTool(gitem); + } + } gitem->setPos(itemPos); mLastCreatedItem = gitem; From 1a8c84c79e0312edb1fe763933a690faebb58518 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Fri, 1 Apr 2022 11:34:41 +0200 Subject: [PATCH 119/130] updated translations --- resources/i18n/OpenBoard_ar.ts | 14 +++++++++++--- resources/i18n/OpenBoard_bg.ts | 14 +++++++++++--- resources/i18n/OpenBoard_ca.ts | 14 +++++++++++--- resources/i18n/OpenBoard_cs.ts | 14 +++++++++++--- resources/i18n/OpenBoard_da.ts | 14 +++++++++++--- resources/i18n/OpenBoard_de.ts | 14 +++++++++++--- resources/i18n/OpenBoard_el.ts | 14 +++++++++++--- resources/i18n/OpenBoard_en.ts | 20 ++++++++------------ resources/i18n/OpenBoard_en_UK.ts | 20 ++++++++------------ resources/i18n/OpenBoard_es.ts | 14 +++++++++++--- resources/i18n/OpenBoard_fr.ts | 14 +++++++++++--- resources/i18n/OpenBoard_fr_CH.ts | 14 +++++++++++--- resources/i18n/OpenBoard_gl.ts | 14 +++++++++++--- resources/i18n/OpenBoard_hu.ts | 14 +++++++++++--- resources/i18n/OpenBoard_it.ts | 14 +++++++++++--- resources/i18n/OpenBoard_iw.ts | 14 +++++++++++--- resources/i18n/OpenBoard_ja.ts | 14 +++++++++++--- resources/i18n/OpenBoard_ko.ts | 14 +++++++++++--- resources/i18n/OpenBoard_mg.ts | 14 +++++++++++--- resources/i18n/OpenBoard_nb.ts | 14 +++++++++++--- resources/i18n/OpenBoard_nl.ts | 14 +++++++++++--- resources/i18n/OpenBoard_pl.ts | 14 +++++++++++--- resources/i18n/OpenBoard_pt.ts | 14 +++++++++++--- resources/i18n/OpenBoard_ro.ts | 14 +++++++++++--- resources/i18n/OpenBoard_ru.ts | 14 +++++++++++--- resources/i18n/OpenBoard_sk.ts | 14 +++++++++++--- resources/i18n/OpenBoard_sv.ts | 14 +++++++++++--- resources/i18n/OpenBoard_tr.ts | 14 +++++++++++--- resources/i18n/OpenBoard_uk.ts | 14 +++++++++++--- resources/i18n/OpenBoard_zh.ts | 14 +++++++++++--- resources/i18n/OpenBoard_zh_CN.ts | 14 +++++++++++--- resources/i18n/OpenBoard_zh_TW.ts | 14 +++++++++++--- 32 files changed, 346 insertions(+), 114 deletions(-) diff --git a/resources/i18n/OpenBoard_ar.ts b/resources/i18n/OpenBoard_ar.ts index e5459ac7..3f19c962 100644 --- a/resources/i18n/OpenBoard_ar.ts +++ b/resources/i18n/OpenBoard_ar.ts @@ -697,11 +697,11 @@ Flash Trap - قلاش تراب + قلاش تراب Trap Flash Content - محتوى فلاش تراب + محتوى فلاش تراب Web Trap @@ -839,6 +839,10 @@ Draw intermediate grid lines + + Capture Web Content + + PasswordDialog @@ -2952,7 +2956,7 @@ p, li { white-space: pre-wrap; } Select a flash to trap - اختيار فلاش للامساك به + اختيار فلاش للامساك به about:blank @@ -2966,5 +2970,9 @@ p, li { white-space: pre-wrap; } Create Application إنشاء تطبيق + + Select a content to capture + + diff --git a/resources/i18n/OpenBoard_bg.ts b/resources/i18n/OpenBoard_bg.ts index a03c28fe..1628b746 100644 --- a/resources/i18n/OpenBoard_bg.ts +++ b/resources/i18n/OpenBoard_bg.ts @@ -697,11 +697,11 @@ Flash Trap - Инструмент за Flash съдържание + Инструмент за Flash съдържание Trap Flash Content - Съдържание на Flash + Съдържание на Flash Web Trap @@ -839,6 +839,10 @@ Draw intermediate grid lines + + Capture Web Content + + PasswordDialog @@ -2932,7 +2936,7 @@ p, li { white-space: pre-wrap; } Select a flash to trap - Избери флаш , който ще използваш + Избери флаш , който ще използваш about:blank @@ -2946,5 +2950,9 @@ p, li { white-space: pre-wrap; } Create Application Създаване на приложение + + Select a content to capture + + diff --git a/resources/i18n/OpenBoard_ca.ts b/resources/i18n/OpenBoard_ca.ts index 98115d42..e18fcfd4 100644 --- a/resources/i18n/OpenBoard_ca.ts +++ b/resources/i18n/OpenBoard_ca.ts @@ -729,11 +729,11 @@ Flash Trap - Captura d'objectes Flash + Captura d'objectes Flash Trap Flash Content - Captura un objecte Flash + Captura un objecte Flash Web Trap @@ -839,6 +839,10 @@ Draw intermediate grid lines + + Capture Web Content + + PasswordDialog @@ -2932,7 +2936,7 @@ p, li { white-space: pre-wrap; } Select a flash to trap - Seleccioneu l'objecte Flash a capturar + Seleccioneu l'objecte Flash a capturar about:blank @@ -2946,5 +2950,9 @@ p, li { white-space: pre-wrap; } Create Application Crea una aplicació + + Select a content to capture + + diff --git a/resources/i18n/OpenBoard_cs.ts b/resources/i18n/OpenBoard_cs.ts index 9516eb32..2860b481 100644 --- a/resources/i18n/OpenBoard_cs.ts +++ b/resources/i18n/OpenBoard_cs.ts @@ -374,7 +374,7 @@ Trap Flash Content - Přenést obsah ve Flashi + Přenést obsah ve Flashi Import @@ -562,7 +562,7 @@ Flash Trap - Přenést Flash + Přenést Flash Window Capture @@ -840,6 +840,10 @@ Draw intermediate grid lines + + Capture Web Content + + PasswordDialog @@ -2938,7 +2942,7 @@ p, li { white-space: pre-wrap; } Select a flash to trap - Vyberte flash, který chcete přenést + Vyberte flash, který chcete přenést about:blank @@ -2952,5 +2956,9 @@ p, li { white-space: pre-wrap; } Create Application Vytvořit aplikaci + + Select a content to capture + + diff --git a/resources/i18n/OpenBoard_da.ts b/resources/i18n/OpenBoard_da.ts index cf1655a8..35cd0e1c 100644 --- a/resources/i18n/OpenBoard_da.ts +++ b/resources/i18n/OpenBoard_da.ts @@ -697,11 +697,11 @@ Flash Trap - Flash Trap + Flash Trap Trap Flash Content - Opsaml Flash-indhold + Opsaml Flash-indhold Web Trap @@ -839,6 +839,10 @@ Draw intermediate grid lines + + Capture Web Content + + PasswordDialog @@ -2935,7 +2939,7 @@ p, li { white-space: pre-wrap; } Select a flash to trap - Vælg en flash der skal opsamles + Vælg en flash der skal opsamles about:blank @@ -2949,5 +2953,9 @@ p, li { white-space: pre-wrap; } Create Application Opret applikation + + Select a content to capture + + diff --git a/resources/i18n/OpenBoard_de.ts b/resources/i18n/OpenBoard_de.ts index 169e01b7..fd9f56b9 100644 --- a/resources/i18n/OpenBoard_de.ts +++ b/resources/i18n/OpenBoard_de.ts @@ -697,11 +697,11 @@ Flash Trap - Einblendung einfangen + Einblendung einfangen Trap Flash Content - Eingeblendeten Inhalt einfangen + Eingeblendeten Inhalt einfangen Web Trap @@ -839,6 +839,10 @@ Draw intermediate grid lines Gitter-Zwischenlinien zeichnen + + Capture Web Content + + PasswordDialog @@ -3006,7 +3010,7 @@ p, li { white-space: pre-wrap; } Select a flash to trap - Wählen Sie eine Einblendung, die festgehalten werden soll + Wählen Sie eine Einblendung, die festgehalten werden soll about:blank @@ -3020,5 +3024,9 @@ p, li { white-space: pre-wrap; } Create Application Anwendung erstellen + + Select a content to capture + + diff --git a/resources/i18n/OpenBoard_el.ts b/resources/i18n/OpenBoard_el.ts index f9057609..4296a070 100644 --- a/resources/i18n/OpenBoard_el.ts +++ b/resources/i18n/OpenBoard_el.ts @@ -697,11 +697,11 @@ Flash Trap - Λήψη αντικειμένου flash + Λήψη αντικειμένου flash Trap Flash Content - Λήψη περιεχομένου ενός αντικειμένου flash + Λήψη περιεχομένου ενός αντικειμένου flash Web Trap @@ -839,6 +839,10 @@ Draw intermediate grid lines + + Capture Web Content + + PasswordDialog @@ -2932,7 +2936,7 @@ p, li { white-space: pre-wrap; } Select a flash to trap - Επιλογή αντικειμένου flash για λήψη + Επιλογή αντικειμένου flash για λήψη about:blank @@ -2946,5 +2950,9 @@ p, li { white-space: pre-wrap; } Create Application Δημιουργία εφαρμογής + + Select a content to capture + + diff --git a/resources/i18n/OpenBoard_en.ts b/resources/i18n/OpenBoard_en.ts index 99d431b2..ca3377a0 100644 --- a/resources/i18n/OpenBoard_en.ts +++ b/resources/i18n/OpenBoard_en.ts @@ -703,14 +703,6 @@ Configure Podcast Recording - - Flash Trap - - - - Trap Flash Content - - Web Trap @@ -831,6 +823,10 @@ Draw intermediate grid lines + + Capture Web Content + + PasswordDialog @@ -2746,10 +2742,6 @@ p, li { white-space: pre-wrap; } Trap flash - - Select a flash to trap - - about:blank @@ -2762,5 +2754,9 @@ p, li { white-space: pre-wrap; } Create Application + + Select a content to capture + + diff --git a/resources/i18n/OpenBoard_en_UK.ts b/resources/i18n/OpenBoard_en_UK.ts index 99d431b2..ca3377a0 100644 --- a/resources/i18n/OpenBoard_en_UK.ts +++ b/resources/i18n/OpenBoard_en_UK.ts @@ -703,14 +703,6 @@ Configure Podcast Recording - - Flash Trap - - - - Trap Flash Content - - Web Trap @@ -831,6 +823,10 @@ Draw intermediate grid lines + + Capture Web Content + + PasswordDialog @@ -2746,10 +2742,6 @@ p, li { white-space: pre-wrap; } Trap flash - - Select a flash to trap - - about:blank @@ -2762,5 +2754,9 @@ p, li { white-space: pre-wrap; } Create Application + + Select a content to capture + + diff --git a/resources/i18n/OpenBoard_es.ts b/resources/i18n/OpenBoard_es.ts index c80f0755..8569eca5 100644 --- a/resources/i18n/OpenBoard_es.ts +++ b/resources/i18n/OpenBoard_es.ts @@ -697,11 +697,11 @@ Flash Trap - Captura de elemento Flash + Captura de elemento Flash Trap Flash Content - Capturar contenido Flash + Capturar contenido Flash Web Trap @@ -839,6 +839,10 @@ Draw intermediate grid lines + + Capture Web Content + + PasswordDialog @@ -2952,7 +2956,7 @@ p, li { white-space: pre-wrap; } Select a flash to trap - Seleccionar un flash para capturar + Seleccionar un flash para capturar about:blank @@ -2966,5 +2970,9 @@ p, li { white-space: pre-wrap; } Create Application Crear aplicación + + Select a content to capture + + diff --git a/resources/i18n/OpenBoard_fr.ts b/resources/i18n/OpenBoard_fr.ts index ae910ba1..8551977e 100644 --- a/resources/i18n/OpenBoard_fr.ts +++ b/resources/i18n/OpenBoard_fr.ts @@ -181,11 +181,11 @@ Flash Trap - Capturer du contenu Flash + Capturer du contenu Flash Trap Flash Content - Capturer du contenu Flash + Capturer du contenu Flash Web Trap @@ -840,6 +840,10 @@ Draw intermediate grid lines Dessiner des lignes intermédiaires + + Capture Web Content + Capturer du contenu web + PasswordDialog @@ -3023,7 +3027,7 @@ p, li { white-space: pre-wrap; } Select a flash to trap - Sélectionner un contenu Flash à capturer + Sélectionner un contenu Flash à capturer about:blank @@ -3037,5 +3041,9 @@ p, li { white-space: pre-wrap; } Create Application Créer une application + + Select a content to capture + Sélectionner un contenu à capturer + diff --git a/resources/i18n/OpenBoard_fr_CH.ts b/resources/i18n/OpenBoard_fr_CH.ts index 7bb7dafc..408972ca 100644 --- a/resources/i18n/OpenBoard_fr_CH.ts +++ b/resources/i18n/OpenBoard_fr_CH.ts @@ -181,11 +181,11 @@ Flash Trap - Capturer du contenu Flash + Capturer du contenu Flash Trap Flash Content - Capturer du contenu Flash + Capturer du contenu Flash Web Trap @@ -840,6 +840,10 @@ Draw intermediate grid lines Dessiner des lignes intermédiaires + + Capture Web Content + Capturer du contenu web + PasswordDialog @@ -3023,7 +3027,7 @@ p, li { white-space: pre-wrap; } Select a flash to trap - Sélectionner un contenu Flash à capturer + Sélectionner un contenu Flash à capturer about:blank @@ -3037,5 +3041,9 @@ p, li { white-space: pre-wrap; } Create Application Créer une application + + Select a content to capture + Sélectionner un contenu à capturer + diff --git a/resources/i18n/OpenBoard_gl.ts b/resources/i18n/OpenBoard_gl.ts index b7378865..6d56580c 100644 --- a/resources/i18n/OpenBoard_gl.ts +++ b/resources/i18n/OpenBoard_gl.ts @@ -697,11 +697,11 @@ Flash Trap - Captura de elemento Flash + Captura de elemento Flash Trap Flash Content - Capturar contido Flash + Capturar contido Flash Web Trap @@ -839,6 +839,10 @@ Draw intermediate grid lines + + Capture Web Content + + PasswordDialog @@ -2929,7 +2933,7 @@ p, li { white-space: pre-wrap; } Select a flash to trap - Seleccionar un flash para capturar + Seleccionar un flash para capturar about:blank @@ -2943,5 +2947,9 @@ p, li { white-space: pre-wrap; } Create Application Crear aplicación + + Select a content to capture + + diff --git a/resources/i18n/OpenBoard_hu.ts b/resources/i18n/OpenBoard_hu.ts index 43a3a97a..9f8bdf4f 100644 --- a/resources/i18n/OpenBoard_hu.ts +++ b/resources/i18n/OpenBoard_hu.ts @@ -701,11 +701,11 @@ Flash Trap - Flash rögzítése + Flash rögzítése Trap Flash Content - Flash tartalom rögzítése + Flash tartalom rögzítése Web Trap @@ -839,6 +839,10 @@ Draw intermediate grid lines Köztes rácsvonalak rajzolása + + Capture Web Content + + PasswordDialog @@ -2889,7 +2893,7 @@ p, li { white-space: pre-wrap; } Select a flash to trap - Flash kiválasztása a rögzítéshez + Flash kiválasztása a rögzítéshez about:blank @@ -2903,5 +2907,9 @@ p, li { white-space: pre-wrap; } Create Application Alkalmazás létrehozása + + Select a content to capture + + diff --git a/resources/i18n/OpenBoard_it.ts b/resources/i18n/OpenBoard_it.ts index e946acf0..b19ee525 100644 --- a/resources/i18n/OpenBoard_it.ts +++ b/resources/i18n/OpenBoard_it.ts @@ -697,11 +697,11 @@ Flash Trap - Cattura Flash + Cattura Flash Trap Flash Content - Cattura contenuto flash + Cattura contenuto flash Web Trap @@ -839,6 +839,10 @@ Draw intermediate grid lines disegna linee di griglia intermedie + + Capture Web Content + + PasswordDialog @@ -2997,7 +3001,7 @@ p, li { white-space: pre-wrap; } Select a flash to trap - Seleziona un'applicazione flash da catturare + Seleziona un'applicazione flash da catturare about:blank @@ -3011,5 +3015,9 @@ p, li { white-space: pre-wrap; } Create Application Crea applicazione + + Select a content to capture + + diff --git a/resources/i18n/OpenBoard_iw.ts b/resources/i18n/OpenBoard_iw.ts index 43c543e4..a94f9f82 100644 --- a/resources/i18n/OpenBoard_iw.ts +++ b/resources/i18n/OpenBoard_iw.ts @@ -697,11 +697,11 @@ Flash Trap - מלכודת פלאש + מלכודת פלאש Trap Flash Content - תוכן מלכודת פלאש + תוכן מלכודת פלאש Web Trap @@ -840,6 +840,10 @@ Draw intermediate grid lines + + Capture Web Content + + PasswordDialog @@ -2924,7 +2928,7 @@ p, li { white-space: pre-wrap; } Select a flash to trap - בחר פלאש ללכידה + בחר פלאש ללכידה about:blank @@ -2938,5 +2942,9 @@ p, li { white-space: pre-wrap; } Create Application צור יישום + + Select a content to capture + + diff --git a/resources/i18n/OpenBoard_ja.ts b/resources/i18n/OpenBoard_ja.ts index 7e5b1721..8051862e 100644 --- a/resources/i18n/OpenBoard_ja.ts +++ b/resources/i18n/OpenBoard_ja.ts @@ -697,11 +697,11 @@ Flash Trap - フラッシュトラップ + フラッシュトラップ Trap Flash Content - トラップフラッシュコンテンツ + トラップフラッシュコンテンツ Web Trap @@ -839,6 +839,10 @@ Draw intermediate grid lines + + Capture Web Content + + PasswordDialog @@ -2920,7 +2924,7 @@ p, li { white-space: pre-wrap; } Select a flash to trap - フラッシュを選択してトラップする + フラッシュを選択してトラップする about:blank @@ -2934,5 +2938,9 @@ p, li { white-space: pre-wrap; } Create Application アプリケーションを作成 + + Select a content to capture + + diff --git a/resources/i18n/OpenBoard_ko.ts b/resources/i18n/OpenBoard_ko.ts index d59d7947..3e0cfe29 100644 --- a/resources/i18n/OpenBoard_ko.ts +++ b/resources/i18n/OpenBoard_ko.ts @@ -697,11 +697,11 @@ Flash Trap - 플래시 트랩 + 플래시 트랩 Trap Flash Content - 플래시 콘텐츠 트랩 + 플래시 콘텐츠 트랩 Web Trap @@ -839,6 +839,10 @@ Draw intermediate grid lines + + Capture Web Content + + PasswordDialog @@ -2943,7 +2947,7 @@ p, li { white-space: pre-wrap; } Select a flash to trap - 플래시를 트랩으로 저장 + 플래시를 트랩으로 저장 about:blank @@ -2957,5 +2961,9 @@ p, li { white-space: pre-wrap; } Create Application 응용 프로그램 만들기 + + Select a content to capture + + diff --git a/resources/i18n/OpenBoard_mg.ts b/resources/i18n/OpenBoard_mg.ts index a5c59cc0..98e8a6db 100644 --- a/resources/i18n/OpenBoard_mg.ts +++ b/resources/i18n/OpenBoard_mg.ts @@ -681,11 +681,11 @@ Flash Trap - Alaina ny Flash + Alaina ny Flash Trap Flash Content - Alaina ny mpiatiny Flash + Alaina ny mpiatiny Flash Web Trap @@ -839,6 +839,10 @@ Draw intermediate grid lines + + Capture Web Content + + PasswordDialog @@ -2939,7 +2943,7 @@ p, li { white-space: pre-wrap; } Select a flash to trap - Safidio ny Flash ho alaina + Safidio ny Flash ho alaina about:blank @@ -2953,5 +2957,9 @@ p, li { white-space: pre-wrap; } Create Application Mamorona rindran'asa + + Select a content to capture + + diff --git a/resources/i18n/OpenBoard_nb.ts b/resources/i18n/OpenBoard_nb.ts index 82d376aa..e03e507c 100644 --- a/resources/i18n/OpenBoard_nb.ts +++ b/resources/i18n/OpenBoard_nb.ts @@ -697,11 +697,11 @@ Flash Trap - Flash trap + Flash trap Trap Flash Content - Trap flash-innhold + Trap flash-innhold Web Trap @@ -839,6 +839,10 @@ Draw intermediate grid lines + + Capture Web Content + + PasswordDialog @@ -2929,7 +2933,7 @@ p, li { white-space: pre-wrap; } Select a flash to trap - Velg flash som skal tas + Velg flash som skal tas about:blank @@ -2943,5 +2947,9 @@ p, li { white-space: pre-wrap; } Create Application Opprett applikasjon + + Select a content to capture + + diff --git a/resources/i18n/OpenBoard_nl.ts b/resources/i18n/OpenBoard_nl.ts index 9fded612..cd013ed2 100644 --- a/resources/i18n/OpenBoard_nl.ts +++ b/resources/i18n/OpenBoard_nl.ts @@ -697,11 +697,11 @@ Flash Trap - Flash Trap + Flash Trap Trap Flash Content - Trap flash inhoud + Trap flash inhoud Web Trap @@ -839,6 +839,10 @@ Draw intermediate grid lines + + Capture Web Content + + PasswordDialog @@ -2928,7 +2932,7 @@ p, li { white-space: pre-wrap; } Select a flash to trap - Te vangen licht selecteren + Te vangen licht selecteren about:blank @@ -2942,5 +2946,9 @@ p, li { white-space: pre-wrap; } Create Application Applicatie maken + + Select a content to capture + + diff --git a/resources/i18n/OpenBoard_pl.ts b/resources/i18n/OpenBoard_pl.ts index 2af7cb25..f1bc06d8 100644 --- a/resources/i18n/OpenBoard_pl.ts +++ b/resources/i18n/OpenBoard_pl.ts @@ -697,11 +697,11 @@ Flash Trap - Pułapka Flash + Pułapka Flash Trap Flash Content - Złap zawartość Flash + Złap zawartość Flash Web Trap @@ -842,6 +842,10 @@ Draw intermediate grid lines Rysuj drobne linie kratki + + Capture Web Content + + PasswordDialog @@ -2950,7 +2954,7 @@ p, li { white-space: pre-wrap; } Select a flash to trap - Wybierz Flash do złapania + Wybierz Flash do złapania about:blank @@ -2964,5 +2968,9 @@ p, li { white-space: pre-wrap; } Create Application Stwórz aplikację + + Select a content to capture + + diff --git a/resources/i18n/OpenBoard_pt.ts b/resources/i18n/OpenBoard_pt.ts index 5aec6470..c79bf7b2 100644 --- a/resources/i18n/OpenBoard_pt.ts +++ b/resources/i18n/OpenBoard_pt.ts @@ -698,11 +698,11 @@ Flash Trap - Captura de Flash + Captura de Flash Trap Flash Content - Captura de Conteúdo Flash + Captura de Conteúdo Flash Web Trap @@ -841,6 +841,10 @@ Draw intermediate grid lines + + Capture Web Content + + PasswordDialog @@ -2968,7 +2972,7 @@ p, li { white-space: pre-wrap; } Select a flash to trap - Selecionar o flash a capturar + Selecionar o flash a capturar about:blank @@ -2982,5 +2986,9 @@ p, li { white-space: pre-wrap; } Create Application Criar Aplicação + + Select a content to capture + + diff --git a/resources/i18n/OpenBoard_ro.ts b/resources/i18n/OpenBoard_ro.ts index d0176e37..0863a9fc 100644 --- a/resources/i18n/OpenBoard_ro.ts +++ b/resources/i18n/OpenBoard_ro.ts @@ -697,11 +697,11 @@ Flash Trap - Blocare flash + Blocare flash Trap Flash Content - Blocare conţinut flash + Blocare conţinut flash Web Trap @@ -839,6 +839,10 @@ Draw intermediate grid lines + + Capture Web Content + + PasswordDialog @@ -2939,7 +2943,7 @@ p, li { white-space: pre-wrap; } Select a flash to trap - Selectaţi un flash pentru blocare + Selectaţi un flash pentru blocare about:blank @@ -2953,5 +2957,9 @@ p, li { white-space: pre-wrap; } Create Application Creare aplicaţie + + Select a content to capture + + diff --git a/resources/i18n/OpenBoard_ru.ts b/resources/i18n/OpenBoard_ru.ts index dee5d9f1..fade77ea 100644 --- a/resources/i18n/OpenBoard_ru.ts +++ b/resources/i18n/OpenBoard_ru.ts @@ -697,11 +697,11 @@ Flash Trap - Захват флэш + Захват флэш Trap Flash Content - Захват флэш-содержимого + Захват флэш-содержимого Web Trap @@ -839,6 +839,10 @@ Draw intermediate grid lines + + Capture Web Content + + PasswordDialog @@ -2941,7 +2945,7 @@ p, li { white-space: pre-wrap; } Select a flash to trap - Выбрать флеш-содержимое для захвата + Выбрать флеш-содержимое для захвата about:blank @@ -2955,5 +2959,9 @@ p, li { white-space: pre-wrap; } Create Application Создать приложение + + Select a content to capture + + diff --git a/resources/i18n/OpenBoard_sk.ts b/resources/i18n/OpenBoard_sk.ts index 6739abcb..fa627759 100644 --- a/resources/i18n/OpenBoard_sk.ts +++ b/resources/i18n/OpenBoard_sk.ts @@ -351,7 +351,7 @@ Trap Flash Content - Označiť obsah vo Flashi + Označiť obsah vo Flashi Import @@ -539,7 +539,7 @@ Flash Trap - Označiť Flash + Označiť Flash Window Capture @@ -843,6 +843,10 @@ Draw intermediate grid lines + + Capture Web Content + + PasswordDialog @@ -2980,7 +2984,7 @@ p, li { white-space: pre-wrap; } Select a flash to trap - Vyberte flash, ktorý chcete označiť + Vyberte flash, ktorý chcete označiť about:blank @@ -2994,5 +2998,9 @@ p, li { white-space: pre-wrap; } Create Application Vytvoriť aplikáciu + + Select a content to capture + + diff --git a/resources/i18n/OpenBoard_sv.ts b/resources/i18n/OpenBoard_sv.ts index 435c3751..f00c99ff 100644 --- a/resources/i18n/OpenBoard_sv.ts +++ b/resources/i18n/OpenBoard_sv.ts @@ -697,11 +697,11 @@ Flash Trap - Lagra flash + Lagra flash Trap Flash Content - Lagra flash innehål + Lagra flash innehål Web Trap @@ -839,6 +839,10 @@ Draw intermediate grid lines + + Capture Web Content + + PasswordDialog @@ -2940,7 +2944,7 @@ p, li { white-space: pre-wrap; } Select a flash to trap - Välj en flash att lagra + Välj en flash att lagra about:blank @@ -2954,5 +2958,9 @@ p, li { white-space: pre-wrap; } Create Application Skapa applikation + + Select a content to capture + + diff --git a/resources/i18n/OpenBoard_tr.ts b/resources/i18n/OpenBoard_tr.ts index 6d372408..d5ed2c55 100644 --- a/resources/i18n/OpenBoard_tr.ts +++ b/resources/i18n/OpenBoard_tr.ts @@ -701,11 +701,11 @@ Flash Trap - Flash Tuzağı + Flash Tuzağı Trap Flash Content - Flash İçeriğini Yakala + Flash İçeriğini Yakala Web Trap @@ -839,6 +839,10 @@ Draw intermediate grid lines + + Capture Web Content + + PasswordDialog @@ -2927,7 +2931,7 @@ p, li { white-space: pre-wrap; } Select a flash to trap - Tuzağa almak için bir flash öğesi seçin + Tuzağa almak için bir flash öğesi seçin about:blank @@ -2941,5 +2945,9 @@ p, li { white-space: pre-wrap; } Create Application Uygulama Oluştur + + Select a content to capture + + diff --git a/resources/i18n/OpenBoard_uk.ts b/resources/i18n/OpenBoard_uk.ts index 02a9c16d..e39b6101 100644 --- a/resources/i18n/OpenBoard_uk.ts +++ b/resources/i18n/OpenBoard_uk.ts @@ -697,11 +697,11 @@ Flash Trap - Захоплення флеш + Захоплення флеш Trap Flash Content - Захоплення флеш-вмісту + Захоплення флеш-вмісту Web Trap @@ -839,6 +839,10 @@ Draw intermediate grid lines + + Capture Web Content + + PasswordDialog @@ -2925,7 +2929,7 @@ p, li { white-space: pre-wrap; } Select a flash to trap - Вибрати флеш-вміст для захоплення + Вибрати флеш-вміст для захоплення about:blank @@ -2939,5 +2943,9 @@ p, li { white-space: pre-wrap; } Create Application Створити програму + + Select a content to capture + + diff --git a/resources/i18n/OpenBoard_zh.ts b/resources/i18n/OpenBoard_zh.ts index 838b6327..453b04db 100644 --- a/resources/i18n/OpenBoard_zh.ts +++ b/resources/i18n/OpenBoard_zh.ts @@ -697,11 +697,11 @@ Flash Trap - 截取动画 + 截取动画 Trap Flash Content - 截取动画内容 + 截取动画内容 Web Trap @@ -839,6 +839,10 @@ Draw intermediate grid lines + + Capture Web Content + + PasswordDialog @@ -2927,7 +2931,7 @@ p, li { white-space: pre-wrap; } Select a flash to trap - 选择要截取的动画 + 选择要截取的动画 about:blank @@ -2941,5 +2945,9 @@ p, li { white-space: pre-wrap; } Create Application 创建应用程序 + + Select a content to capture + + diff --git a/resources/i18n/OpenBoard_zh_CN.ts b/resources/i18n/OpenBoard_zh_CN.ts index 838b6327..453b04db 100644 --- a/resources/i18n/OpenBoard_zh_CN.ts +++ b/resources/i18n/OpenBoard_zh_CN.ts @@ -697,11 +697,11 @@ Flash Trap - 截取动画 + 截取动画 Trap Flash Content - 截取动画内容 + 截取动画内容 Web Trap @@ -839,6 +839,10 @@ Draw intermediate grid lines + + Capture Web Content + + PasswordDialog @@ -2927,7 +2931,7 @@ p, li { white-space: pre-wrap; } Select a flash to trap - 选择要截取的动画 + 选择要截取的动画 about:blank @@ -2941,5 +2945,9 @@ p, li { white-space: pre-wrap; } Create Application 创建应用程序 + + Select a content to capture + + diff --git a/resources/i18n/OpenBoard_zh_TW.ts b/resources/i18n/OpenBoard_zh_TW.ts index 19235899..336adbca 100644 --- a/resources/i18n/OpenBoard_zh_TW.ts +++ b/resources/i18n/OpenBoard_zh_TW.ts @@ -681,11 +681,11 @@ Flash Trap - Flash 動畫擷取 + Flash 動畫擷取 Trap Flash Content - 擷取 Flash 動畫內容 + 擷取 Flash 動畫內容 Web Trap @@ -839,6 +839,10 @@ Draw intermediate grid lines + + Capture Web Content + + PasswordDialog @@ -2921,7 +2925,7 @@ p, li { white-space: pre-wrap; } Select a flash to trap - 選擇要擷取的 flash 動畫 + 選擇要擷取的 flash 動畫 about:blank @@ -2935,5 +2939,9 @@ p, li { white-space: pre-wrap; } Create Application 建立應用程式 + + Select a content to capture + + From d206b31a0ba3145f100b3e3d76a3de6a3655a08c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Fri, 1 Apr 2022 13:53:52 +0200 Subject: [PATCH 120/130] fixed an issue where the rotate cursor would not update correctly after deselection/selection --- src/domain/UBGraphicsDelegateFrame.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/domain/UBGraphicsDelegateFrame.cpp b/src/domain/UBGraphicsDelegateFrame.cpp index 36334510..e0970eb5 100644 --- a/src/domain/UBGraphicsDelegateFrame.cpp +++ b/src/domain/UBGraphicsDelegateFrame.cpp @@ -103,11 +103,10 @@ UBGraphicsDelegateFrame::UBGraphicsDelegateFrame(UBGraphicsItemDelegate* pDelega mRotateButton->setCursor(UBResources::resources()->rotateCursor); mRotateButton->setVisible(mDelegate->testUBFlags(GF_REVOLVABLE)); - updateResizeCursors(); - setAntiScale(1.0); positionHandles(); + updateResizeCursors(); this->setAcceptHoverEvents(true); } @@ -839,6 +838,7 @@ void UBGraphicsDelegateFrame::positionHandles() resetTransform(); setTransform(QTransform::fromTranslate(center.x(), center.y()), true); setTransform(QTransform().rotate(-angle), true); + mAngle = angle; setTransform(QTransform::fromTranslate(-center.x(), -center.y()), true); //TODO: combine these transforms into one From d3e82de307a2893b2ffbddea8a68c4050ebcceb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Fri, 1 Apr 2022 14:59:49 +0200 Subject: [PATCH 121/130] call pageSelectionChanged when importing a file --- src/document/UBDocumentController.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/document/UBDocumentController.cpp b/src/document/UBDocumentController.cpp index 55d82ce3..db095b51 100644 --- a/src/document/UBDocumentController.cpp +++ b/src/document/UBDocumentController.cpp @@ -2969,6 +2969,7 @@ void UBDocumentController::importFile() if (createdDocument) { selectDocument(createdDocument, true, true, true); + pageSelectionChanged(); } else { showMessage(tr("Failed to import file ... ")); From 4f4c2e47cc92c0cc143b4e127ab3f28ac29b3379 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Fri, 1 Apr 2022 16:19:24 +0200 Subject: [PATCH 122/130] fixed an issue where buttons corresponding to saved pen width and eraser width would not be checked (at start) --- src/board/UBBoardController.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/board/UBBoardController.cpp b/src/board/UBBoardController.cpp index 5768591e..c2902620 100644 --- a/src/board/UBBoardController.cpp +++ b/src/board/UBBoardController.cpp @@ -346,6 +346,8 @@ void UBBoardController::setupToolbar() , lineWidthChoice, SLOT(setCurrentIndex(int))); lineWidthChoice->displayText(QVariant(settings->appToolBarDisplayText->get().toBool())); + lineWidthChoice->setCurrentIndex(settings->penWidthIndex()); + lineWidthActions.at(settings->penWidthIndex())->setChecked(true); mMainWindow->boardToolBar->insertWidget(mMainWindow->actionBackgrounds, lineWidthChoice); @@ -367,6 +369,7 @@ void UBBoardController::setupToolbar() eraserWidthChoice->displayText(QVariant(settings->appToolBarDisplayText->get().toBool())); eraserWidthChoice->setCurrentIndex(settings->eraserWidthIndex()); + eraserWidthActions.at(settings->eraserWidthIndex())->setChecked(true); mMainWindow->boardToolBar->insertSeparator(mMainWindow->actionBackgrounds); From 3566bc9d0985c56cf9cb6fa2c63e4b0314907cd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Mon, 4 Apr 2022 14:07:27 +0200 Subject: [PATCH 123/130] updated german translations --- resources/i18n/OpenBoard_de.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/i18n/OpenBoard_de.ts b/resources/i18n/OpenBoard_de.ts index fd9f56b9..c6305089 100644 --- a/resources/i18n/OpenBoard_de.ts +++ b/resources/i18n/OpenBoard_de.ts @@ -841,7 +841,7 @@ Capture Web Content - + Webinhalte erfassen @@ -3026,7 +3026,7 @@ p, li { white-space: pre-wrap; } Select a content to capture - + Einen zu erfassenden Inhalt auswählen From 3a80dc4b2b3a4be446cf8c79bda794cf42091952 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Wed, 6 Apr 2022 11:39:53 +0200 Subject: [PATCH 124/130] use standard Qt (5.15.3 on 22.04) --- release_scripts/linux/build.sh | 7 +++---- release_scripts/linux/package.sh | 6 +++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/release_scripts/linux/build.sh b/release_scripts/linux/build.sh index 8379c738..e882f622 100755 --- a/release_scripts/linux/build.sh +++ b/release_scripts/linux/build.sh @@ -17,7 +17,7 @@ initializeVariables() { APPLICATION_NAME="OpenBoard" - STANDARD_QT_USED=false + STANDARD_QT_USED=true # Root directory SCRIPT_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" @@ -26,7 +26,7 @@ initializeVariables() PRODUCT_PATH="$BUILD_DIR/product" # Qt installation path. This may vary across machines - QT_PATH="/home/dev/Qt/5.15.0/gcc_64" + QT_PATH="/usr/lib/x86_64-linux-gnu/qt5" PLUGINS_PATH="$QT_PATH/plugins" GUI_TRANSLATIONS_DIRECTORY_PATH="/usr/share/qt5/translations" QMAKE_PATH="$QT_PATH/bin/qmake" @@ -39,8 +39,7 @@ initializeVariables() ARCHITECTURE=`uname -m` if [ $ARCHITECTURE == "x86_64" ]; then ARCHITECTURE="amd64" - fi - if [$ARCHITECTURE == "armv7l" ]; then + elif [$ARCHITECTURE == "armv7l" ]; then $ARCHITECTURE="armhf" QT_PATH="/usr/lib/arm-linux-gnueabihf/qt5" fi diff --git a/release_scripts/linux/package.sh b/release_scripts/linux/package.sh index 25d07230..2014c3c7 100755 --- a/release_scripts/linux/package.sh +++ b/release_scripts/linux/package.sh @@ -85,13 +85,13 @@ initializeVariables() # Include Qt libraries and plugins in the package, or not # (this is necessary if the target system doesn't provide Qt 5.5.1) - BUNDLE_QT=true + BUNDLE_QT=false # Qt installation path. This may vary across machines - QT_PATH="/home/dev/Qt/5.15.0/gcc_64" + QT_PATH="/usr/lib/x86_64-linux-gnu/qt5" QT_PLUGINS_SOURCE_PATH="$QT_PATH/plugins" GUI_TRANSLATIONS_DIRECTORY_PATH="/usr/share/qt5/translations" - QT_LIBRARY_SOURCE_PATH="$QT_PATH/lib" + QT_LIBRARY_SOURCE_PATH="$QT_PATH/.." NOTIFY_CMD=`which notify-send` ZIP_PATH=`which zip` From b8ff50297ecee7800d997c38beef4355a1f3d880 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Wed, 6 Apr 2022 12:34:46 +0200 Subject: [PATCH 125/130] update thumbnails when a page is dropped in its own document + don't call treeviewselectionChanged if dropped a page as it will not change treeview --- src/document/UBDocumentController.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/document/UBDocumentController.cpp b/src/document/UBDocumentController.cpp index db095b51..f2b7c477 100644 --- a/src/document/UBDocumentController.cpp +++ b/src/document/UBDocumentController.cpp @@ -1558,8 +1558,11 @@ void UBDocumentTreeView::dropEvent(QDropEvent *event) Q_ASSERT(QFileInfo(thumbTo).exists()); auto pix = std::make_shared(thumbTmp); - UBDocumentController *ctrl = UBApplication::documentController; - ctrl->addPixmapAt(pix, toIndex); + UBApplication::documentController->insertExistingThumbPage(toIndex, pix); + if (UBApplication::documentController->selectedDocument() == targetDocProxy) + { + UBApplication::documentController->reloadThumbnails(); + } if (UBApplication::boardController->selectedDocument() == targetDocProxy) { UBApplication::boardController->insertThumbPage(toIndex); @@ -1572,8 +1575,6 @@ void UBDocumentTreeView::dropEvent(QDropEvent *event) } UBApplication::applicationController->showMessage(tr("%1 pages copied", "", total).arg(total), false); - UBApplication::documentController->TreeViewSelectionChanged(UBApplication::documentController->firstSelectedTreeIndex(), QModelIndex()); - } else { From c1d45a3d116b7d46515a2025c92bf86f13661191 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Wed, 6 Apr 2022 14:56:22 +0200 Subject: [PATCH 126/130] call reloadThumbnails when adding a pdf in board mode --- src/board/UBBoardController.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/board/UBBoardController.cpp b/src/board/UBBoardController.cpp index c2902620..1a02262d 100644 --- a/src/board/UBBoardController.cpp +++ b/src/board/UBBoardController.cpp @@ -1439,6 +1439,7 @@ UBItem *UBBoardController::downloadFinished(bool pSuccess, QUrl sourceUrl, QUrl QStringList fileNames; fileNames << pdfFile.fileName(); result = UBDocumentManager::documentManager()->addFilesToDocument(selectedDocument(), fileNames); + reloadThumbnails(); pdfFile.close(); } } From c8795b24d08547d69145e82a0999ea275e6cb2fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Wed, 6 Apr 2022 15:27:41 +0200 Subject: [PATCH 127/130] updated quazip headers location + changed version to 1.6.2rc-0408 --- OpenBoard.pro | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenBoard.pro b/OpenBoard.pro index 3c5d2dc7..dc9f6f23 100644 --- a/OpenBoard.pro +++ b/OpenBoard.pro @@ -11,7 +11,7 @@ VERSION_MAJ = 1 VERSION_MIN = 6 VERSION_PATCH = 2 VERSION_TYPE = rc # a = alpha, b = beta, rc = release candidate, r = release, other => error -VERSION_BUILD = 0323 +VERSION_BUILD = 0408 VERSION = "$${VERSION_MAJ}.$${VERSION_MIN}.$${VERSION_PATCH}-$${VERSION_TYPE}.$${VERSION_BUILD}" @@ -452,7 +452,7 @@ linux-g++* { #LIBS += -lprofiler LIBS += -lX11 LIBS += -lquazip5 - INCLUDEPATH += "/usr/include/quazip" + INCLUDEPATH += "/usr/include/quazip5" LIBS += -lpoppler INCLUDEPATH += "/usr/include/poppler" From 2118671a14df829905ae89f95fbd7c4bfd8558d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Fri, 8 Apr 2022 16:01:57 +0200 Subject: [PATCH 128/130] remove register keyword (in unused code) as it is no longer accpted in C++17 with clang --- src/domain/UBGraphicsItemDelegate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/domain/UBGraphicsItemDelegate.cpp b/src/domain/UBGraphicsItemDelegate.cpp index 49085698..458f0115 100644 --- a/src/domain/UBGraphicsItemDelegate.cpp +++ b/src/domain/UBGraphicsItemDelegate.cpp @@ -1307,7 +1307,7 @@ void MediaTimer::setNumDigits(int numDigits) } else { if (numDigits == ndigits) // no change return; - register int i; + int i; int dif; if (numDigits > ndigits) { // expand dif = numDigits - ndigits; From 60cf43d6e15ac238d8e3c815fc257f9bd9b92065 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Fri, 8 Apr 2022 16:03:18 +0200 Subject: [PATCH 129/130] changed global config to C++17 use --- OpenBoard.pro | 2 +- src/pdf/pdf.pri | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/OpenBoard.pro b/OpenBoard.pro index dc9f6f23..ef132b74 100644 --- a/OpenBoard.pro +++ b/OpenBoard.pro @@ -1,7 +1,7 @@ TARGET = "OpenBoard" TEMPLATE = app -CONFIG += c++14 +CONFIG += c++17 CONFIG -= flat CONFIG += debug_and_release \ no_include_pwd diff --git a/src/pdf/pdf.pri b/src/pdf/pdf.pri index aeb09b5b..82b7bf50 100644 --- a/src/pdf/pdf.pri +++ b/src/pdf/pdf.pri @@ -1,5 +1,3 @@ -CONFIG += c++17 - HEADERS += src/pdf/GraphicsPDFItem.h \ src/pdf/PDFRenderer.h \ src/pdf/UBWebPluginPDFWidget.h \ @@ -9,4 +7,4 @@ SOURCES += src/pdf/GraphicsPDFItem.cpp \ src/pdf/PDFRenderer.cpp \ src/pdf/UBWebPluginPDFWidget.cpp \ src/pdf/XPDFRenderer.cpp - \ No newline at end of file + From a6c99bc0043364f2a23c32122265ebdb81f650f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fauconnier?= Date: Fri, 8 Apr 2022 18:01:46 +0200 Subject: [PATCH 130/130] updated italian translations --- resources/i18n/OpenBoard_it.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/i18n/OpenBoard_it.ts b/resources/i18n/OpenBoard_it.ts index b19ee525..f7ec8b85 100644 --- a/resources/i18n/OpenBoard_it.ts +++ b/resources/i18n/OpenBoard_it.ts @@ -841,7 +841,7 @@ Capture Web Content - + Catturare il contenuto del web @@ -3017,7 +3017,7 @@ p, li { white-space: pre-wrap; } Select a content to capture - + Selezionare il contenuto da catturare