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 }