/* * Copyright (C) 2013 Open Education Foundation * * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour * l'Education Numérique en Afrique (GIP ENA) * * This file is part of OpenBoard. * * OpenBoard is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 of the License, * with a specific linking exception for the OpenSSL project's * "OpenSSL" library (or with modified versions of it that use the * same license as the "OpenSSL" library). * * OpenBoard is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenBoard. If not, see . */ #include "UBDocumentTreeWidget.h" #include "document/UBDocumentProxy.h" #include "core/UBSettings.h" #include "core/UBApplication.h" #include "core/UBPersistenceManager.h" #include "core/UBMimeData.h" #include "core/UBApplicationController.h" #include "core/UBDocumentManager.h" #include "gui/UBMainWindow.h" #include "document/UBDocumentController.h" #include "adaptors/UBThumbnailAdaptor.h" #include "adaptors/UBSvgSubsetAdaptor.h" #include "frameworks/UBFileSystemUtils.h" #include "core/memcheck.h" #include UBDocumentTreeWidget::UBDocumentTreeWidget(QWidget * parent) : QTreeWidget(parent) , mSelectedProxyTi(0) , mDropTargetProxyTi(0) , mLastItemCompletePath("") { setDragDropMode(QAbstractItemView::InternalMove); setAutoScroll(true); mScrollTimer = new QTimer(this); connect(UBDocumentManager::documentManager(), SIGNAL(documentUpdated(UBDocumentProxy*)), this, SLOT(documentUpdated(UBDocumentProxy*))); connect(this, SIGNAL(itemChanged(QTreeWidgetItem *, int)) , this, SLOT(itemChangedValidation(QTreeWidgetItem *, int))); connect(mScrollTimer, SIGNAL(timeout()) , this, SLOT(autoScroll())); connect(this,SIGNAL(itemPressed(QTreeWidgetItem*,int)),this,SLOT(onItemPressed(QTreeWidgetItem*,int))); } UBDocumentTreeWidget::~UBDocumentTreeWidget() { // NOOP } void UBDocumentTreeWidget::onItemPressed(QTreeWidgetItem* item, int column) { Q_UNUSED(column) UBDocumentGroupTreeItem* group = dynamic_cast(item); if(group){ mLastItemCompletePath = group->buildEntirePath(); mLastItemName = group->groupName(); } } void UBDocumentTreeWidget::itemChangedValidation(QTreeWidgetItem * item, int column) { // QString emptyNameWarningTitle = tr("Empty name"); // QString emptyNameWarningText = tr("The name should not be empty. Please enter a valid name."); QString alreadyExistsNameWarningTitle = tr("Name already used"); QString alreadyExistsNameWarningText = tr("The actual name is in conflict with and existing. Please choose another one."); UBDocumentProxyTreeItem* treeItem = dynamic_cast< UBDocumentProxyTreeItem *>(item); if (treeItem) { QString name = treeItem->text(column); // if(name.isEmpty()) // UBApplication::mainWindow->warning(emptyNameWarningTitle,emptyNameWarningText); for(int i = 0; i < treeItem->parent()->childCount(); i++) { QTreeWidgetItem* childAtPosition = treeItem->parent()->child(i); if (childAtPosition != item && childAtPosition->text(column) == name){ UBApplication::mainWindow->warning(alreadyExistsNameWarningTitle,alreadyExistsNameWarningText); // This is not really a good way but at this time we are not yet out of the editing time // this is not what is told by the name of the function itemChanged... mFailedValidationForTreeItem = item; mFailedValidationItemColumn = column; QTimer::singleShot(100,this,SLOT(validationFailed())); return; } } } UBDocumentGroupTreeItem* group = dynamic_cast(item); if(group) { QString name = group->text(column); // if(name.isEmpty()) // UBApplication::mainWindow->warning(emptyNameWarningTitle,emptyNameWarningText); if(group->parent()){ for(int i = 0; i < group->parent()->childCount(); i++) { QTreeWidgetItem* childAtPosition = group->parent()->child(i); if (childAtPosition != item && childAtPosition->text(column) == name){ UBApplication::mainWindow->warning(alreadyExistsNameWarningTitle,alreadyExistsNameWarningText); mFailedValidationForTreeItem = item; mFailedValidationItemColumn = column; QTimer::singleShot(100,this,SLOT(validationFailed())); return; } } } else{ // We are looking at the top level items; for(int i = 0; i < topLevelItemCount(); i += 1){ if(topLevelItem(i) != item && dynamic_cast(topLevelItem(i))->groupName() == group->groupName()){ UBApplication::mainWindow->warning(tr("Name already in use"),tr("Please choose another name for the directory. The chosed name is already used.")); mFailedValidationForTreeItem = item; mFailedValidationItemColumn = column; QTimer::singleShot(100,this,SLOT(validationFailed())); return; } } } QString newPath = group->buildEntirePath(); group->updateChildrenPath(column, mLastItemCompletePath, newPath); UBApplication::documentController->treeGroupItemRenamed(mLastItemCompletePath, newPath); } } void UBDocumentTreeWidget::validationFailed() { editItem(mFailedValidationForTreeItem,mFailedValidationItemColumn); } Qt::DropActions UBDocumentTreeWidget::supportedDropActions() const { return Qt::MoveAction | Qt::CopyAction; } void UBDocumentTreeWidget::mousePressEvent(QMouseEvent *event) { QTreeWidgetItem* twItem = this->itemAt(event->pos()); mSelectedProxyTi = dynamic_cast(twItem); QTreeWidget::mousePressEvent(event); } void UBDocumentTreeWidget::dragEnterEvent(QDragEnterEvent *event) { event->acceptProposedAction(); } void UBDocumentTreeWidget::dragLeaveEvent(QDragLeaveEvent *event) { Q_UNUSED(event); if (mScrollTimer->isActive()) { mScrollMagnitude = 0; mScrollTimer->stop(); } if (mDropTargetProxyTi) { mDropTargetProxyTi->setBackground(0, mBackground); mDropTargetProxyTi = 0; } } void UBDocumentTreeWidget::dragMoveEvent(QDragMoveEvent *event) { QRect boundingFrame = frameRect(); //setting up automatic scrolling const int SCROLL_DISTANCE = 4; int bottomDist = boundingFrame.bottom() - event->pos().y(), topDist = boundingFrame.top() - event->pos().y(); if(qAbs(bottomDist) <= SCROLL_DISTANCE) { mScrollMagnitude = (SCROLL_DISTANCE - bottomDist)*4; if(verticalScrollBar()->isVisible() && !mScrollTimer->isActive()) mScrollTimer->start(100); } else if(qAbs(topDist) <= SCROLL_DISTANCE) { mScrollMagnitude = (- SCROLL_DISTANCE - topDist)*4; if(verticalScrollBar()->isVisible() && !mScrollTimer->isActive()) mScrollTimer->start(100); } else { mScrollMagnitude = 0; mScrollTimer->stop(); } QTreeWidgetItem* underlyingItem = this->itemAt(event->pos()); if (event->mimeData()->hasFormat(UBApplication::mimeTypeUniboardPage)) { UBDocumentProxyTreeItem *targetProxyTreeItem = dynamic_cast(underlyingItem); if (targetProxyTreeItem && targetProxyTreeItem != mSelectedProxyTi) { event->setDropAction(Qt::CopyAction); event->accept(); } else { event->ignore(); } } else { UBDocumentGroupTreeItem *groupItem = dynamic_cast(underlyingItem); if (groupItem && mSelectedProxyTi && groupItem != mSelectedProxyTi->parent()) event->acceptProposedAction(); else event->ignore(); } if (event->isAccepted()) { if (mDropTargetProxyTi) { if (underlyingItem != mDropTargetProxyTi) { mBackground = underlyingItem->background(0); mDropTargetProxyTi->setBackground(0, mBackground); mDropTargetProxyTi = underlyingItem; mDropTargetProxyTi->setBackground(0, QBrush(QColor("#6682b5"))); } } else { mBackground = underlyingItem->background(0); mDropTargetProxyTi = underlyingItem; mDropTargetProxyTi->setBackground(0, QBrush(QColor("#6682b5"))); } } else if (mDropTargetProxyTi) { mDropTargetProxyTi->setBackground(0, mBackground); mDropTargetProxyTi = 0; } } void UBDocumentTreeWidget::focusInEvent(QFocusEvent *event) { QTreeWidget::focusInEvent(event); } void UBDocumentTreeWidget::dropEvent(QDropEvent *event) { if (mDropTargetProxyTi) { mDropTargetProxyTi->setBackground(0, mBackground); mDropTargetProxyTi = 0; } QTreeWidgetItem* underlyingItem = this->itemAt(event->pos()); UBDocumentGroupTreeItem *groupItem = dynamic_cast(underlyingItem); if (groupItem && mSelectedProxyTi && mSelectedProxyTi->proxy()) { UBDocumentGroupTreeItem *sourceGroupItem = dynamic_cast(mSelectedProxyTi->parent()); bool isTrashItem = sourceGroupItem && sourceGroupItem->isTrashFolder(); if ((isTrashItem && !groupItem->isTrashFolder()) || (!isTrashItem && mSelectedProxyTi->proxy()->groupName() != groupItem->groupName())) { QString groupName; if (groupItem->isTrashFolder()) { QString oldGroupName = mSelectedProxyTi->proxy()->metaData(UBSettings::documentGroupName).toString(); groupName = UBSettings::trashedDocumentGroupNamePrefix + oldGroupName; } else { if (groupItem->groupName() == UBApplication::app()->documentController->defaultDocumentGroupName()) groupName = ""; else groupName = groupItem->groupName(); } mSelectedProxyTi->proxy()->setMetaData(UBSettings::documentGroupName, groupName); UBPersistenceManager::persistenceManager()->persistDocumentMetadata(mSelectedProxyTi->proxy()); mSelectedProxyTi->parent()->removeChild(mSelectedProxyTi); int i = 0; for (i = 0; i < groupItem->childCount(); i++) { QTreeWidgetItem *ti = groupItem->child(i); UBDocumentProxyTreeItem* pi = dynamic_cast(ti); if (pi) { if (mSelectedProxyTi->proxy()->metaData(UBSettings::documentDate).toString() >= pi->proxy()->metaData(UBSettings::documentDate).toString()) { break; } } } groupItem->insertChild(i, mSelectedProxyTi); if (isTrashItem) mSelectedProxyTi->setFlags(mSelectedProxyTi->flags() | Qt::ItemIsEditable); if (groupItem->isTrashFolder()) mSelectedProxyTi->setFlags(mSelectedProxyTi->flags() ^ Qt::ItemIsEditable); expandItem(groupItem); scrollToItem(mSelectedProxyTi); // disabled, as those 2 calls are buggy on windows, the item disappears if we selected them // setCurrentItem(mSelectedProxyTi); mSelectedProxyTi->setSelected(true); event->setDropAction(Qt::IgnoreAction); event->accept(); } } else { QTreeWidgetItem* underlyingTreeItem = this->itemAt(event->pos()); UBDocumentProxyTreeItem *targetProxyTreeItem = dynamic_cast(underlyingTreeItem); if (targetProxyTreeItem && targetProxyTreeItem != mSelectedProxyTi) { if (event->mimeData()->hasFormat(UBApplication::mimeTypeUniboardPage)) { event->setDropAction(Qt::CopyAction); event->accept(); const UBMimeData *mimeData = qobject_cast (event->mimeData()); if (mimeData && mimeData->items().size() > 0) { int count = 0; int total = mimeData->items().size(); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); foreach (UBMimeDataItem sourceItem, mimeData->items()) { count++; UBApplication::applicationController->showMessage(tr("Copying page %1/%2").arg(count).arg(total), true); // TODO UB 4.x Move following code to some controller class UBGraphicsScene *scene = UBPersistenceManager::persistenceManager()->loadDocumentScene(sourceItem.documentProxy(), sourceItem.sceneIndex()); if (scene) { UBGraphicsScene* sceneClone = scene->sceneDeepCopy(); UBDocumentProxy *targetDocProxy = targetProxyTreeItem->proxy(); foreach (QUrl relativeFile, scene->relativeDependencies()) { QString source = scene->document()->persistencePath() + "/" + relativeFile.toString(); QString target = targetDocProxy->persistencePath() + "/" + relativeFile.toString(); if(QFileInfo(source).isDir()) Q_ASSERT(UBFileSystemUtils::copyDir(source,target)); else{ QFileInfo fi(target); QDir d = fi.dir(); d.mkpath(d.absolutePath()); Q_ASSERT(QFile::copy(source, target)); } } UBPersistenceManager::persistenceManager()->insertDocumentSceneAt(targetDocProxy, sceneClone, targetDocProxy->pageCount()); //due to incorrect generation of thumbnails of invisible scene I've used direct copying of thumbnail files //it's not universal and good way but it's faster QString from = sourceItem.documentProxy()->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.thumbnail.jpg", sourceItem.sceneIndex()); QString to = targetDocProxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.thumbnail.jpg", targetDocProxy->pageCount()); QFile::remove(to); QFile::copy(from, to); } } QApplication::restoreOverrideCursor(); UBApplication::applicationController->showMessage(tr("%1 pages copied", "", total).arg(total), false); } } else { event->setDropAction(Qt::IgnoreAction); event->ignore(); } } } } void UBDocumentTreeWidget::documentUpdated(UBDocumentProxy *pDocument) { UBDocumentProxyTreeItem *treeItem = UBApplication::documentController->findDocument(pDocument); if (treeItem) { QTreeWidgetItem * parent = treeItem->parent(); if (parent) { for (int i = 0; i < parent->indexOfChild(treeItem); i++) { QTreeWidgetItem *ti = parent->child(i); UBDocumentProxyTreeItem* pi = dynamic_cast(ti); if (pi) { if (pDocument->metaData(UBSettings::documentDate).toString() >= pi->proxy()->metaData(UBSettings::documentDate).toString()) { bool selected = treeItem->isSelected(); parent->removeChild(treeItem); parent->insertChild(i, treeItem); for (int j = 0; j < selectedItems().count(); j++) selectedItems().at(j)->setSelected(false); if (selected) treeItem->setSelected(true); break; } } } } } } UBDocumentProxyTreeItem::UBDocumentProxyTreeItem(QTreeWidgetItem * parent, UBDocumentProxy* proxy, bool isEditable) : QTreeWidgetItem() , mProxy(proxy) { Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled; if (isEditable) flags |= Qt::ItemIsEditable; setFlags(flags); int i = 0; for (i = 0; i < parent->childCount(); i++) { QTreeWidgetItem *ti = parent->child(i); UBDocumentProxyTreeItem* pi = dynamic_cast(ti); if (pi) { if (proxy->metaData(UBSettings::documentDate).toString() >= pi->proxy()->metaData(UBSettings::documentDate).toString()) { break; } } } parent->insertChild(i, this); } UBDocumentGroupTreeItem::UBDocumentGroupTreeItem(QTreeWidgetItem *parent, bool isEditable) : QTreeWidgetItem(parent) { Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDropEnabled; if (isEditable) flags |= Qt::ItemIsEditable; setFlags(flags); } UBDocumentGroupTreeItem::~UBDocumentGroupTreeItem() { // NOOP } void UBDocumentGroupTreeItem::setGroupName(const QString& groupName) { setText(0, groupName); } QString UBDocumentGroupTreeItem::groupName() const { return text(0); } QString UBDocumentGroupTreeItem::buildEntirePath() { QString result(groupName()); UBDocumentGroupTreeItem* item = this; while(item->parent()){ item = dynamic_cast(item->parent()); result = item->groupName() + "/" + result; } return result; } void UBDocumentGroupTreeItem::updateChildrenPath(int column, QString& previousText, const QString& text) { for(int i = 0; i < childCount(); i += 1){ UBDocumentGroupTreeItem* groupTreeItem = dynamic_cast(child(i)); if(groupTreeItem) groupTreeItem->updateChildrenPath(column, previousText,text); else{ UBDocumentProxyTreeItem* docProxyItem = dynamic_cast(child(i)); QString groupName = docProxyItem->proxy()->metaData(UBSettings::documentGroupName).toString(); groupName = groupName.remove(0,previousText.length()); groupName = text + groupName; docProxyItem->proxy()->setMetaData(UBSettings::documentGroupName, groupName); UBPersistenceManager::persistenceManager()->persistDocumentMetadata(docProxyItem->proxy()); } } } bool UBDocumentGroupTreeItem::isTrashFolder() const { return (0 == (flags() & Qt::ItemIsEditable)) && UBApplication::app()->documentController && (groupName() == UBApplication::app()->documentController->documentTrashGroupName()); } bool UBDocumentGroupTreeItem::isDefaultFolder() const { return (0 == (flags() & Qt::ItemIsEditable)) && UBApplication::app()->documentController && (groupName() == UBApplication::app()->documentController->defaultDocumentGroupName()); } void UBDocumentTreeWidget::autoScroll() { this->verticalScrollBar()->setValue(this->verticalScrollBar()->value() + mScrollMagnitude); }