#include "versioncontroltimeline.h"
// Tnz6 includes
#include "tapp.h"
// TnzQt includes
#include "toonzqt/icongenerator.h"
#include "toonzqt/gutil.h"
// TnzLib includes
#include "toonz/txshsimplelevel.h"
#include "toonz/toonzscene.h"
// TnzCore includes
#include "tfiletype.h"
#include "tfilepath.h"
#include "tsystem.h"
// Qt includes
#include <QPushButton>
#include <QCheckBox>
#include <QListWidget>
#include <QFileInfo>
#include <QTemporaryFile>
#include <QLabel>
#include <QMovie>
#include <QPainter>
#include <QScrollBar>
#include <QDate>
#include <QTimer>
#include <QDir>
#include <QMainWindow>
using namespace DVGui;
#define ICON_WIDTH 60
#define ICON_HEIGHT 60
//=============================================================================
// TimelineWidget
//-----------------------------------------------------------------------------
TimelineWidget::TimelineWidget(QWidget *parent) : QWidget(parent) {
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->setMargin(0);
mainLayout->setSpacing(0);
QLabel *label = new QLabel(tr("Recent Version"));
label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
label->setAlignment(Qt::AlignHCenter);
label->setStyleSheet("border: 1px solid rgb(150,150,150);");
mainLayout->addWidget(label);
m_listWidget = new QListWidget;
// m_listWidget->setItemDelegate(new ItemDelegate(m_listWidget));
m_listWidget->setFlow(QListView::TopToBottom);
m_listWidget->setViewMode(QListView::ListMode);
m_listWidget->setMovement(QListView::Static);
m_listWidget->setWrapping(false);
m_listWidget->setAlternatingRowColors(true);
m_listWidget->setIconSize(QSize(ICON_WIDTH, ICON_HEIGHT));
m_listWidget->setMinimumWidth(400);
m_listWidget->setMinimumHeight(300);
mainLayout->addWidget(m_listWidget);
label = new QLabel(tr("Older Version"));
label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
label->setAlignment(Qt::AlignHCenter);
label->setStyleSheet("border: 1px solid rgb(150,150,150); ");
mainLayout->addWidget(label);
setLayout(mainLayout);
}
//-----------------------------------------------------------------------------
int TimelineWidget::getCurrentIndex() const {
return m_listWidget->currentIndex().row();
}
//=============================================================================
// SVNTimeline
//-----------------------------------------------------------------------------
SVNTimeline::SVNTimeline(QWidget *parent, const QString &workingDir,
const QString &fileName, const QStringList &auxFiles)
: Dialog(TApp::instance()->getMainWindow(), true, false)
, m_workingDir(workingDir)
, m_auxFiles(auxFiles)
, m_fileName(fileName)
, m_currentExportIndex(0)
, m_currentAuxExportIndex(0) {
setModal(false);
setAttribute(Qt::WA_DeleteOnClose, true);
setWindowTitle(tr("Version Control: Timeline ") + fileName);
QHBoxLayout *hLayout = new QHBoxLayout;
m_waitingLabel = new QLabel;
QMovie *waitingMove = new QMovie(":Resources/waiting.gif");
waitingMove->setParent(this);
m_waitingLabel->setMovie(waitingMove);
waitingMove->setCacheMode(QMovie::CacheAll);
waitingMove->start();
m_textLabel = new QLabel(tr("Getting file history..."));
hLayout->addStretch();
hLayout->addWidget(m_waitingLabel);
hLayout->addWidget(m_textLabel);
hLayout->addStretch();
m_timelineWidget = new TimelineWidget;
m_timelineWidget->setStyleSheet("QListWidget:item { margin: 5px; }");
m_timelineWidget->hide();
connect(m_timelineWidget->getListWidget(), SIGNAL(itemSelectionChanged()),
this, SLOT(onSelectionChanged()));
QHBoxLayout *checkBoxLayout = new QHBoxLayout;
checkBoxLayout->setMargin(0);
m_sceneContentsCheckBox = new QCheckBox(this);
m_sceneContentsCheckBox->setVisible(m_fileName.endsWith(".tnz"));
connect(m_sceneContentsCheckBox, SIGNAL(toggled(bool)), this,
SLOT(onSceneContentsToggled(bool)));
m_sceneContentsCheckBox->setChecked(false);
m_sceneContentsCheckBox->setText(tr("Get Scene Contents"));
checkBoxLayout->addStretch();
checkBoxLayout->addWidget(m_sceneContentsCheckBox);
checkBoxLayout->addStretch();
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->setMargin(0);
mainLayout->addLayout(hLayout);
mainLayout->addWidget(m_timelineWidget);
mainLayout->addSpacing(5);
mainLayout->addLayout(checkBoxLayout);
QWidget *container = new QWidget;
container->setLayout(mainLayout);
beginHLayout();
addWidget(container, false);
endHLayout();
m_updateButton = new QPushButton(tr("Get Last Revision"));
connect(m_updateButton, SIGNAL(clicked()), this,
SLOT(onUpdateButtonClicked()));
m_updateToRevisionButton = new QPushButton(tr("Get Selected Revision"));
connect(m_updateToRevisionButton, SIGNAL(clicked()), this,
SLOT(onUpdateToRevisionButtonClicked()));
m_updateToRevisionButton->setEnabled(false);
m_closeButton = new QPushButton(tr("Close"));
connect(m_closeButton, SIGNAL(clicked()), this, SLOT(close()));
addButtonBarWidget(m_updateButton, m_updateToRevisionButton, m_closeButton);
// 0. Connect for svn errors (that may occur every time)
connect(&m_thread, SIGNAL(error(const QString &)), this,
SLOT(onError(const QString &)));
// 1. get the log (history) of fileName
QStringList args;
args << "log" << m_fileName << "--xml";
connect(&m_thread, SIGNAL(done(const QString &)),
SLOT(onLogDone(const QString &)));
m_thread.executeCommand(m_workingDir, "svn", args, true);
}
//-----------------------------------------------------------------------------
SVNTimeline::~SVNTimeline() {
removeTempFiles();
// Delete, if exist the sceneIcons folder
QDir d(QDir::tempPath());
d.rmdir("sceneIcons");
}
//-----------------------------------------------------------------------------
void SVNTimeline::removeTempFiles() {
int tempFileCount = m_tempFiles.count();
for (int i = 0; i < tempFileCount; i++) {
QTemporaryFile *file = m_tempFiles.at(i);
file->close();
QFileInfo fi(*file);
QFile::remove(fi.absoluteFilePath());
delete file;
}
m_tempFiles.clear();
tempFileCount = m_auxTempFiles.count();
for (int i = 0; i < tempFileCount; i++) {
QTemporaryFile *file = m_auxTempFiles.at(i);
file->close();
QFileInfo fi(*file);
QFile::remove(fi.absoluteFilePath());
delete file;
}
m_auxTempFiles.clear();
}
//-----------------------------------------------------------------------------
void SVNTimeline::onLogDone(const QString &xmlResponse) {
m_textLabel->hide();
m_waitingLabel->hide();
// qDebug(xmlResponse.toAscii());
SVNLogReader lr(xmlResponse);
m_log = lr.getLog();
int logCount = m_log.size();
if (logCount == 0) return;
QString fileNameType =
QString::fromStdString(TFilePath(m_fileName.toStdWString()).getType());
for (int i = 0; i < logCount; i++) {
SVNLog log = m_log.at(i);
QDate d = QDate::fromString(log.m_date.left(10), "yyyy-MM-dd");
int dayCount = d.daysTo(QDate::currentDate());
QString text = "\n" + tr("Date") + ": " + d.toString("MM-dd-yyyy");
if (dayCount == 0) {
// text += "Today\nby " + log.m_author;
QString timeString = log.m_date.split("T").at(1);
timeString = timeString.left(5);
// Convert the current time, to UTC
QDateTime currentTime = QDateTime::currentDateTime().toUTC();
QTime t = QTime::fromString(timeString, "hh:mm");
QTime now = QTime::fromString(currentTime.toString("hh:mm"), "hh:mm");
int seconds = t.secsTo(now);
int minute = seconds / 60;
text += " ( " + QString::number(minute) + " minutes ago )\n" +
tr("Author") + ": " + log.m_author + "\n" + tr("Comment") + ": " +
log.m_msg;
} else
text += " ( " + QString::number(dayCount) + " days ago )\n" +
tr("Author") + ": " + log.m_author + "\n" + tr("Comment") + ": " +
log.m_msg;
QString tooltip = "<b>" + tr("Revision") + "</b>: " + log.m_revision +
"<br><b>" + tr("Author") + "</b>: " + log.m_author +
"<br><b>" + tr("Date") + "</b>: " + log.m_date +
"<br><b>" + tr("Comment") + "</b>: " + log.m_msg;
QPixmap pixmap(ICON_WIDTH, ICON_HEIGHT);
pixmap.fill(Qt::lightGray);
QListWidgetItem *lwi = new QListWidgetItem(
QIcon(pixmap), text, m_timelineWidget->getListWidget());
lwi->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
lwi->setToolTip(tooltip);
lwi->setTextAlignment(Qt::AlignLeft);
QString tmpFileName = QDir::tempPath() + "/svn_temp_img_" +
QString::number(i) + "." + fileNameType;
m_tempFiles.append(new QTemporaryFile());
m_tempFiles.last()->setFileName(tmpFileName);
// Add also auxiliary files
TFilePath fp(TFilePath(m_workingDir.toStdWString()) +
m_fileName.toStdWString());
if (fp.getDots() == ".." || fp.getType() == "tlv" ||
fp.getType() == "pli") {
TFilePathSet fpset;
TXshSimpleLevel::getFiles(fp, fpset);
TFilePathSet::iterator it;
for (it = fpset.begin(); it != fpset.end(); ++it) {
TFilePath fp = *it;
if (fp.getType() == "tpl") {
QString tmpFileName = QDir::tempPath() + "/svn_temp_img_" +
QString::number(i) + "." +
QString::fromStdString(fp.getType());
m_auxTempFiles.append(new QTemporaryFile());
m_auxTempFiles.last()->setFileName(tmpFileName);
}
}
}
// Add sceneIcon (only for scene files)
else if (fp.getType() == "tnz") {
TFilePath iconPath = ToonzScene::getIconPath(fp);
if (TFileStatus(iconPath).doesExist()) {
QDir dir(toQString(fp.getParentDir()));
// Create the sceneIcons folder
QDir d(QDir::tempPath());
d.mkdir("sceneIcons");
QString tmpFileName = QDir::tempPath() + QString("/sceneicons/") +
"svn_temp_img_" + QString::number(i) + " .png";
m_auxTempFiles.append(new QTemporaryFile());
m_auxTempFiles.at(m_auxTempFiles.size() - 1)->setFileName(tmpFileName);
}
}
m_listWidgetitems.append(lwi);
}
setMinimumSize(430, 450);
m_timelineWidget->getListWidget()->update();
m_timelineWidget->show();
// Export to temporary files
m_thread.disconnect(SIGNAL(done(const QString &)));
connect(&m_thread, SIGNAL(done(const QString &)), SLOT(onExportDone()));
exportToTemporaryFile(m_currentExportIndex);
}
//-----------------------------------------------------------------------------
void SVNTimeline::onExportDone() {
int itemCount = m_listWidgetitems.count();
if (m_currentExportIndex < 0 || m_currentExportIndex >= itemCount) {
m_currentExportIndex++;
return;
}
QListWidgetItem *lwi = m_listWidgetitems.at(m_currentExportIndex);
if (lwi) {
QFileInfo fi(*m_tempFiles.at(m_currentExportIndex));
// qDebug(fi.absoluteFilePath().toAscii());
// Create the icon if there isn't any auxiliary files associated (palette,
// sceneIcons..)
if (m_auxTempFiles.isEmpty())
lwi->setIcon(createIcon(fi.absoluteFilePath()));
lwi->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
}
m_currentExportIndex++;
if (m_currentExportIndex < itemCount) {
exportToTemporaryFile(m_currentExportIndex);
} else {
if (!m_auxTempFiles.isEmpty()) {
m_thread.disconnect(SIGNAL(done(const QString &)));
connect(&m_thread, SIGNAL(done(const QString &)),
SLOT(onExportAuxDone()));
exportToTemporaryFile(m_currentAuxExportIndex, true);
}
}
}
//-----------------------------------------------------------------------------
void SVNTimeline::onExportAuxDone() {
int itemCount = m_listWidgetitems.count();
if (m_currentAuxExportIndex < 0 || m_currentAuxExportIndex >= itemCount) {
m_currentAuxExportIndex++;
return;
}
QListWidgetItem *lwi = m_listWidgetitems.at(m_currentAuxExportIndex);
if (lwi) {
QFileInfo fi(*m_tempFiles.at(m_currentAuxExportIndex));
// qDebug(fi.absoluteFilePath().toAscii());
lwi->setIcon(createIcon(fi.absoluteFilePath()));
}
m_currentAuxExportIndex++;
if (m_currentAuxExportIndex < itemCount) {
exportToTemporaryFile(m_currentAuxExportIndex, true);
}
}
//-----------------------------------------------------------------------------
void SVNTimeline::exportToTemporaryFile(int index, bool isAuxFile) {
QFileInfo fi;
QString fileName = m_fileName;
fileName.chop(4);
if (isAuxFile)
fi = QFileInfo(*m_auxTempFiles.at(index));
else
fi = QFileInfo(*m_tempFiles.at(index));
QString extension = fi.completeSuffix();
QStringList args;
args << "export";
// SceneIcons (pay attention to add a space at the fileName end)
if (isAuxFile && fi.completeSuffix() == "png") {
args << "sceneIcons/" + fileName + " .png@" + m_log.at(index).m_revision;
args << fi.absoluteFilePath();
} else {
args << m_fileName + "@" + m_log.at(index).m_revision;
args << fi.absoluteFilePath();
}
args << "--force";
// Export to temporary file...
m_thread.executeCommand(m_workingDir, "svn", args, true);
}
//-----------------------------------------------------------------------------
void SVNTimeline::onError(const QString &text) {
m_waitingLabel->hide();
m_sceneContentsCheckBox->hide();
m_textLabel->setText(text);
m_textLabel->show();
m_timelineWidget->hide();
m_closeButton->setEnabled(true);
}
//-----------------------------------------------------------------------------
void SVNTimeline::onSelectionChanged() {
int selectedItemCount =
m_timelineWidget->getListWidget()->selectedItems().count();
m_updateToRevisionButton->setEnabled(selectedItemCount > 0);
}
//-----------------------------------------------------------------------------
QIcon SVNTimeline::createIcon(const QString &fileName) {
TDimension iconSize(60, 60);
TFilePath path(fileName.toStdWString());
TRaster32P iconRaster;
std::string type(path.getType());
QPixmap filePixmap(fileName);
if (filePixmap.isNull()) {
if (type == "tnz" || type == "tab")
filePixmap = rasterToQPixmap(
IconGenerator::generateSceneFileIcon(path, iconSize, 0));
else if (type == "pli")
filePixmap = rasterToQPixmap(
IconGenerator::generateVectorFileIcon(path, iconSize, 1));
else if (type == "tpl")
filePixmap = QPixmap(":Resources/paletteicon.svg");
else if (type == "tzp")
filePixmap = QPixmap(":Resources/tzpicon.png");
else if (type == "tzu")
filePixmap = QPixmap(":Resources/tzuicon.png");
else if (TFileType::getInfo(path) == TFileType::AUDIO_LEVEL)
filePixmap = QPixmap(svgToPixmap(":Resources/audio.svg",
QSize(iconSize.lx, iconSize.ly),
Qt::KeepAspectRatio));
else if (type == "scr")
filePixmap = QPixmap(":Resources/savescreen.png");
else if (type == "psd")
filePixmap = QPixmap(svgToPixmap(":Resources/psd.svg",
QSize(iconSize.lx, iconSize.ly),
Qt::KeepAspectRatio));
else if (TFileType::isViewable(TFileType::getInfo(path)) || type == "tlv")
filePixmap = rasterToQPixmap(
IconGenerator::generateRasterFileIcon(path, iconSize, 1));
else if (type == "mpath")
filePixmap = QPixmap(svgToPixmap(":Resources/motionpath_fileicon.svg",
QSize(iconSize.lx, iconSize.ly),
Qt::KeepAspectRatio));
else if (type == "curve")
filePixmap = QPixmap(svgToPixmap(":Resources/curve.svg",
QSize(iconSize.lx, iconSize.ly),
Qt::KeepAspectRatio));
else if (type == "cln")
filePixmap = QPixmap(svgToPixmap(":Resources/cleanup.svg",
QSize(iconSize.lx, iconSize.ly),
Qt::KeepAspectRatio));
else if (type == "tnzbat")
filePixmap = QPixmap(svgToPixmap(":Resources/tasklist.svg",
QSize(iconSize.lx, iconSize.ly),
Qt::KeepAspectRatio));
else if (type == "js")
filePixmap = QPixmap(":Resources/scripticon.png");
else
filePixmap = QPixmap(svgToPixmap(":Resources/unknown.svg",
QSize(iconSize.lx, iconSize.ly),
Qt::KeepAspectRatio));
}
if (filePixmap.isNull()) return QIcon();
if (filePixmap.size() != QSize(ICON_WIDTH, ICON_HEIGHT))
filePixmap = filePixmap.scaled(ICON_WIDTH, ICON_HEIGHT, Qt::KeepAspectRatio,
Qt::SmoothTransformation);
if (filePixmap.size() == QSize(ICON_WIDTH, ICON_HEIGHT))
return QIcon(filePixmap);
QPixmap iconPixmap(ICON_WIDTH, ICON_HEIGHT);
iconPixmap.fill(Qt::white);
int y = (int)((iconPixmap.height() - filePixmap.height()) * 0.5);
QPainter p(&iconPixmap);
p.drawPixmap(0, y, filePixmap);
p.end();
QIcon icon;
icon.addPixmap(iconPixmap, QIcon::Normal, QIcon::On);
icon.addPixmap(iconPixmap, QIcon::Selected, QIcon::Off);
return icon;
}
//-----------------------------------------------------------------------------
void SVNTimeline::onUpdateButtonClicked() {
m_updateButton->hide();
m_sceneContentsCheckBox->hide();
m_updateToRevisionButton->hide();
m_timelineWidget->hide();
m_waitingLabel->show();
if (m_sceneResources.isEmpty())
m_textLabel->setText(tr("Getting the status for %1...").arg(m_fileName));
else
m_textLabel->setText(tr("Getting repository status..."));
m_textLabel->show();
QStringList files;
files.append(m_fileName);
files.append(m_sceneResources);
files.append(m_auxFiles);
// Getting status to control if an update is needed
connect(&m_thread, SIGNAL(statusRetrieved(const QString &)), this,
SLOT(onStatusRetrieved(const QString &)));
m_thread.getSVNStatus(m_workingDir, files, true);
}
//-----------------------------------------------------------------------------
void SVNTimeline::onUpdateToRevisionButtonClicked() {
int index = m_timelineWidget->getCurrentIndex();
if (index == -1) return;
SVNLog log = m_log.at(index);
m_updateButton->hide();
m_updateToRevisionButton->hide();
m_timelineWidget->hide();
m_sceneContentsCheckBox->hide();
m_waitingLabel->show();
if (m_sceneResources.isEmpty())
m_textLabel->setText(
tr("Getting %1 to revision %2...").arg(m_fileName).arg(log.m_revision));
else
m_textLabel->setText(tr("Getting %1 items to revision %2...")
.arg(m_sceneResources.size() + 1)
.arg(log.m_revision));
m_textLabel->show();
QStringList args;
args << "update" << m_fileName;
int auxFilesSize = m_auxFiles.count();
for (int i = 0; i < auxFilesSize; i++) args << m_auxFiles.at(i);
int sceneResourcesSize = m_sceneResources.count();
for (int i = 0; i < sceneResourcesSize; i++) args << m_sceneResources.at(i);
args << QString("-r").append(log.m_revision);
m_thread.disconnect(SIGNAL(done(const QString &)));
connect(&m_thread, SIGNAL(done(const QString &)), SLOT(onUpdateDone()));
m_thread.executeCommand(m_workingDir, "svn", args, true);
}
//-----------------------------------------------------------------------------
void SVNTimeline::onStatusRetrieved(const QString &xmlResponse) {
SVNStatusReader sr(xmlResponse);
QList<SVNStatus> list = sr.getStatus();
if (!list.empty()) {
SVNStatus s = list.at(0);
if (s.m_item == "none" || s.m_item == "missing" ||
s.m_repoStatus == "modified") {
if (list.size() == 1)
m_textLabel->setText(tr("Getting %1...").arg(m_fileName));
else
m_textLabel->setText(
tr("Getting %1 items...").arg(m_sceneResources.size() + 1));
m_sceneContentsCheckBox->hide();
QStringList args;
args << "update" << m_fileName;
for (int i = 1; i < list.size(); i++) {
s = list.at(i);
if (s.m_item == "none" || s.m_item == "missing" ||
s.m_repoStatus == "modified")
args << s.m_path;
}
m_thread.disconnect(SIGNAL(done(const QString &)));
connect(&m_thread, SIGNAL(done(const QString &)), SLOT(onUpdateDone()));
m_thread.executeCommand(m_workingDir, "svn", args, true);
return;
}
}
// Do nothing (restore the GUI)
m_updateButton->show();
m_updateToRevisionButton->show();
m_timelineWidget->show();
if (m_fileName.endsWith(".tnz")) m_sceneContentsCheckBox->show();
m_waitingLabel->hide();
m_textLabel->hide();
}
//-----------------------------------------------------------------------------
void SVNTimeline::onUpdateDone() {
// Get the new log ...
m_log.clear();
m_timelineWidget->getListWidget()->clear();
m_listWidgetitems.clear();
removeTempFiles();
m_currentExportIndex = 0;
m_currentAuxExportIndex = 0;
// Update the filebrowser
QStringList files;
files.append(m_fileName);
files.append(m_sceneResources);
files.append(m_auxFiles);
emit commandDone(files);
m_updateButton->show();
m_updateToRevisionButton->show();
if (m_fileName.endsWith(".tnz")) m_sceneContentsCheckBox->show();
QStringList args;
args << "log" << m_fileName << "--xml";
m_thread.disconnect(SIGNAL(done(const QString &)));
connect(&m_thread, SIGNAL(done(const QString &)),
SLOT(onLogDone(const QString &)));
m_thread.executeCommand(m_workingDir, "svn", args, true);
}
//-----------------------------------------------------------------------------
void SVNTimeline::onSceneContentsToggled(bool checked) {
if (!checked)
m_sceneResources.clear();
else {
VersionControl *vc = VersionControl::instance();
m_sceneResources.append(vc->getSceneContents(m_workingDir, m_fileName));
}
}