/*
* 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);
}