Warning, file /sdk/cervisia/updateview_items.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002  * Copyright (C) 1999-2002 Bernd Gehrmann <bernd@mail.berlios.de>
0003  * Copyright (c) 2003-2008 André Wöbbeking <Woebbeking@kde.org>
0004  *
0005  * This program is free software; you can redistribute it and/or modify
0006  * it under the terms of the GNU General Public License as published by
0007  * the Free Software Foundation; either version 2 of the License, or
0008  * (at your option) any later version.
0009  *
0010  * This program is distributed in the hope that it will be useful,
0011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013  * GNU General Public License for more details.
0014  *
0015  * You should have received a copy of the GNU General Public License
0016  * along with this program; if not, write to the Free Software
0017  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
0018  */
0019 
0020 #include "updateview_items.h"
0021 
0022 #include <cassert>
0023 
0024 #include <qdir.h>
0025 
0026 #include <QTextStream>
0027 
0028 #include <kcolorscheme.h>
0029 
0030 #include "cvsdir.h"
0031 #include "debug.h"
0032 #include "misc.h"
0033 #include "updateview_visitors.h"
0034 
0035 using Cervisia::Entry;
0036 using Cervisia::EntryStatus;
0037 
0038 // ------------------------------------------------------------------------------
0039 // UpdateItem
0040 // ------------------------------------------------------------------------------
0041 
0042 QString UpdateItem::dirPath() const
0043 {
0044     QString path;
0045 
0046     const UpdateItem *item = static_cast<UpdateItem *>(parent());
0047     while (item) {
0048         const UpdateItem *parentItem = static_cast<UpdateItem *>(item->parent());
0049         if (parentItem) {
0050             path.prepend(item->m_entry.m_name + QDir::separator());
0051         }
0052 
0053         item = parentItem;
0054     }
0055 
0056     return path;
0057 }
0058 
0059 QString UpdateItem::filePath() const
0060 {
0061     // the filePath of the root item is '.'
0062     return parent() ? QString(dirPath() + m_entry.m_name) : QLatin1String(".");
0063 }
0064 
0065 // ------------------------------------------------------------------------------
0066 // UpdateDirItem
0067 // ------------------------------------------------------------------------------
0068 
0069 UpdateDirItem::UpdateDirItem(UpdateDirItem *parent, const Entry &entry)
0070     : UpdateItem(parent, entry, RTTI)
0071     , m_opened(false)
0072 {
0073     setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator);
0074     setIcon(0, QIcon::fromTheme("folder"));
0075 }
0076 
0077 UpdateDirItem::UpdateDirItem(UpdateView *parent, const Entry &entry)
0078     : UpdateItem(parent, entry, RTTI)
0079     , m_opened(false)
0080 {
0081     setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator);
0082     setIcon(0, QIcon::fromTheme("folder"));
0083 }
0084 
0085 /**
0086  * Update the status of an item; if it doesn't exist yet, create new one
0087  */
0088 void UpdateDirItem::updateChildItem(const QString &name, EntryStatus status, bool isdir)
0089 {
0090     if (UpdateItem *item = findItem(name)) {
0091         if (isFileItem(item)) {
0092             auto fileItem = static_cast<UpdateFileItem *>(item);
0093             fileItem->setStatus(status);
0094         }
0095         return;
0096     }
0097 
0098     // Not found, make new entry
0099     Entry entry;
0100     entry.m_name = name;
0101     if (isdir) {
0102         entry.m_type = Entry::Dir;
0103         createDirItem(entry)->maybeScanDir(true);
0104     } else {
0105         entry.m_type = Entry::File;
0106         createFileItem(entry)->setStatus(status);
0107     }
0108 }
0109 
0110 /**
0111  * Update the revision and tag of an item. Use status only to create
0112  * new items and for items which were NotInCVS.
0113  */
0114 void UpdateDirItem::updateEntriesItem(const Entry &entry, bool isBinary)
0115 {
0116     if (UpdateItem *item = findItem(entry.m_name)) {
0117         if (isFileItem(item)) {
0118             auto fileItem = static_cast<UpdateFileItem *>(item);
0119             if (fileItem->entry().m_status == Cervisia::NotInCVS || fileItem->entry().m_status == Cervisia::LocallyRemoved
0120                 || fileItem->entry().m_status == Cervisia::Unknown || entry.m_status == Cervisia::LocallyAdded || entry.m_status == Cervisia::LocallyRemoved
0121                 || entry.m_status == Cervisia::Conflict) {
0122                 fileItem->setStatus(entry.m_status);
0123             }
0124             fileItem->setRevTag(entry.m_revision, entry.m_tag);
0125             fileItem->setDate(entry.m_dateTime);
0126             fileItem->setIcon(0, isBinary ? QIcon::fromTheme("application-octet-stream") : QIcon());
0127         }
0128         return;
0129     }
0130 
0131     // Not found, make new entry
0132     if (entry.m_type == Entry::Dir)
0133         createDirItem(entry)->maybeScanDir(true);
0134     else
0135         createFileItem(entry);
0136 }
0137 
0138 void UpdateDirItem::scanDirectory()
0139 {
0140     const QString &path(filePath());
0141     if (!QFile::exists(path))
0142         return;
0143 
0144     const CvsDir dir(path);
0145 
0146     const QFileInfoList *files = dir.entryInfoList();
0147     if (files) {
0148         Q_FOREACH (QFileInfo info, *files) {
0149             Entry entry;
0150             entry.m_name = info.fileName();
0151             if (info.isDir()) {
0152                 entry.m_type = Entry::Dir;
0153                 createDirItem(entry);
0154             } else {
0155                 entry.m_type = Entry::File;
0156                 entry.m_status = Cervisia::NotInCVS;
0157                 createFileItem(entry);
0158             }
0159         }
0160     }
0161 }
0162 
0163 UpdateDirItem *UpdateDirItem::createDirItem(const Entry &entry)
0164 {
0165     UpdateItem *item(insertItem(new UpdateDirItem(this, entry)));
0166     assert(isDirItem(item));
0167     return static_cast<UpdateDirItem *>(item);
0168 }
0169 
0170 UpdateFileItem *UpdateDirItem::createFileItem(const Entry &entry)
0171 {
0172     UpdateItem *item(insertItem(new UpdateFileItem(this, entry)));
0173     assert(isFileItem(item));
0174     return static_cast<UpdateFileItem *>(item);
0175 }
0176 
0177 UpdateItem *UpdateDirItem::insertItem(UpdateItem *item)
0178 {
0179     const TMapItemsByName::iterator it = m_itemsByName.find(item->entry().m_name);
0180     if (it != m_itemsByName.end()) {
0181         // OK, an item with that name already exists. If the item type is the
0182         // same then keep the old one to preserve it's status information
0183         UpdateItem *existingItem = *it;
0184         if (existingItem->type() == item->type()) {
0185             delete item;
0186             item = existingItem;
0187         } else {
0188             // avoid dangling pointers in the view
0189             updateView()->replaceItem(existingItem, item);
0190 
0191             delete existingItem;
0192             *it = item;
0193         }
0194     } else {
0195         m_itemsByName.insert(item->entry().m_name, item);
0196     }
0197 
0198     return item;
0199 }
0200 
0201 UpdateItem *UpdateDirItem::findItem(const QString &name) const
0202 {
0203     const TMapItemsByName::const_iterator it = m_itemsByName.find(name);
0204 
0205     return (it != m_itemsByName.end()) ? *it : 0;
0206 }
0207 
0208 // Format of the CVS/Entries file:
0209 //   /NAME/REVISION/[CONFLICT+]TIMESTAMP/OPTIONS/TAGDATE
0210 
0211 void UpdateDirItem::syncWithEntries()
0212 {
0213     const QString path(filePath() + QDir::separator());
0214 
0215     QFile f(path + "CVS/Entries");
0216     if (f.open(QIODevice::ReadOnly)) {
0217         QTextStream stream(&f);
0218         while (!stream.atEnd()) {
0219             QString line = stream.readLine();
0220 
0221             Cervisia::Entry entry;
0222 
0223             const bool isDir(line[0] == 'D');
0224 
0225             if (isDir)
0226                 line.remove(0, 1);
0227 
0228             if (line[0] != '/')
0229                 continue;
0230 
0231             entry.m_type = isDir ? Entry::Dir : Entry::File;
0232 
0233             // since QString::section() always calls split internally, let's do it only once
0234             const QStringList sections = line.split(QLatin1Char('/'), QString::KeepEmptyParts, Qt::CaseSensitive);
0235 
0236             entry.m_name = sections[1];
0237 
0238             if (isDir) {
0239                 updateEntriesItem(entry, false);
0240             } else {
0241                 QString rev(sections[2]);
0242                 const QString timestamp(sections[3]);
0243                 const QString options(sections[4]);
0244                 entry.m_tag = sections[5];
0245 
0246                 const bool isBinary = options.contains("-kb");
0247 
0248                 // file date in local time
0249                 entry.m_dateTime = QFileInfo(path + entry.m_name).lastModified();
0250 
0251                 // set milliseconds to 0 since CVS/Entries does only contain seconds resolution
0252                 QTime t = entry.m_dateTime.time();
0253                 t.setHMS(t.hour(), t.minute(), t.second());
0254                 entry.m_dateTime.setTime(t);
0255 
0256                 if (rev == "0")
0257                     entry.m_status = Cervisia::LocallyAdded;
0258                 else if (rev.length() > 2 && rev[0] == '-') {
0259                     entry.m_status = Cervisia::LocallyRemoved;
0260                     rev.remove(0, 1);
0261                 } else if (timestamp.contains('+')) {
0262                     entry.m_status = Cervisia::Conflict;
0263                 } else {
0264                     QDateTime date(QDateTime::fromString(timestamp)); // UTC Time
0265                     date.setTimeSpec(Qt::UTC);
0266                     const QDateTime fileDateUTC(entry.m_dateTime.toUTC());
0267                     if (date != fileDateUTC)
0268                         entry.m_status = Cervisia::LocallyModified;
0269                 }
0270 
0271                 entry.m_revision = rev;
0272 
0273                 updateEntriesItem(entry, isBinary);
0274             }
0275         }
0276     }
0277 }
0278 
0279 /**
0280  * Test if files was removed from repository.
0281  */
0282 void UpdateDirItem::syncWithDirectory()
0283 {
0284     QDir dir(filePath());
0285 
0286     for (TMapItemsByName::iterator it(m_itemsByName.begin()), itEnd(m_itemsByName.end()); it != itEnd; ++it) {
0287         // only files
0288         if (isFileItem(*it)) {
0289             auto fileItem = static_cast<UpdateFileItem *>(*it);
0290 
0291             // is file removed?
0292             if (!dir.exists(it.key())) {
0293                 fileItem->setStatus(Cervisia::Removed);
0294                 fileItem->setRevTag(QString(), QString());
0295             }
0296         }
0297     }
0298 }
0299 
0300 /**
0301  * Read in the content of the directory. If recursive is false, this
0302  * is shallow, otherwise all child directories are scanned recursively.
0303  */
0304 void UpdateDirItem::maybeScanDir(bool recursive)
0305 {
0306     if (!m_opened) {
0307         m_opened = true;
0308         scanDirectory();
0309         syncWithEntries();
0310     }
0311 
0312     if (recursive) {
0313         for (TMapItemsByName::iterator it(m_itemsByName.begin()), itEnd(m_itemsByName.end()); it != itEnd; ++it) {
0314             if (isDirItem(*it))
0315                 static_cast<UpdateDirItem *>(*it)->maybeScanDir(true);
0316         }
0317     }
0318 }
0319 
0320 void UpdateDirItem::accept(Visitor &visitor)
0321 {
0322     visitor.preVisit(this);
0323 
0324     for (TMapItemsByName::iterator it(m_itemsByName.begin()), itEnd(m_itemsByName.end()); it != itEnd; ++it) {
0325         (*it)->accept(visitor);
0326     }
0327 
0328     visitor.postVisit(this);
0329 }
0330 
0331 void UpdateDirItem::setOpen(bool open)
0332 {
0333     if (open) {
0334         const bool openFirstTime(!wasScanned());
0335 
0336         maybeScanDir(false);
0337 
0338         // if new items were created their visibility must be checked
0339         // (not while unfoldTree() as this could be slow and unfoldTree()
0340         // calls setFilter() itself)
0341         UpdateView *view = updateView();
0342         if (openFirstTime && !view->isUnfoldingTree())
0343             view->setFilter(view->filter());
0344     }
0345 
0346     setExpanded(open);
0347 }
0348 
0349 bool UpdateDirItem::operator<(const QTreeWidgetItem &other) const
0350 {
0351     // UpdateDirItems are always lesser than UpdateFileItems
0352     if (isFileItem(&other))
0353         return true;
0354 
0355     const auto &item(static_cast<const UpdateDirItem &>(other));
0356 
0357     // for every column just compare the directory name
0358     return entry().m_name.localeAwareCompare(item.entry().m_name) < 0;
0359 }
0360 
0361 QVariant UpdateDirItem::data(int column, int role) const
0362 {
0363     if ((role == Qt::DisplayRole) && (column == Name))
0364         return entry().m_name;
0365 
0366     return QTreeWidgetItem::data(column, role);
0367 }
0368 
0369 // ------------------------------------------------------------------------------
0370 // UpdateFileItem
0371 // ------------------------------------------------------------------------------
0372 
0373 UpdateFileItem::UpdateFileItem(UpdateDirItem *parent, const Entry &entry)
0374     : UpdateItem(parent, entry, RTTI)
0375     , m_undefined(false)
0376 {
0377 }
0378 
0379 void UpdateFileItem::setStatus(EntryStatus status)
0380 {
0381     if (status != m_entry.m_status) {
0382         m_entry.m_status = status;
0383         emitDataChanged();
0384     }
0385     m_undefined = false;
0386 }
0387 
0388 void UpdateFileItem::accept(Visitor &visitor)
0389 {
0390     visitor.visit(this);
0391 }
0392 
0393 bool UpdateFileItem::applyFilter(UpdateView::Filter filter)
0394 {
0395     bool visible(true);
0396     if (filter & UpdateView::OnlyDirectories)
0397         visible = false;
0398 
0399     bool unmodified = (entry().m_status == Cervisia::UpToDate) || (entry().m_status == Cervisia::Unknown);
0400     if ((filter & UpdateView::NoUpToDate) && unmodified)
0401         visible = false;
0402     if ((filter & UpdateView::NoRemoved) && (entry().m_status == Cervisia::Removed))
0403         visible = false;
0404     if ((filter & UpdateView::NoNotInCVS) && (entry().m_status == Cervisia::NotInCVS))
0405         visible = false;
0406 
0407     setHidden(!visible);
0408 
0409     return visible;
0410 }
0411 
0412 void UpdateFileItem::setRevTag(const QString &rev, const QString &tag)
0413 {
0414     m_entry.m_revision = rev;
0415 
0416     if (tag.length() == 20 && tag[0] == 'D' && tag[5] == '.' && tag[8] == '.' && tag[11] == '.' && tag[14] == '.' && tag[17] == '.') {
0417         const QDate tagDate(tag.mid(1, 4).toInt(), tag.mid(6, 2).toInt(), tag.mid(9, 2).toInt());
0418         const QTime tagTime(tag.mid(12, 2).toInt(), tag.mid(15, 2).toInt(), tag.mid(18, 2).toInt());
0419         const QDateTime tagDateTimeUtc(tagDate, tagTime);
0420 
0421         if (tagDateTimeUtc.isValid()) {
0422             // This is in UTC and must be converted to local time.
0423             //
0424             // A bit strange but I didn't find anything easier which is portable.
0425             // Compute the difference between UTC and local timezone for this
0426             // tag date.
0427             const unsigned int dateTimeInSeconds(tagDateTimeUtc.toTime_t());
0428             QDateTime dateTime;
0429             dateTime.setTime_t(dateTimeInSeconds);
0430             const int localUtcOffset(dateTime.secsTo(tagDateTimeUtc));
0431 
0432             const QDateTime tagDateTimeLocal(tagDateTimeUtc.addSecs(localUtcOffset));
0433 
0434             m_entry.m_tag = QLocale().toString(tagDateTimeLocal);
0435         } else
0436             m_entry.m_tag = tag;
0437     } else if (tag.length() > 1 && tag[0] == 'T')
0438         m_entry.m_tag = tag.mid(1);
0439     else
0440         m_entry.m_tag = tag;
0441 
0442     emitDataChanged();
0443 }
0444 
0445 void UpdateFileItem::setDate(const QDateTime &date)
0446 {
0447     m_entry.m_dateTime = date;
0448 }
0449 
0450 void UpdateFileItem::markUpdated(bool laststage, bool success)
0451 {
0452     EntryStatus newstatus = m_entry.m_status;
0453 
0454     if (laststage) {
0455         if (undefinedState() && m_entry.m_status != Cervisia::NotInCVS)
0456             newstatus = success ? Cervisia::UpToDate : Cervisia::Unknown;
0457         setStatus(newstatus);
0458     } else
0459         setUndefinedState(true);
0460 }
0461 
0462 int UpdateFileItem::statusClass() const
0463 {
0464     int iResult(0);
0465     switch (entry().m_status) {
0466     case Cervisia::Conflict:
0467         iResult = 0;
0468         break;
0469     case Cervisia::LocallyAdded:
0470         iResult = 1;
0471         break;
0472     case Cervisia::LocallyRemoved:
0473         iResult = 2;
0474         break;
0475     case Cervisia::LocallyModified:
0476         iResult = 3;
0477         break;
0478     case Cervisia::Updated:
0479     case Cervisia::NeedsUpdate:
0480     case Cervisia::Patched:
0481     case Cervisia::Removed:
0482     case Cervisia::NeedsPatch:
0483     case Cervisia::NeedsMerge:
0484         iResult = 4;
0485         break;
0486     case Cervisia::NotInCVS:
0487         iResult = 5;
0488         break;
0489     case Cervisia::UpToDate:
0490     case Cervisia::Unknown:
0491         iResult = 6;
0492         break;
0493     }
0494 
0495     return iResult;
0496 }
0497 
0498 bool UpdateFileItem::operator<(const QTreeWidgetItem &other) const
0499 {
0500     // UpdateDirItems are always lesser than UpdateFileItems
0501     if (isDirItem(&other))
0502         return false;
0503 
0504     const auto &item = static_cast<const UpdateFileItem &>(other);
0505 
0506     switch (treeWidget()->sortColumn()) {
0507     case Name:
0508         return entry().m_name.localeAwareCompare(item.entry().m_name) < 0;
0509 
0510     case Status:
0511         if (::compare(statusClass(), item.statusClass()) == 0)
0512             return entry().m_name.localeAwareCompare(item.entry().m_name) < 0;
0513         else
0514             return false;
0515 
0516     case Revision:
0517         return ::compareRevisions(entry().m_revision, item.entry().m_revision) < 0;
0518 
0519     case TagOrDate:
0520         return entry().m_tag.localeAwareCompare(item.entry().m_tag) < 0;
0521 
0522     case Timestamp:
0523         return ::compare(entry().m_dateTime, item.entry().m_dateTime) < 0;
0524     }
0525 
0526     return false;
0527 }
0528 
0529 QVariant UpdateFileItem::data(int column, int role) const
0530 {
0531     if (role == Qt::DisplayRole) {
0532         switch (column) {
0533         case Name:
0534             return entry().m_name;
0535 
0536         case Status:
0537             return toString(entry().m_status);
0538 
0539         case Revision:
0540             return entry().m_revision;
0541 
0542         case TagOrDate:
0543             return entry().m_tag;
0544 
0545         case Timestamp:
0546             if (entry().m_dateTime.isValid())
0547                 return QLocale().toString(entry().m_dateTime);
0548             break;
0549         }
0550     } else if ((role == Qt::ForegroundRole) || (role == Qt::FontRole)) {
0551         const auto view = static_cast<const UpdateView *>(treeWidget());
0552 
0553         QColor color;
0554         switch (m_entry.m_status) {
0555         case Cervisia::Conflict:
0556             color = view->conflictColor();
0557             break;
0558         case Cervisia::LocallyAdded:
0559         case Cervisia::LocallyModified:
0560         case Cervisia::LocallyRemoved:
0561             color = view->localChangeColor();
0562             break;
0563         case Cervisia::NeedsMerge:
0564         case Cervisia::NeedsPatch:
0565         case Cervisia::NeedsUpdate:
0566         case Cervisia::Patched:
0567         case Cervisia::Removed:
0568         case Cervisia::Updated:
0569             color = view->remoteChangeColor();
0570             break;
0571         case Cervisia::NotInCVS:
0572             color = view->notInCvsColor();
0573             break;
0574         case Cervisia::Unknown:
0575         case Cervisia::UpToDate:
0576             break;
0577         }
0578 
0579         // potentially slow - cache it
0580         static QColor schemeForeCol = KColorScheme(QPalette::Active, KColorScheme::View).foreground().color();
0581 
0582         if ((role == Qt::FontRole) && color.isValid() && (color != schemeForeCol)) {
0583             QFont f = view->font();
0584             f.setBold(true);
0585             return f;
0586         }
0587 
0588         if (role == Qt::ForegroundRole)
0589             return color;
0590     }
0591 
0592     return QTreeWidgetItem::data(column, role);
0593 }
0594 
0595 /**
0596  * Finds or creates the UpdateDirItem with path \a dirPath. If \a dirPath
0597  * is "." \a rootItem is returned.
0598  */
0599 UpdateDirItem *findOrCreateDirItem(const QString &dirPath, UpdateDirItem *rootItem)
0600 {
0601     assert(!dirPath.isEmpty());
0602     assert(rootItem);
0603 
0604     UpdateDirItem *dirItem(rootItem);
0605 
0606     if (dirPath != QLatin1String(".")) {
0607         const QStringList &dirNames(dirPath.split('/'));
0608         const QStringList::const_iterator itDirNameEnd(dirNames.end());
0609         for (QStringList::const_iterator itDirName(dirNames.begin()); itDirName != itDirNameEnd; ++itDirName) {
0610             const QString &dirName(*itDirName);
0611 
0612             UpdateItem *item = dirItem->findItem(dirName);
0613             if (isFileItem(item)) {
0614                 // this happens if you
0615                 // - add a directory outside of Cervisia
0616                 // - update status (a file item is created for the directory)
0617                 // - add new directory in Cervisia
0618                 // - update status
0619                 qCDebug(log_cervisia) << "file changed to dir " << dirName;
0620 
0621                 // just create a new dir item, createDirItem() will delete the
0622                 // file item and update the m_itemsByName map
0623                 item = 0;
0624             }
0625 
0626             if (!item) {
0627                 qCDebug(log_cervisia) << "create dir item " << dirName;
0628                 Entry entry;
0629                 entry.m_name = dirName;
0630                 entry.m_type = Entry::Dir;
0631                 item = dirItem->createDirItem(entry);
0632             }
0633 
0634             assert(isDirItem(item));
0635 
0636             dirItem = static_cast<UpdateDirItem *>(item);
0637         }
0638     }
0639 
0640     return dirItem;
0641 }